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 aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows || wasip1
     6  
     7  package os
     8  
     9  import (
    10  	"runtime"
    11  	"slices"
    12  	"sync"
    13  	"syscall"
    14  )
    15  
    16  // root implementation for platforms with a function to open a file
    17  // relative to a directory.
    18  type root struct {
    19  	name string
    20  
    21  	// refs is incremented while an operation is using fd.
    22  	// closed is set when Close is called.
    23  	// fd is closed when closed is true and refs is 0.
    24  	mu     sync.Mutex
    25  	fd     sysfdType
    26  	refs   int  // number of active operations
    27  	closed bool // set when closed
    28  }
    29  
    30  func (r *root) Close() error {
    31  	r.mu.Lock()
    32  	defer r.mu.Unlock()
    33  	if !r.closed && r.refs == 0 {
    34  		syscall.Close(r.fd)
    35  	}
    36  	r.closed = true
    37  	runtime.SetFinalizer(r, nil) // no need for a finalizer any more
    38  	return nil
    39  }
    40  
    41  func (r *root) incref() error {
    42  	r.mu.Lock()
    43  	defer r.mu.Unlock()
    44  	if r.closed {
    45  		return ErrClosed
    46  	}
    47  	r.refs++
    48  	return nil
    49  }
    50  
    51  func (r *root) decref() {
    52  	r.mu.Lock()
    53  	defer r.mu.Unlock()
    54  	if r.refs <= 0 {
    55  		panic("bad Root refcount")
    56  	}
    57  	r.refs--
    58  	if r.closed && r.refs == 0 {
    59  		syscall.Close(r.fd)
    60  	}
    61  }
    62  
    63  func (r *root) Name() string {
    64  	return r.name
    65  }
    66  
    67  func rootMkdir(r *Root, name string, perm FileMode) error {
    68  	_, err := doInRoot(r, name, func(parent sysfdType, name string) (struct{}, error) {
    69  		return struct{}{}, mkdirat(parent, name, perm)
    70  	})
    71  	if err != nil {
    72  		return &PathError{Op: "mkdirat", Path: name, Err: err}
    73  	}
    74  	return err
    75  }
    76  
    77  func rootRemove(r *Root, name string) error {
    78  	_, err := doInRoot(r, name, func(parent sysfdType, name string) (struct{}, error) {
    79  		return struct{}{}, removeat(parent, name)
    80  	})
    81  	if err != nil {
    82  		return &PathError{Op: "removeat", Path: name, Err: err}
    83  	}
    84  	return err
    85  }
    86  
    87  // doInRoot performs an operation on a path in a Root.
    88  //
    89  // It opens the directory containing the final element of the path,
    90  // and calls f with the directory FD and name of the final element.
    91  //
    92  // If the path refers to a symlink which should be followed,
    93  // then f must return errSymlink.
    94  // doInRoot will follow the symlink and call f again.
    95  func doInRoot[T any](r *Root, name string, f func(parent sysfdType, name string) (T, error)) (ret T, err error) {
    96  	if err := r.root.incref(); err != nil {
    97  		return ret, err
    98  	}
    99  	defer r.root.decref()
   100  
   101  	parts, err := splitPathInRoot(name, nil, nil)
   102  	if err != nil {
   103  		return ret, err
   104  	}
   105  
   106  	rootfd := r.root.fd
   107  	dirfd := rootfd
   108  	defer func() {
   109  		if dirfd != rootfd {
   110  			syscall.Close(dirfd)
   111  		}
   112  	}()
   113  
   114  	// When resolving .. path components, we restart path resolution from the root.
   115  	// (We can't openat(dir, "..") to move up to the parent directory,
   116  	// because dir may have moved since we opened it.)
   117  	// To limit how many opens a malicious path can cause us to perform, we set
   118  	// a limit on the total number of path steps and the total number of restarts
   119  	// caused by .. components. If *both* limits are exceeded, we halt the operation.
   120  	const maxSteps = 255
   121  	const maxRestarts = 8
   122  
   123  	i := 0
   124  	steps := 0
   125  	restarts := 0
   126  	symlinks := 0
   127  	for {
   128  		steps++
   129  		if steps > maxSteps && restarts > maxRestarts {
   130  			return ret, syscall.ENAMETOOLONG
   131  		}
   132  
   133  		if parts[i] == ".." {
   134  			// Resolve one or more parent ("..") path components.
   135  			//
   136  			// Rewrite the original path,
   137  			// removing the elements eliminated by ".." components,
   138  			// and start over from the beginning.
   139  			restarts++
   140  			end := i + 1
   141  			for end < len(parts) && parts[end] == ".." {
   142  				end++
   143  			}
   144  			count := end - i
   145  			if count > i {
   146  				return ret, errPathEscapes
   147  			}
   148  			parts = slices.Delete(parts, i-count, end)
   149  			i = 0
   150  			if dirfd != rootfd {
   151  				syscall.Close(dirfd)
   152  			}
   153  			dirfd = rootfd
   154  			continue
   155  		}
   156  
   157  		if i == len(parts)-1 {
   158  			// This is the last path element.
   159  			// Call f to decide what to do with it.
   160  			// If f returns errSymlink, this element is a symlink
   161  			// which should be followed.
   162  			ret, err = f(dirfd, parts[i])
   163  			if _, ok := err.(errSymlink); !ok {
   164  				return ret, err
   165  			}
   166  		} else {
   167  			var fd sysfdType
   168  			fd, err = rootOpenDir(dirfd, parts[i])
   169  			if err == nil {
   170  				if dirfd != rootfd {
   171  					syscall.Close(dirfd)
   172  				}
   173  				dirfd = fd
   174  			} else if _, ok := err.(errSymlink); !ok {
   175  				return ret, err
   176  			}
   177  		}
   178  
   179  		if e, ok := err.(errSymlink); ok {
   180  			symlinks++
   181  			if symlinks > rootMaxSymlinks {
   182  				return ret, syscall.ELOOP
   183  			}
   184  			newparts, err := splitPathInRoot(string(e), parts[:i], parts[i+1:])
   185  			if err != nil {
   186  				return ret, err
   187  			}
   188  			if len(newparts) < i || !slices.Equal(parts[:i], newparts[:i]) {
   189  				// Some component in the path which we have already traversed
   190  				// has changed. We need to restart parsing from the root.
   191  				i = 0
   192  				if dirfd != rootfd {
   193  					syscall.Close(dirfd)
   194  				}
   195  				dirfd = rootfd
   196  			}
   197  			parts = newparts
   198  			continue
   199  		}
   200  
   201  		i++
   202  	}
   203  }
   204  
   205  // errSymlink reports that a file being operated on is actually a symlink,
   206  // and the target of that symlink.
   207  type errSymlink string
   208  
   209  func (errSymlink) Error() string { panic("errSymlink is not user-visible") }
   210  

View as plain text