github.com/dctrud/umoci@v0.4.3-0.20191016193643-05a1d37de015/oci/layer/utils.go (about)

     1  /*
     2   * umoci: Umoci Modifies Open Containers' Images
     3   * Copyright (C) 2016, 2017, 2018 SUSE LLC.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *    http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  
    18  package layer
    19  
    20  import (
    21  	"archive/tar"
    22  	"os"
    23  	"path/filepath"
    24  
    25  	"github.com/apex/log"
    26  	"github.com/golang/protobuf/proto"
    27  	"github.com/openSUSE/umoci/pkg/idtools"
    28  	rspec "github.com/opencontainers/runtime-spec/specs-go"
    29  	"github.com/pkg/errors"
    30  	rootlesscontainers "github.com/rootless-containers/proto/go-proto"
    31  )
    32  
    33  // MapOptions specifies the UID and GID mappings used when unpacking and
    34  // repacking images.
    35  type MapOptions struct {
    36  	// UIDMappings and GIDMappings are the UID and GID mappings to apply when
    37  	// packing and unpacking image rootfs layers.
    38  	UIDMappings []rspec.LinuxIDMapping `json:"uid_mappings"`
    39  	GIDMappings []rspec.LinuxIDMapping `json:"gid_mappings"`
    40  
    41  	// Rootless specifies whether any to error out if chown fails.
    42  	Rootless bool `json:"rootless"`
    43  
    44  	// KeepDirlinks is essentially the same as rsync's optio
    45  	// --keep-dirlinks: if, on extraction, a directory would be created
    46  	// where a symlink to a directory previously existed, KeepDirlinks
    47  	// doesn't create that directory, but instead just uses the existing
    48  	// symlink.
    49  	KeepDirlinks bool `json:"-"`
    50  }
    51  
    52  // mapHeader maps a tar.Header generated from the filesystem so that it
    53  // describes the inode as it would be observed by a container process. In
    54  // particular this involves apply an ID mapping from the host filesystem to the
    55  // container mappings. Returns an error if it's not possible to map the given
    56  // UID.
    57  func mapHeader(hdr *tar.Header, mapOptions MapOptions) error {
    58  	var newUID, newGID int
    59  
    60  	// It only makes sense to do un-mapping if we're not rootless. If we're
    61  	// rootless then all of the files will be owned by us anyway.
    62  	if !mapOptions.Rootless {
    63  		var err error
    64  		newUID, err = idtools.ToContainer(hdr.Uid, mapOptions.UIDMappings)
    65  		if err != nil {
    66  			return errors.Wrap(err, "map uid to container")
    67  		}
    68  		newGID, err = idtools.ToContainer(hdr.Gid, mapOptions.GIDMappings)
    69  		if err != nil {
    70  			return errors.Wrap(err, "map gid to container")
    71  		}
    72  	}
    73  
    74  	// We have special handling for the "user.rootlesscontainers" xattr. If
    75  	// we're rootless then we override the owner of the file we're currently
    76  	// parsing (and then remove the xattr). If we're not rootless then the user
    77  	// is doing something strange, so we log a warning but just ignore the
    78  	// xattr otherwise.
    79  	//
    80  	// TODO: We should probably add a flag to opt-out of this (though I'm not
    81  	//       sure why anyone would intentionally use this incorrectly).
    82  	if value, ok := hdr.Xattrs[rootlesscontainers.Keyname]; !ok {
    83  		// noop
    84  	} else if !mapOptions.Rootless {
    85  		log.Warnf("suspicious filesystem: saw special rootless xattr %s in non-rootless invocation", rootlesscontainers.Keyname)
    86  	} else {
    87  		var payload rootlesscontainers.Resource
    88  		if err := proto.Unmarshal([]byte(value), &payload); err != nil {
    89  			return errors.Wrap(err, "unmarshal rootlesscontainers payload")
    90  		}
    91  
    92  		// If the payload isn't uint32(-1) we apply it. The xattr includes the
    93  		// *in-container* owner so we don't want to map it.
    94  		if uid := payload.GetUid(); uid != rootlesscontainers.NoopID {
    95  			newUID = int(uid)
    96  		}
    97  		if gid := payload.GetGid(); gid != rootlesscontainers.NoopID {
    98  			newGID = int(gid)
    99  		}
   100  
   101  		// Drop the xattr since it's just a marker for us and shouldn't be in
   102  		// layers. This is technically out-of-spec, but so is
   103  		// "user.rootlesscontainers".
   104  		delete(hdr.Xattrs, rootlesscontainers.Keyname)
   105  	}
   106  
   107  	hdr.Uid = newUID
   108  	hdr.Gid = newGID
   109  	return nil
   110  }
   111  
   112  // unmapHeader maps a tar.Header from a tar layer stream so that it describes
   113  // the inode as it would be exist on the host filesystem. In particular this
   114  // involves applying an ID mapping from the container filesystem to the host
   115  // mappings. Returns an error if it's not possible to map the given UID.
   116  func unmapHeader(hdr *tar.Header, mapOptions MapOptions) error {
   117  	// To avoid nil references.
   118  	if hdr.Xattrs == nil {
   119  		hdr.Xattrs = make(map[string]string)
   120  	}
   121  
   122  	// If there is already a "user.rootlesscontainers" we give a warning in
   123  	// both rootless and root cases -- but in rootless we explicitly delete the
   124  	// entry because we might replace it.
   125  	if _, ok := hdr.Xattrs[rootlesscontainers.Keyname]; ok {
   126  		if mapOptions.Rootless {
   127  			log.Warnf("rootless{%s} ignoring special xattr %s stored in layer", hdr.Name, rootlesscontainers.Keyname)
   128  			delete(hdr.Xattrs, rootlesscontainers.Keyname)
   129  		} else {
   130  			log.Warnf("suspicious layer: saw special xattr %s in non-rootless invocation", rootlesscontainers.Keyname)
   131  		}
   132  	}
   133  
   134  	// In rootless mode there are a few things we need to do. We need to map
   135  	// all of the files in the layer to have an owner of (0, 0) because we
   136  	// cannot lchown(2) anything -- and then if the owner was non-root we have
   137  	// to create a "user.rootlesscontainers" xattr for it.
   138  	if mapOptions.Rootless {
   139  		// Fill the rootlesscontainers payload with the original (uid, gid). If
   140  		// either is 0, we replace it with uint32(-1). Technically we could
   141  		// just leave it as 0 (since that is what the source of truth told us
   142  		// the owner was), but this would result in a massive increase in
   143  		// xattrs with no real benefit.
   144  		payload := rootlesscontainers.Resource{
   145  			Uid: rootlesscontainers.NoopID,
   146  			Gid: rootlesscontainers.NoopID,
   147  		}
   148  		if uid := hdr.Uid; uid != 0 {
   149  			payload.Uid = uint32(uid)
   150  		}
   151  		if gid := hdr.Gid; gid != 0 {
   152  			payload.Gid = uint32(gid)
   153  		}
   154  
   155  		// Don't add the xattr if the owner isn't just (0, 0) because that's a
   156  		// waste of space.
   157  		if !rootlesscontainers.IsDefault(payload) {
   158  			valueBytes, err := proto.Marshal(&payload)
   159  			if err != nil {
   160  				return errors.Wrap(err, "marshal rootlesscontainers payload")
   161  			}
   162  			// While the payload is almost certainly not UTF-8, Go strings can
   163  			// actually be arbitrary bytes (in case you didn't know this and
   164  			// were confused like me when this worked). See
   165  			// <https://blog.golang.org/strings> for more detail.
   166  			hdr.Xattrs[rootlesscontainers.Keyname] = string(valueBytes)
   167  		}
   168  
   169  		hdr.Uid = 0
   170  		hdr.Gid = 0
   171  	}
   172  
   173  	newUID, err := idtools.ToHost(hdr.Uid, mapOptions.UIDMappings)
   174  	if err != nil {
   175  		return errors.Wrap(err, "map uid to host")
   176  	}
   177  	newGID, err := idtools.ToHost(hdr.Gid, mapOptions.GIDMappings)
   178  	if err != nil {
   179  		return errors.Wrap(err, "map gid to host")
   180  	}
   181  
   182  	hdr.Uid = newUID
   183  	hdr.Gid = newGID
   184  	return nil
   185  }
   186  
   187  // CleanPath makes a path safe for use with filepath.Join. This is done by not
   188  // only cleaning the path, but also (if the path is relative) adding a leading
   189  // '/' and cleaning it (then removing the leading '/'). This ensures that a
   190  // path resulting from prepending another path will always resolve to lexically
   191  // be a subdirectory of the prefixed path. This is all done lexically, so paths
   192  // that include symlinks won't be safe as a result of using CleanPath.
   193  //
   194  // This function comes from runC (libcontainer/utils/utils.go).
   195  func CleanPath(path string) string {
   196  	// Deal with empty strings nicely.
   197  	if path == "" {
   198  		return ""
   199  	}
   200  
   201  	// Ensure that all paths are cleaned (especially problematic ones like
   202  	// "/../../../../../" which can cause lots of issues).
   203  	path = filepath.Clean(path)
   204  
   205  	// If the path isn't absolute, we need to do more processing to fix paths
   206  	// such as "../../../../<etc>/some/path". We also shouldn't convert absolute
   207  	// paths to relative ones.
   208  	if !filepath.IsAbs(path) {
   209  		path = filepath.Clean(string(os.PathSeparator) + path)
   210  		// This can't fail, as (by definition) all paths are relative to root.
   211  		path, _ = filepath.Rel(string(os.PathSeparator), path)
   212  	}
   213  
   214  	// Clean the path again for good measure.
   215  	return filepath.Clean(path)
   216  }
   217  
   218  // InnerErrno returns the "real" system error from an error that originally
   219  // came from the "os" package. The returned error can be compared directly with
   220  // unix.* (or syscall.*) errno values. If the type could not be detected we just return
   221  func InnerErrno(err error) error {
   222  	// All of the os.* cases as well as an explicit
   223  	errno := errors.Cause(err)
   224  	switch err := errno.(type) {
   225  	case *os.PathError:
   226  		errno = err.Err
   227  	case *os.LinkError:
   228  		errno = err.Err
   229  	case *os.SyscallError:
   230  		errno = err.Err
   231  	}
   232  	return errno
   233  }