github.com/tych0/umoci@v0.4.2/pkg/unpriv/unpriv.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 unpriv
    19  
    20  import (
    21  	"archive/tar"
    22  	"os"
    23  	"path/filepath"
    24  	"sort"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/cyphar/filepath-securejoin"
    29  	"github.com/openSUSE/umoci/pkg/system"
    30  	"github.com/pkg/errors"
    31  	"golang.org/x/sys/unix"
    32  )
    33  
    34  // fiRestore restores the state given by an os.FileInfo instance at the given
    35  // path by ensuring that an Lstat(path) will return as-close-to the same
    36  // os.FileInfo.
    37  func fiRestore(path string, fi os.FileInfo) {
    38  	// archive/tar handles the OS-specific syscall stuff required to get atime
    39  	// and mtime information for a file.
    40  	hdr, _ := tar.FileInfoHeader(fi, "")
    41  
    42  	// Apply the relevant information from the FileInfo.
    43  	// XXX: Should we return errors here to ensure that everything is
    44  	//      deterministic or we fail?
    45  	os.Chmod(path, fi.Mode())
    46  	os.Chtimes(path, hdr.AccessTime, hdr.ModTime)
    47  }
    48  
    49  // splitpath splits the given path into each of the path components.
    50  func splitpath(path string) []string {
    51  	path = filepath.Clean(path)
    52  	parts := strings.Split(path, string(os.PathSeparator))
    53  	if filepath.IsAbs(path) {
    54  		parts = append([]string{string(os.PathSeparator)}, parts...)
    55  	}
    56  	return parts
    57  }
    58  
    59  // WrapFunc is a function that can be passed to Wrap. It takes a path (and
    60  // presumably operates on it -- since Wrap only ensures that the path given is
    61  // resolvable) and returns some form of error.
    62  type WrapFunc func(path string) error
    63  
    64  // Wrap will wrap a given function, and call it in a context where all of the
    65  // parent directories in the given path argument are such that the path can be
    66  // resolved (you may need to make your own changes to the path to make it
    67  // readable). Note that the provided function may be called several times, and
    68  // if the error returned is such that !os.IsPermission(err), then no trickery
    69  // will be performed. If fn returns an error, so will this function. All of the
    70  // trickery is reverted when this function returns (which is when fn returns).
    71  func Wrap(path string, fn WrapFunc) error {
    72  	// FIXME: Should we be calling fn() here first?
    73  	if err := fn(path); err == nil || !os.IsPermission(errors.Cause(err)) {
    74  		return err
    75  	}
    76  
    77  	// We need to chown all of the path components we don't have execute rights
    78  	// to. Specifically these are the path components which are parents of path
    79  	// components we cannot stat. However, we must make sure to not touch the
    80  	// path itself.
    81  	parts := splitpath(filepath.Dir(path))
    82  	start := len(parts)
    83  	for {
    84  		current := filepath.Join(parts[:start]...)
    85  		_, err := os.Lstat(current)
    86  		if err == nil {
    87  			// We've hit the first element we can chown.
    88  			break
    89  		}
    90  		if !os.IsPermission(err) {
    91  			// This is a legitimate error.
    92  			return errors.Wrapf(err, "unpriv.wrap: lstat parent: %s", current)
    93  		}
    94  		start--
    95  	}
    96  	// Chown from the top down.
    97  	for i := start; i <= len(parts); i++ {
    98  		current := filepath.Join(parts[:i]...)
    99  		fi, err := os.Lstat(current)
   100  		if err != nil {
   101  			return errors.Wrapf(err, "unpriv.wrap: lstat parent: %s", current)
   102  		}
   103  		// Add +rwx permissions to directories. If we have the access to change
   104  		// the mode at all then we are the user owner (not just a group owner).
   105  		if err := os.Chmod(current, fi.Mode()|0700); err != nil {
   106  			return errors.Wrapf(err, "unpriv.wrap: chmod parent: %s", current)
   107  		}
   108  		defer fiRestore(current, fi)
   109  	}
   110  
   111  	// Everything is wrapped. Return from this nightmare.
   112  	return fn(path)
   113  }
   114  
   115  // Open is a wrapper around os.Open which has been wrapped with unpriv.Wrap to
   116  // make it possible to open paths even if you do not currently have read
   117  // permission. Note that the returned file handle references a path that you do
   118  // not have read access to (since all changes are reverted when this function
   119  // returns), so attempts to do Readdir() or similar functions that require
   120  // doing lstat(2) may fail.
   121  func Open(path string) (*os.File, error) {
   122  	var fh *os.File
   123  	err := Wrap(path, func(path string) error {
   124  		// Get information so we can revert it.
   125  		fi, err := os.Lstat(path)
   126  		if err != nil {
   127  			return errors.Wrap(err, "lstat file")
   128  		}
   129  
   130  		// Add +r permissions to the file.
   131  		if err := os.Chmod(path, fi.Mode()|0400); err != nil {
   132  			return errors.Wrap(err, "chmod +r")
   133  		}
   134  		defer fiRestore(path, fi)
   135  
   136  		// Open the damn thing.
   137  		fh, err = os.Open(path)
   138  		return err
   139  	})
   140  	return fh, errors.Wrap(err, "unpriv.open")
   141  }
   142  
   143  // Create is a wrapper around os.Create which has been wrapped with unpriv.Wrap
   144  // to make it possible to create paths even if you do not currently have read
   145  // permission. Note that the returned file handle references a path that you do
   146  // not have read access to (since all changes are reverted when this function
   147  // returns).
   148  func Create(path string) (*os.File, error) {
   149  	var fh *os.File
   150  	err := Wrap(path, func(path string) error {
   151  		var err error
   152  		fh, err = os.Create(path)
   153  		return err
   154  	})
   155  	return fh, errors.Wrap(err, "unpriv.create")
   156  }
   157  
   158  // Readdir is a wrapper around (*os.File).Readdir which has been wrapper with
   159  // unpriv.Wrap to make it possible to get []os.FileInfo for the set of children
   160  // of the provided directory path. The interface for this is quite different to
   161  // (*os.File).Readdir because we have to have a proper filesystem path in order
   162  // to get the set of child FileInfos (because all of the child paths need to be
   163  // resolveable).
   164  func Readdir(path string) ([]os.FileInfo, error) {
   165  	var infos []os.FileInfo
   166  	err := Wrap(path, func(path string) error {
   167  		// Get information so we can revert it.
   168  		fi, err := os.Lstat(path)
   169  		if err != nil {
   170  			return errors.Wrap(err, "lstat dir")
   171  		}
   172  
   173  		// Add +rx permissions to the file.
   174  		if err := os.Chmod(path, fi.Mode()|0500); err != nil {
   175  			return errors.Wrap(err, "chmod +rx")
   176  		}
   177  		defer fiRestore(path, fi)
   178  
   179  		// Open the damn thing.
   180  		fh, err := os.Open(path)
   181  		if err != nil {
   182  			return errors.Wrap(err, "opendir")
   183  		}
   184  		defer fh.Close()
   185  
   186  		// Get the set of dirents.
   187  		infos, err = fh.Readdir(-1)
   188  		return err
   189  	})
   190  	return infos, errors.Wrap(err, "unpriv.readdir")
   191  }
   192  
   193  // Lstat is a wrapper around os.Lstat which has been wrapped with unpriv.Wrap
   194  // to make it possible to get os.FileInfo about a path even if you do not
   195  // currently have the required mode bits set to resolve the path. Note that you
   196  // may not have resolve access after this function returns because all of the
   197  // trickery is reverted by unpriv.Wrap.
   198  func Lstat(path string) (os.FileInfo, error) {
   199  	var fi os.FileInfo
   200  	err := Wrap(path, func(path string) error {
   201  		// Fairly simple.
   202  		var err error
   203  		fi, err = os.Lstat(path)
   204  		return err
   205  	})
   206  	return fi, errors.Wrap(err, "unpriv.lstat")
   207  }
   208  
   209  // Lstatx is like Lstat but uses unix.Lstat and returns unix.Stat_t instead
   210  func Lstatx(path string) (unix.Stat_t, error) {
   211  	var s unix.Stat_t
   212  	err := Wrap(path, func(path string) error {
   213  		return unix.Lstat(path, &s)
   214  	})
   215  	return s, errors.Wrap(err, "unpriv.lstatx")
   216  }
   217  
   218  // Readlink is a wrapper around os.Readlink which has been wrapped with
   219  // unpriv.Wrap to make it possible to get the linkname of a symlink even if you
   220  // do not currently have teh required mode bits set to resolve the path. Note
   221  // that you may not have resolve access after this function returns because all
   222  // of this trickery is reverted by unpriv.Wrap.
   223  func Readlink(path string) (string, error) {
   224  	var linkname string
   225  	err := Wrap(path, func(path string) error {
   226  		// Fairly simple.
   227  		var err error
   228  		linkname, err = os.Readlink(path)
   229  		return err
   230  	})
   231  	return linkname, errors.Wrap(err, "unpriv.readlink")
   232  }
   233  
   234  // Symlink is a wrapper around os.Symlink which has been wrapped with
   235  // unpriv.Wrap to make it possible to create a symlink even if you do not
   236  // currently have the required access bits to create the symlink. Note that you
   237  // may not have resolve access after this function returns because all of the
   238  // trickery is reverted by unpriv.Wrap.
   239  func Symlink(linkname, path string) error {
   240  	return errors.Wrap(Wrap(path, func(path string) error {
   241  		return os.Symlink(linkname, path)
   242  	}), "unpriv.symlink")
   243  }
   244  
   245  // Link is a wrapper around os.Link which has been wrapped with unpriv.Wrap to
   246  // make it possible to create a hard link even if you do not currently have the
   247  // required access bits to create the hard link. Note that you may not have
   248  // resolve access after this function returns because all of the trickery is
   249  // reverted by unpriv.Wrap.
   250  func Link(linkname, path string) error {
   251  	return errors.Wrap(Wrap(path, func(path string) error {
   252  		// We have to double-wrap this, because you need search access to the
   253  		// linkname. This is safe because any common ancestors will be reverted
   254  		// in reverse call stack order.
   255  		return errors.Wrap(Wrap(linkname, func(linkname string) error {
   256  			return os.Link(linkname, path)
   257  		}), "unpriv.wrap linkname")
   258  	}), "unpriv.link")
   259  }
   260  
   261  // Chmod is a wrapper around os.Chmod which has been wrapped with unpriv.Wrap
   262  // to make it possible to change the permission bits of a path even if you do
   263  // not currently have the required access bits to access the path.
   264  func Chmod(path string, mode os.FileMode) error {
   265  	return errors.Wrap(Wrap(path, func(path string) error {
   266  		return os.Chmod(path, mode)
   267  	}), "unpriv.chmod")
   268  }
   269  
   270  // Lchown is a wrapper around os.Lchown which has been wrapped with unpriv.Wrap
   271  // to make it possible to change the owner of a path even if you do not
   272  // currently have the required access bits to access the path. Note that this
   273  // function is not particularly useful in most rootless scenarios.
   274  //
   275  // FIXME: This probably should be removed because it's questionably useful.
   276  func Lchown(path string, uid, gid int) error {
   277  	return errors.Wrap(Wrap(path, func(path string) error {
   278  		return os.Lchown(path, uid, gid)
   279  	}), "unpriv.lchown")
   280  }
   281  
   282  // Chtimes is a wrapper around os.Chtimes which has been wrapped with
   283  // unpriv.Wrap to make it possible to change the modified times of a path even
   284  // if you do not currently have the required access bits to access the path.
   285  func Chtimes(path string, atime, mtime time.Time) error {
   286  	return errors.Wrap(Wrap(path, func(path string) error {
   287  		return os.Chtimes(path, atime, mtime)
   288  	}), "unpriv.chtimes")
   289  }
   290  
   291  // Lutimes is a wrapper around system.Lutimes which has been wrapped with
   292  // unpriv.Wrap to make it possible to change the modified times of a path even
   293  // if you do no currently have the required access bits to access the path.
   294  func Lutimes(path string, atime, mtime time.Time) error {
   295  	return errors.Wrap(Wrap(path, func(path string) error {
   296  		return system.Lutimes(path, atime, mtime)
   297  	}), "unpriv.lutimes")
   298  }
   299  
   300  // Remove is a wrapper around os.Remove which has been wrapped with unpriv.Wrap
   301  // to make it possible to remove a path even if you do not currently have the
   302  // required access bits to modify or resolve the path.
   303  func Remove(path string) error {
   304  	return errors.Wrap(Wrap(path, os.Remove), "unpriv.remove")
   305  }
   306  
   307  // foreachSubpath executes WrapFunc for each child of the given path (not
   308  // including the path itself). If path is not a directory, then WrapFunc will
   309  // not be called and no error will be returned. This should be called within a
   310  // context where path has already been made resolveable, however the . If WrapFunc returns an
   311  // error, the first error is returned and iteration is halted.
   312  func foreachSubpath(path string, wrapFn WrapFunc) error {
   313  	// Is the path a directory?
   314  	fi, err := os.Lstat(path)
   315  	if err != nil {
   316  		return errors.WithStack(err)
   317  	}
   318  	if !fi.IsDir() {
   319  		return nil
   320  	}
   321  
   322  	// Open the directory.
   323  	fd, err := Open(path)
   324  	if err != nil {
   325  		return errors.WithStack(err)
   326  	}
   327  	defer fd.Close()
   328  
   329  	// We need to change the mode to Readdirnames. We don't need to worry about
   330  	// permissions because we're already in a context with filepath.Dir(path)
   331  	// is at least a+rx. However, because we are calling wrapFn we need to
   332  	// restore the original mode immediately.
   333  	os.Chmod(path, fi.Mode()|0444)
   334  	names, err := fd.Readdirnames(-1)
   335  	fiRestore(path, fi)
   336  	if err != nil {
   337  		return errors.WithStack(err)
   338  	}
   339  
   340  	// Make iteration order consistent.
   341  	sort.Strings(names)
   342  
   343  	// Call on all the sub-directories. We run it in a Wrap context to ensure
   344  	// that the path we pass is resolveable when executed.
   345  	for _, name := range names {
   346  		subpath := filepath.Join(path, name)
   347  		if err := Wrap(subpath, wrapFn); err != nil {
   348  			return err
   349  		}
   350  	}
   351  	return nil
   352  }
   353  
   354  // RemoveAll is similar to os.RemoveAll but with all of the internal functions
   355  // wrapped with unpriv.Wrap to make it possible to remove a path (even if it
   356  // has child paths) even if you do not currently have enough access bits.
   357  func RemoveAll(path string) error {
   358  	return errors.Wrap(Wrap(path, func(path string) error {
   359  		// If remove works, we're done.
   360  		err := os.Remove(path)
   361  		if err == nil || os.IsNotExist(errors.Cause(err)) {
   362  			return nil
   363  		}
   364  
   365  		// Is this a directory?
   366  		fi, serr := os.Lstat(path)
   367  		if serr != nil {
   368  			// Use securejoin's IsNotExist to handle ENOTDIR sanely.
   369  			if securejoin.IsNotExist(errors.Cause(serr)) {
   370  				serr = nil
   371  			}
   372  			return errors.Wrap(serr, "lstat")
   373  		}
   374  		// Return error from remove if it's not a directory.
   375  		if !fi.IsDir() {
   376  			return errors.Wrap(err, "remove non-directory")
   377  		}
   378  		err = nil
   379  
   380  		err1 := foreachSubpath(path, func(subpath string) error {
   381  			err2 := RemoveAll(subpath)
   382  			if err == nil {
   383  				err = err2
   384  			}
   385  			return nil
   386  		})
   387  		if err1 != nil {
   388  			// We must have hit a race, but we don't care.
   389  			if os.IsNotExist(errors.Cause(err1)) {
   390  				err1 = nil
   391  			}
   392  			return errors.Wrap(err1, "foreach subpath")
   393  		}
   394  
   395  		// Remove the directory. This should now work.
   396  		err1 = os.Remove(path)
   397  		if err1 == nil || os.IsNotExist(errors.Cause(err1)) {
   398  			return nil
   399  		}
   400  		if err == nil {
   401  			err = err1
   402  		}
   403  		return errors.Wrap(err, "remove")
   404  	}), "unpriv.removeall")
   405  }
   406  
   407  // Mkdir is a wrapper around os.Mkdir which has been wrapped with unpriv.Wrap
   408  // to make it possible to remove a path even if you do not currently have the
   409  // required access bits to modify or resolve the path.
   410  func Mkdir(path string, perm os.FileMode) error {
   411  	return errors.Wrap(Wrap(path, func(path string) error {
   412  		return os.Mkdir(path, perm)
   413  	}), "unpriv.mkdir")
   414  }
   415  
   416  // MkdirAll is similar to os.MkdirAll but in order to implement it properly all
   417  // of the internal functions were wrapped with unpriv.Wrap to make it possible
   418  // to create a path even if you do not currently have enough access bits.
   419  func MkdirAll(path string, perm os.FileMode) error {
   420  	return errors.Wrap(Wrap(path, func(path string) error {
   421  		// Check whether the path already exists.
   422  		fi, err := os.Stat(path)
   423  		if err == nil {
   424  			if fi.IsDir() {
   425  				return nil
   426  			}
   427  			return &os.PathError{Op: "mkdir", Path: path, Err: unix.ENOTDIR}
   428  		}
   429  
   430  		// Create parent.
   431  		parent := filepath.Dir(path)
   432  		if parent != "." && parent != "/" {
   433  			err = MkdirAll(parent, perm)
   434  			if err != nil {
   435  				return err
   436  			}
   437  		}
   438  
   439  		// Parent exists, now we can create the path.
   440  		err = os.Mkdir(path, perm)
   441  		if err != nil {
   442  			// Handle "foo/.".
   443  			fi, err1 := os.Lstat(path)
   444  			if err1 == nil && fi.IsDir() {
   445  				return nil
   446  			}
   447  			return err
   448  		}
   449  		return nil
   450  	}), "unpriv.mkdirall")
   451  }
   452  
   453  // Mknod is a wrapper around unix.Mknod which has been wrapped with unpriv.Wrap
   454  // to make it possible to remove a path even if you do not currently have the
   455  // required access bits to modify or resolve the path.
   456  func Mknod(path string, mode os.FileMode, dev uint64) error {
   457  	return errors.Wrap(Wrap(path, func(path string) error {
   458  		return unix.Mknod(path, uint32(mode), int(dev))
   459  	}), "unpriv.mknod")
   460  }
   461  
   462  // Llistxattr is a wrapper around system.Llistxattr which has been wrapped with
   463  // unpriv.Wrap to make it possible to remove a path even if you do not
   464  // currently have the required access bits to resolve the path.
   465  func Llistxattr(path string) ([]string, error) {
   466  	var xattrs []string
   467  	err := Wrap(path, func(path string) error {
   468  		var err error
   469  		xattrs, err = system.Llistxattr(path)
   470  		return err
   471  	})
   472  	return xattrs, errors.Wrap(err, "unpriv.llistxattr")
   473  }
   474  
   475  // Lremovexattr is a wrapper around system.Lremovexattr which has been wrapped
   476  // with unpriv.Wrap to make it possible to remove a path even if you do not
   477  // currently have the required access bits to resolve the path.
   478  func Lremovexattr(path, name string) error {
   479  	return errors.Wrap(Wrap(path, func(path string) error {
   480  		return unix.Lremovexattr(path, name)
   481  	}), "unpriv.lremovexattr")
   482  }
   483  
   484  // Lsetxattr is a wrapper around system.Lsetxattr which has been wrapped
   485  // with unpriv.Wrap to make it possible to set a path even if you do not
   486  // currently have the required access bits to resolve the path.
   487  func Lsetxattr(path, name string, value []byte, flags int) error {
   488  	return errors.Wrap(Wrap(path, func(path string) error {
   489  		return unix.Lsetxattr(path, name, value, flags)
   490  	}), "unpriv.lsetxattr")
   491  }
   492  
   493  // Lgetxattr is a wrapper around system.Lgetxattr which has been wrapped
   494  // with unpriv.Wrap to make it possible to get a path even if you do not
   495  // currently have the required access bits to resolve the path.
   496  func Lgetxattr(path, name string) ([]byte, error) {
   497  	var value []byte
   498  	err := Wrap(path, func(path string) error {
   499  		var err error
   500  		value, err = system.Lgetxattr(path, name)
   501  		return err
   502  	})
   503  	return value, errors.Wrap(err, "unpriv.lgetxattr")
   504  }
   505  
   506  // Lclearxattrs is similar to system.Lclearxattrs but in order to implement it
   507  // properly all of the internal functions were wrapped with unpriv.Wrap to make
   508  // it possible to create a path even if you do not currently have enough access
   509  // bits.
   510  func Lclearxattrs(path string, except map[string]struct{}) error {
   511  	return errors.Wrap(Wrap(path, func(path string) error {
   512  		names, err := Llistxattr(path)
   513  		if err != nil {
   514  			return err
   515  		}
   516  		for _, name := range names {
   517  			if _, skip := except[name]; skip {
   518  				continue
   519  			}
   520  			if err := Lremovexattr(path, name); err != nil {
   521  				// SELinux won't let you change security.selinux (for obvious
   522  				// security reasons), so we don't clear xattrs if attempting to
   523  				// clear them causes an EPERM. This EPERM will not be due to
   524  				// resolution issues (Llistxattr already has done that for us).
   525  				if os.IsPermission(errors.Cause(err)) {
   526  					continue
   527  				}
   528  				return err
   529  			}
   530  		}
   531  		return nil
   532  	}), "unpriv.lclearxattrs")
   533  }
   534  
   535  // walk is the inner implementation of Walk.
   536  func walk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
   537  	// Always run walkFn first. If we're not a directory there's no children to
   538  	// iterate over and so we bail even if there wasn't an error.
   539  	err := walkFn(path, info, nil)
   540  	if !info.IsDir() || err != nil {
   541  		return err
   542  	}
   543  
   544  	// Now just execute walkFn over each subpath.
   545  	return foreachSubpath(path, func(subpath string) error {
   546  		info, err := Lstat(subpath)
   547  		if err != nil {
   548  			// If it doesn't exist, just pass it directly to walkFn.
   549  			if err := walkFn(subpath, info, err); err != nil {
   550  				// Ignore SkipDir.
   551  				if errors.Cause(err) != filepath.SkipDir {
   552  					return err
   553  				}
   554  			}
   555  		} else {
   556  			if err := walk(subpath, info, walkFn); err != nil {
   557  				// Ignore error if it's SkipDir and subpath is a directory.
   558  				if !(info.IsDir() && errors.Cause(err) == filepath.SkipDir) {
   559  					return err
   560  				}
   561  			}
   562  		}
   563  		return nil
   564  	})
   565  }
   566  
   567  // Walk is a reimplementation of filepath.Walk, wrapping all of the relevant
   568  // function calls with Wrap, allowing you to walk over a tree even in the face
   569  // of multiple nested cases where paths are not normally accessible. The
   570  // os.FileInfo passed to walkFn is the "pristine" version (as opposed to the
   571  // currently-on-disk version that may have been temporarily modified by Wrap).
   572  func Walk(root string, walkFn filepath.WalkFunc) error {
   573  	return Wrap(root, func(root string) error {
   574  		info, err := Lstat(root)
   575  		if err != nil {
   576  			err = walkFn(root, nil, err)
   577  		} else {
   578  			err = walk(root, info, walkFn)
   579  		}
   580  		if errors.Cause(err) == filepath.SkipDir {
   581  			err = nil
   582  		}
   583  		return errors.Wrap(err, "unpriv.walk")
   584  	})
   585  }