...

Source file src/github.com/opencontainers/runc/libcontainer/utils/utils.go

Documentation: github.com/opencontainers/runc/libcontainer/utils

     1  package utils
     2  
     3  import (
     4  	"encoding/binary"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  	"strconv"
    11  	"strings"
    12  	"unsafe"
    13  
    14  	securejoin "github.com/cyphar/filepath-securejoin"
    15  	"golang.org/x/sys/unix"
    16  )
    17  
    18  const (
    19  	exitSignalOffset = 128
    20  )
    21  
    22  // NativeEndian is the native byte order of the host system.
    23  var NativeEndian binary.ByteOrder
    24  
    25  func init() {
    26  	// Copied from <golang.org/x/net/internal/socket/sys.go>.
    27  	i := uint32(1)
    28  	b := (*[4]byte)(unsafe.Pointer(&i))
    29  	if b[0] == 1 {
    30  		NativeEndian = binary.LittleEndian
    31  	} else {
    32  		NativeEndian = binary.BigEndian
    33  	}
    34  }
    35  
    36  // ExitStatus returns the correct exit status for a process based on if it
    37  // was signaled or exited cleanly
    38  func ExitStatus(status unix.WaitStatus) int {
    39  	if status.Signaled() {
    40  		return exitSignalOffset + int(status.Signal())
    41  	}
    42  	return status.ExitStatus()
    43  }
    44  
    45  // WriteJSON writes the provided struct v to w using standard json marshaling
    46  func WriteJSON(w io.Writer, v interface{}) error {
    47  	data, err := json.Marshal(v)
    48  	if err != nil {
    49  		return err
    50  	}
    51  	_, err = w.Write(data)
    52  	return err
    53  }
    54  
    55  // CleanPath makes a path safe for use with filepath.Join. This is done by not
    56  // only cleaning the path, but also (if the path is relative) adding a leading
    57  // '/' and cleaning it (then removing the leading '/'). This ensures that a
    58  // path resulting from prepending another path will always resolve to lexically
    59  // be a subdirectory of the prefixed path. This is all done lexically, so paths
    60  // that include symlinks won't be safe as a result of using CleanPath.
    61  func CleanPath(path string) string {
    62  	// Deal with empty strings nicely.
    63  	if path == "" {
    64  		return ""
    65  	}
    66  
    67  	// Ensure that all paths are cleaned (especially problematic ones like
    68  	// "/../../../../../" which can cause lots of issues).
    69  	path = filepath.Clean(path)
    70  
    71  	// If the path isn't absolute, we need to do more processing to fix paths
    72  	// such as "../../../../<etc>/some/path". We also shouldn't convert absolute
    73  	// paths to relative ones.
    74  	if !filepath.IsAbs(path) {
    75  		path = filepath.Clean(string(os.PathSeparator) + path)
    76  		// This can't fail, as (by definition) all paths are relative to root.
    77  		path, _ = filepath.Rel(string(os.PathSeparator), path)
    78  	}
    79  
    80  	// Clean the path again for good measure.
    81  	return filepath.Clean(path)
    82  }
    83  
    84  // stripRoot returns the passed path, stripping the root path if it was
    85  // (lexicially) inside it. Note that both passed paths will always be treated
    86  // as absolute, and the returned path will also always be absolute. In
    87  // addition, the paths are cleaned before stripping the root.
    88  func stripRoot(root, path string) string {
    89  	// Make the paths clean and absolute.
    90  	root, path = CleanPath("/"+root), CleanPath("/"+path)
    91  	switch {
    92  	case path == root:
    93  		path = "/"
    94  	case root == "/":
    95  		// do nothing
    96  	case strings.HasPrefix(path, root+"/"):
    97  		path = strings.TrimPrefix(path, root+"/")
    98  	}
    99  	return CleanPath("/" + path)
   100  }
   101  
   102  // WithProcfd runs the passed closure with a procfd path (/proc/self/fd/...)
   103  // corresponding to the unsafePath resolved within the root. Before passing the
   104  // fd, this path is verified to have been inside the root -- so operating on it
   105  // through the passed fdpath should be safe. Do not access this path through
   106  // the original path strings, and do not attempt to use the pathname outside of
   107  // the passed closure (the file handle will be freed once the closure returns).
   108  func WithProcfd(root, unsafePath string, fn func(procfd string) error) error {
   109  	// Remove the root then forcefully resolve inside the root.
   110  	unsafePath = stripRoot(root, unsafePath)
   111  	path, err := securejoin.SecureJoin(root, unsafePath)
   112  	if err != nil {
   113  		return fmt.Errorf("resolving path inside rootfs failed: %w", err)
   114  	}
   115  
   116  	// Open the target path.
   117  	fh, err := os.OpenFile(path, unix.O_PATH|unix.O_CLOEXEC, 0)
   118  	if err != nil {
   119  		return fmt.Errorf("open o_path procfd: %w", err)
   120  	}
   121  	defer fh.Close()
   122  
   123  	// Double-check the path is the one we expected.
   124  	procfd := "/proc/self/fd/" + strconv.Itoa(int(fh.Fd()))
   125  	if realpath, err := os.Readlink(procfd); err != nil {
   126  		return fmt.Errorf("procfd verification failed: %w", err)
   127  	} else if realpath != path {
   128  		return fmt.Errorf("possibly malicious path detected -- refusing to operate on %s", realpath)
   129  	}
   130  
   131  	// Run the closure.
   132  	return fn(procfd)
   133  }
   134  
   135  // SearchLabels searches a list of key-value pairs for the provided key and
   136  // returns the corresponding value. The pairs must be separated with '='.
   137  func SearchLabels(labels []string, query string) string {
   138  	for _, l := range labels {
   139  		parts := strings.SplitN(l, "=", 2)
   140  		if len(parts) < 2 {
   141  			continue
   142  		}
   143  		if parts[0] == query {
   144  			return parts[1]
   145  		}
   146  	}
   147  	return ""
   148  }
   149  
   150  // Annotations returns the bundle path and user defined annotations from the
   151  // libcontainer state.  We need to remove the bundle because that is a label
   152  // added by libcontainer.
   153  func Annotations(labels []string) (bundle string, userAnnotations map[string]string) {
   154  	userAnnotations = make(map[string]string)
   155  	for _, l := range labels {
   156  		parts := strings.SplitN(l, "=", 2)
   157  		if len(parts) < 2 {
   158  			continue
   159  		}
   160  		if parts[0] == "bundle" {
   161  			bundle = parts[1]
   162  		} else {
   163  			userAnnotations[parts[0]] = parts[1]
   164  		}
   165  	}
   166  	return
   167  }
   168  

View as plain text