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

View as plain text