Source file src/os/root_openat.go

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build unix || windows || wasip1
     6  
     7  package os
     8  
     9  import (
    10  	"runtime"
    11  	"slices"
    12  	"sync"
    13  	"syscall"
    14  	"time"
    15  )
    16  
    17  // root implementation for platforms with a function to open a file
    18  // relative to a directory.
    19  type root struct {
    20  	name string
    21  
    22  	// refs is incremented while an operation is using fd.
    23  	// closed is set when Close is called.
    24  	// fd is closed when closed is true and refs is 0.
    25  	mu      sync.Mutex
    26  	fd      sysfdType
    27  	refs    int             // number of active operations
    28  	closed  bool            // set when closed
    29  	cleanup runtime.Cleanup // cleanup closes the file when no longer referenced
    30  }
    31  
    32  func (r *root) Close() error {
    33  	r.mu.Lock()
    34  	defer r.mu.Unlock()
    35  	if !r.closed && r.refs == 0 {
    36  		syscall.Close(r.fd)
    37  	}
    38  	r.closed = true
    39  	// There is no need for a cleanup at this point. Root must be alive at the point
    40  	// where cleanup.stop is called.
    41  	r.cleanup.Stop()
    42  	return nil
    43  }
    44  
    45  func (r *root) incref() error {
    46  	r.mu.Lock()
    47  	defer r.mu.Unlock()
    48  	if r.closed {
    49  		return ErrClosed
    50  	}
    51  	r.refs++
    52  	return nil
    53  }
    54  
    55  func (r *root) decref() {
    56  	r.mu.Lock()
    57  	defer r.mu.Unlock()
    58  	if r.refs <= 0 {
    59  		panic("bad Root refcount")
    60  	}
    61  	r.refs--
    62  	if r.closed && r.refs == 0 {
    63  		syscall.Close(r.fd)
    64  	}
    65  }
    66  
    67  func (r *root) Name() string {
    68  	return r.name
    69  }
    70  
    71  func rootChmod(r *Root, name string, mode FileMode) error {
    72  	_, err := doInRoot(r, name, nil, func(parent sysfdType, name string) (struct{}, error) {
    73  		return struct{}{}, chmodat(parent, name, mode)
    74  	})
    75  	if err != nil {
    76  		return &PathError{Op: "chmodat", Path: name, Err: err}
    77  	}
    78  	return nil
    79  }
    80  
    81  func rootChown(r *Root, name string, uid, gid int) error {
    82  	_, err := doInRoot(r, name, nil, func(parent sysfdType, name string) (struct{}, error) {
    83  		return struct{}{}, chownat(parent, name, uid, gid)
    84  	})
    85  	if err != nil {
    86  		return &PathError{Op: "chownat", Path: name, Err: err}
    87  	}
    88  	return nil
    89  }
    90  
    91  func rootLchown(r *Root, name string, uid, gid int) error {
    92  	_, err := doInRoot(r, name, nil, func(parent sysfdType, name string) (struct{}, error) {
    93  		return struct{}{}, lchownat(parent, name, uid, gid)
    94  	})
    95  	if err != nil {
    96  		return &PathError{Op: "lchownat", Path: name, Err: err}
    97  	}
    98  	return err
    99  }
   100  
   101  func rootChtimes(r *Root, name string, atime time.Time, mtime time.Time) error {
   102  	_, err := doInRoot(r, name, nil, func(parent sysfdType, name string) (struct{}, error) {
   103  		return struct{}{}, chtimesat(parent, name, atime, mtime)
   104  	})
   105  	if err != nil {
   106  		return &PathError{Op: "chtimesat", Path: name, Err: err}
   107  	}
   108  	return err
   109  }
   110  
   111  func rootMkdir(r *Root, name string, perm FileMode) error {
   112  	_, err := doInRoot(r, name, nil, func(parent sysfdType, name string) (struct{}, error) {
   113  		return struct{}{}, mkdirat(parent, name, perm)
   114  	})
   115  	if err != nil {
   116  		return &PathError{Op: "mkdirat", Path: name, Err: err}
   117  	}
   118  	return nil
   119  }
   120  
   121  func rootMkdirAll(r *Root, fullname string, perm FileMode) error {
   122  	// doInRoot opens each path element in turn.
   123  	//
   124  	// openDirFunc opens all but the last path component.
   125  	// The usual default openDirFunc just opens directories with O_DIRECTORY.
   126  	// We replace it here with one that creates missing directories along the way.
   127  	openDirFunc := func(parent sysfdType, name string) (sysfdType, error) {
   128  		for try := range 2 {
   129  			fd, err := rootOpenDir(parent, name)
   130  			switch err.(type) {
   131  			case nil, errSymlink:
   132  				return fd, err
   133  			}
   134  			if try > 0 || !IsNotExist(err) {
   135  				return 0, &PathError{Op: "openat", Err: err}
   136  			}
   137  			if err := mkdirat(parent, name, perm); err != nil {
   138  				return 0, &PathError{Op: "mkdirat", Err: err}
   139  			}
   140  		}
   141  		panic("unreachable")
   142  	}
   143  	// openLastComponentFunc opens the last path component.
   144  	openLastComponentFunc := func(parent sysfdType, name string) (struct{}, error) {
   145  		err := mkdirat(parent, name, perm)
   146  		if err == syscall.EEXIST {
   147  			mode, e := modeAt(parent, name)
   148  			if e == nil {
   149  				if mode.IsDir() {
   150  					// The target of MkdirAll is an existing directory.
   151  					err = nil
   152  				} else if mode&ModeSymlink != 0 {
   153  					// The target of MkdirAll is a symlink.
   154  					// For consistency with os.MkdirAll,
   155  					// succeed if the link resolves to a directory.
   156  					// We don't return errSymlink here, because we don't
   157  					// want to create the link target if it doesn't exist.
   158  					fi, e := r.Stat(fullname)
   159  					if e == nil && fi.Mode().IsDir() {
   160  						err = nil
   161  					}
   162  				}
   163  			}
   164  		}
   165  		switch err.(type) {
   166  		case nil, errSymlink:
   167  			return struct{}{}, err
   168  		}
   169  		return struct{}{}, &PathError{Op: "mkdirat", Err: err}
   170  	}
   171  	_, err := doInRoot(r, fullname, openDirFunc, openLastComponentFunc)
   172  	if err != nil {
   173  		if _, ok := err.(*PathError); !ok {
   174  			err = &PathError{Op: "mkdirat", Path: fullname, Err: err}
   175  		}
   176  	}
   177  	return err
   178  }
   179  
   180  func rootReadlink(r *Root, name string) (string, error) {
   181  	target, err := doInRoot(r, name, nil, func(parent sysfdType, name string) (string, error) {
   182  		return readlinkat(parent, name)
   183  	})
   184  	if err != nil {
   185  		return "", &PathError{Op: "readlinkat", Path: name, Err: err}
   186  	}
   187  	return target, nil
   188  }
   189  
   190  func rootRemove(r *Root, name string) error {
   191  	_, err := doInRoot(r, name, nil, func(parent sysfdType, name string) (struct{}, error) {
   192  		return struct{}{}, removeat(parent, name)
   193  	})
   194  	if err != nil {
   195  		return &PathError{Op: "removeat", Path: name, Err: err}
   196  	}
   197  	return nil
   198  }
   199  
   200  func rootRemoveAll(r *Root, name string) error {
   201  	// Consistency with os.RemoveAll: Strip trailing /s from the name,
   202  	// so RemoveAll("not_a_directory/") succeeds.
   203  	for len(name) > 0 && IsPathSeparator(name[len(name)-1]) {
   204  		name = name[:len(name)-1]
   205  	}
   206  	if endsWithDot(name) {
   207  		// Consistency with os.RemoveAll: Return EINVAL when trying to remove .
   208  		return &PathError{Op: "RemoveAll", Path: name, Err: syscall.EINVAL}
   209  	}
   210  	_, err := doInRoot(r, name, nil, func(parent sysfdType, name string) (struct{}, error) {
   211  		return struct{}{}, removeAllFrom(parent, name)
   212  	})
   213  	if IsNotExist(err) {
   214  		return nil
   215  	}
   216  	if err != nil {
   217  		return &PathError{Op: "RemoveAll", Path: name, Err: underlyingError(err)}
   218  	}
   219  	return err
   220  }
   221  
   222  func rootRename(r *Root, oldname, newname string) error {
   223  	_, err := doInRoot(r, oldname, nil, func(oldparent sysfdType, oldname string) (struct{}, error) {
   224  		_, err := doInRoot(r, newname, nil, func(newparent sysfdType, newname string) (struct{}, error) {
   225  			return struct{}{}, renameat(oldparent, oldname, newparent, newname)
   226  		})
   227  		return struct{}{}, err
   228  	})
   229  	if err != nil {
   230  		return &LinkError{"renameat", oldname, newname, err}
   231  	}
   232  	return err
   233  }
   234  
   235  func rootLink(r *Root, oldname, newname string) error {
   236  	_, err := doInRoot(r, oldname, nil, func(oldparent sysfdType, oldname string) (struct{}, error) {
   237  		_, err := doInRoot(r, newname, nil, func(newparent sysfdType, newname string) (struct{}, error) {
   238  			return struct{}{}, linkat(oldparent, oldname, newparent, newname)
   239  		})
   240  		return struct{}{}, err
   241  	})
   242  	if err != nil {
   243  		return &LinkError{"linkat", oldname, newname, err}
   244  	}
   245  	return err
   246  }
   247  
   248  // doInRoot performs an operation on a path in a Root.
   249  //
   250  // It calls f with the FD or handle for the directory containing the last
   251  // path element, and the name of the last path element.
   252  //
   253  // For example, given the path a/b/c it calls f with the FD for a/b and the name "c".
   254  //
   255  // If openDirFunc is non-nil, it is called to open intermediate path elements.
   256  // For example, given the path a/b/c openDirFunc will be called to open a and a/b in turn.
   257  //
   258  // f or openDirFunc may return errSymlink to indicate that the path element is a symlink
   259  // which should be followed. Note that this can result in f being called multiple times
   260  // with different names. For example, give the path "link" which is a symlink to "target",
   261  // f is called with the path "link", returns errSymlink("target"), and is called again with
   262  // the path "target".
   263  //
   264  // If f or openDirFunc return a *PathError, doInRoot will set PathError.Path to the
   265  // full path which caused the error.
   266  func doInRoot[T any](r *Root, name string, openDirFunc func(parent sysfdType, name string) (sysfdType, error), f func(parent sysfdType, name string) (T, error)) (ret T, err error) {
   267  	if err := r.root.incref(); err != nil {
   268  		return ret, err
   269  	}
   270  	defer r.root.decref()
   271  
   272  	parts, suffixSep, err := splitPathInRoot(name, nil, nil)
   273  	if err != nil {
   274  		return ret, err
   275  	}
   276  	if openDirFunc == nil {
   277  		openDirFunc = rootOpenDir
   278  	}
   279  
   280  	rootfd := r.root.fd
   281  	dirfd := rootfd
   282  	defer func() {
   283  		if dirfd != rootfd {
   284  			syscall.Close(dirfd)
   285  		}
   286  	}()
   287  
   288  	// When resolving .. path components, we restart path resolution from the root.
   289  	// (We can't openat(dir, "..") to move up to the parent directory,
   290  	// because dir may have moved since we opened it.)
   291  	// To limit how many opens a malicious path can cause us to perform, we set
   292  	// a limit on the total number of path steps and the total number of restarts
   293  	// caused by .. components. If *both* limits are exceeded, we halt the operation.
   294  	const maxSteps = 255
   295  	const maxRestarts = 8
   296  
   297  	i := 0
   298  	steps := 0
   299  	restarts := 0
   300  	symlinks := 0
   301  Loop:
   302  	for {
   303  		steps++
   304  		if steps > maxSteps && restarts > maxRestarts {
   305  			return ret, syscall.ENAMETOOLONG
   306  		}
   307  
   308  		if parts[i] == ".." {
   309  			// Resolve one or more parent ("..") path components.
   310  			//
   311  			// Rewrite the original path,
   312  			// removing the elements eliminated by ".." components,
   313  			// and start over from the beginning.
   314  			restarts++
   315  			end := i + 1
   316  			for end < len(parts) && parts[end] == ".." {
   317  				end++
   318  			}
   319  			count := end - i
   320  			if count > i {
   321  				return ret, errPathEscapes
   322  			}
   323  			parts = slices.Delete(parts, i-count, end)
   324  			if len(parts) == 0 {
   325  				parts = []string{"."}
   326  			}
   327  			i = 0
   328  			if dirfd != rootfd {
   329  				syscall.Close(dirfd)
   330  			}
   331  			dirfd = rootfd
   332  			continue
   333  		}
   334  
   335  		if i == len(parts)-1 {
   336  			// This is the last path element.
   337  			// Call f to decide what to do with it.
   338  			// If f returns errSymlink, this element is a symlink
   339  			// which should be followed.
   340  			// suffixSep contains any trailing separator characters
   341  			// which we rejoin to the final part at this time.
   342  			ret, err = f(dirfd, parts[i]+suffixSep)
   343  			if err == nil {
   344  				return
   345  			}
   346  		} else {
   347  			var fd sysfdType
   348  			fd, err = openDirFunc(dirfd, parts[i])
   349  			if err == nil {
   350  				if dirfd != rootfd {
   351  					syscall.Close(dirfd)
   352  				}
   353  				dirfd = fd
   354  			}
   355  		}
   356  
   357  		switch e := err.(type) {
   358  		case nil:
   359  		case errSymlink:
   360  			symlinks++
   361  			if symlinks > rootMaxSymlinks {
   362  				return ret, syscall.ELOOP
   363  			}
   364  			newparts, newSuffixSep, err := splitPathInRoot(string(e), parts[:i], parts[i+1:])
   365  			if err != nil {
   366  				return ret, err
   367  			}
   368  			if i == len(parts)-1 {
   369  				// suffixSep contains any trailing path separator characters
   370  				// in the link target.
   371  				// If we are replacing the remainder of the path, retain these.
   372  				// If we're replacing some intermediate component of the path,
   373  				// ignore them, since intermediate components must always be
   374  				// directories.
   375  				suffixSep = newSuffixSep
   376  			}
   377  			if len(newparts) < i || !slices.Equal(parts[:i], newparts[:i]) {
   378  				// Some component in the path which we have already traversed
   379  				// has changed. We need to restart parsing from the root.
   380  				i = 0
   381  				if dirfd != rootfd {
   382  					syscall.Close(dirfd)
   383  				}
   384  				dirfd = rootfd
   385  			}
   386  			parts = newparts
   387  			continue Loop
   388  		case *PathError:
   389  			// This is strings.Join(parts[:i+1], PathSeparator).
   390  			e.Path = parts[0]
   391  			for _, part := range parts[1 : i+1] {
   392  				e.Path += string(PathSeparator) + part
   393  			}
   394  			return ret, e
   395  		default:
   396  			return ret, err
   397  		}
   398  
   399  		i++
   400  	}
   401  }
   402  
   403  // errSymlink reports that a file being operated on is actually a symlink,
   404  // and the target of that symlink.
   405  type errSymlink string
   406  
   407  func (errSymlink) Error() string { panic("errSymlink is not user-visible") }
   408  

View as plain text