github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sentry/vfs/resolving_path.go (about)

     1  // Copyright 2019 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package vfs
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"github.com/nicocha30/gvisor-ligolo/pkg/abi/linux"
    21  	"github.com/nicocha30/gvisor-ligolo/pkg/context"
    22  	"github.com/nicocha30/gvisor-ligolo/pkg/errors/linuxerr"
    23  	"github.com/nicocha30/gvisor-ligolo/pkg/fspath"
    24  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/kernel/auth"
    25  	"github.com/nicocha30/gvisor-ligolo/pkg/sync"
    26  )
    27  
    28  // ResolvingPath represents the state of an in-progress path resolution, shared
    29  // between VFS and FilesystemImpl methods that take a path.
    30  //
    31  // From the perspective of FilesystemImpl methods, a ResolvingPath represents a
    32  // starting Dentry on the associated Filesystem (on which a reference is
    33  // already held), a stream of path components relative to that Dentry, and
    34  // elements of the invoking Context that are commonly required by
    35  // FilesystemImpl methods.
    36  //
    37  // ResolvingPath is loosely analogous to Linux's struct nameidata.
    38  //
    39  // +stateify savable
    40  type ResolvingPath struct {
    41  	vfs   *VirtualFilesystem
    42  	root  VirtualDentry // refs borrowed from PathOperation
    43  	mount *Mount
    44  	start *Dentry
    45  	pit   fspath.Iterator
    46  
    47  	flags     uint16
    48  	mustBeDir bool  // final file must be a directory?
    49  	symlinks  uint8 // number of symlinks traversed
    50  	curPart   uint8 // index into parts
    51  
    52  	creds *auth.Credentials
    53  
    54  	// Data associated with resolve*Errors, stored in ResolvingPath so that
    55  	// those errors don't need to allocate.
    56  	nextMount        *Mount  // ref held if not nil
    57  	nextStart        *Dentry // ref held if not nil
    58  	absSymlinkTarget fspath.Path
    59  
    60  	// ResolvingPath tracks relative paths, which is updated whenever a relative
    61  	// symlink is encountered.
    62  	parts [1 + linux.MaxSymlinkTraversals]fspath.Iterator
    63  }
    64  
    65  const (
    66  	rpflagsHaveMountRef       = 1 << iota // do we hold a reference on mount?
    67  	rpflagsHaveStartRef                   // do we hold a reference on start?
    68  	rpflagsFollowFinalSymlink             // same as PathOperation.FollowFinalSymlink
    69  )
    70  
    71  func init() {
    72  	if maxParts := len(ResolvingPath{}.parts); maxParts > 255 {
    73  		panic(fmt.Sprintf("uint8 is insufficient to accommodate len(ResolvingPath.parts) (%d)", maxParts))
    74  	}
    75  }
    76  
    77  // Error types that communicate state from the FilesystemImpl-caller,
    78  // VFS-callee side of path resolution (i.e. errors returned by
    79  // ResolvingPath.Resolve*()) to the VFS-caller, FilesystemImpl-callee side
    80  // (i.e. VFS methods => ResolvingPath.handleError()). These are empty structs
    81  // rather than error values because Go doesn't support non-primitive constants,
    82  // so error "constants" are really mutable vars, necessitating somewhat
    83  // expensive interface object comparisons.
    84  
    85  // +stateify savable
    86  type resolveMountRootOrJumpError struct{}
    87  
    88  // Error implements error.Error.
    89  func (resolveMountRootOrJumpError) Error() string {
    90  	return "resolving mount root or jump"
    91  }
    92  
    93  // +stateify savable
    94  type resolveMountPointError struct{}
    95  
    96  // Error implements error.Error.
    97  func (resolveMountPointError) Error() string {
    98  	return "resolving mount point"
    99  }
   100  
   101  // +stateify savable
   102  type resolveAbsSymlinkError struct{}
   103  
   104  // Error implements error.Error.
   105  func (resolveAbsSymlinkError) Error() string {
   106  	return "resolving absolute symlink"
   107  }
   108  
   109  var resolvingPathPool = sync.Pool{
   110  	New: func() any {
   111  		return &ResolvingPath{}
   112  	},
   113  }
   114  
   115  // getResolvingPath gets a new ResolvingPath from the pool. Caller must call
   116  // ResolvingPath.Release() when done.
   117  func (vfs *VirtualFilesystem) getResolvingPath(creds *auth.Credentials, pop *PathOperation) *ResolvingPath {
   118  	rp := resolvingPathPool.Get().(*ResolvingPath)
   119  	rp.vfs = vfs
   120  	rp.root = pop.Root
   121  	rp.mount = pop.Start.mount
   122  	rp.start = pop.Start.dentry
   123  	rp.pit = pop.Path.Begin
   124  	rp.flags = 0
   125  	if pop.FollowFinalSymlink {
   126  		rp.flags |= rpflagsFollowFinalSymlink
   127  	}
   128  	rp.mustBeDir = pop.Path.Dir
   129  	rp.symlinks = 0
   130  	rp.curPart = 0
   131  	rp.creds = creds
   132  	rp.parts[0] = pop.Path.Begin
   133  	return rp
   134  }
   135  
   136  // Copy creates another ResolvingPath with the same state as the original.
   137  // Copies are independent, using the copy does not change the original and
   138  // vice-versa.
   139  //
   140  // Caller must call Resease() when done.
   141  func (rp *ResolvingPath) Copy() *ResolvingPath {
   142  	copy := resolvingPathPool.Get().(*ResolvingPath)
   143  	*copy = *rp // All fields all shallow copiable.
   144  
   145  	// Take extra reference for the copy if the original had them.
   146  	if copy.flags&rpflagsHaveStartRef != 0 {
   147  		copy.start.IncRef()
   148  	}
   149  	if copy.flags&rpflagsHaveMountRef != 0 {
   150  		copy.mount.IncRef()
   151  	}
   152  	// Reset error state.
   153  	copy.nextStart = nil
   154  	copy.nextMount = nil
   155  	return copy
   156  }
   157  
   158  // Release decrements references if needed and returns the object to the pool.
   159  func (rp *ResolvingPath) Release(ctx context.Context) {
   160  	rp.root = VirtualDentry{}
   161  	rp.decRefStartAndMount(ctx)
   162  	rp.mount = nil
   163  	rp.start = nil
   164  	rp.releaseErrorState(ctx)
   165  	resolvingPathPool.Put(rp)
   166  }
   167  
   168  func (rp *ResolvingPath) decRefStartAndMount(ctx context.Context) {
   169  	if rp.flags&rpflagsHaveStartRef != 0 {
   170  		rp.start.DecRef(ctx)
   171  	}
   172  	if rp.flags&rpflagsHaveMountRef != 0 {
   173  		rp.mount.DecRef(ctx)
   174  	}
   175  }
   176  
   177  func (rp *ResolvingPath) releaseErrorState(ctx context.Context) {
   178  	if rp.nextStart != nil {
   179  		rp.nextStart.DecRef(ctx)
   180  		rp.nextStart = nil
   181  	}
   182  	if rp.nextMount != nil {
   183  		rp.nextMount.DecRef(ctx)
   184  		rp.nextMount = nil
   185  	}
   186  }
   187  
   188  // VirtualFilesystem returns the containing VirtualFilesystem.
   189  func (rp *ResolvingPath) VirtualFilesystem() *VirtualFilesystem {
   190  	return rp.vfs
   191  }
   192  
   193  // Credentials returns the credentials of rp's provider.
   194  func (rp *ResolvingPath) Credentials() *auth.Credentials {
   195  	return rp.creds
   196  }
   197  
   198  // Mount returns the Mount on which path resolution is currently occurring. It
   199  // does not take a reference on the returned Mount.
   200  func (rp *ResolvingPath) Mount() *Mount {
   201  	return rp.mount
   202  }
   203  
   204  // Start returns the starting Dentry represented by rp. It does not take a
   205  // reference on the returned Dentry.
   206  func (rp *ResolvingPath) Start() *Dentry {
   207  	return rp.start
   208  }
   209  
   210  // Done returns true if there are no remaining path components in the stream
   211  // represented by rp.
   212  func (rp *ResolvingPath) Done() bool {
   213  	// We don't need to check for rp.curPart == 0 because rp.Advance() won't
   214  	// set rp.pit to a terminal iterator otherwise.
   215  	return !rp.pit.Ok()
   216  }
   217  
   218  // Final returns true if there is exactly one remaining path component in the
   219  // stream represented by rp.
   220  //
   221  // Preconditions: !rp.Done().
   222  func (rp *ResolvingPath) Final() bool {
   223  	return rp.curPart == 0 && !rp.pit.NextOk()
   224  }
   225  
   226  // Component returns the current path component in the stream represented by
   227  // rp.
   228  //
   229  // Preconditions: !rp.Done().
   230  func (rp *ResolvingPath) Component() string {
   231  	if checkInvariants {
   232  		if !rp.pit.Ok() {
   233  			panic("ResolvingPath.Component() called at end of relative path")
   234  		}
   235  	}
   236  	return rp.pit.String()
   237  }
   238  
   239  // Advance advances the stream of path components represented by rp.
   240  //
   241  // Preconditions: !rp.Done().
   242  func (rp *ResolvingPath) Advance() {
   243  	if checkInvariants {
   244  		if !rp.pit.Ok() {
   245  			panic("ResolvingPath.Advance() called at end of relative path")
   246  		}
   247  	}
   248  	next := rp.pit.Next()
   249  	if next.Ok() || rp.curPart == 0 { // have next component, or at end of path
   250  		rp.pit = next
   251  	} else { // at end of path segment, continue with next one
   252  		rp.curPart--
   253  		rp.pit = rp.parts[rp.curPart]
   254  	}
   255  }
   256  
   257  // GetComponents emits all the remaining path components in rp. It does *not*
   258  // update rp state. It halts if emit() returns false. If excludeLast is true,
   259  // then the last path component is not emitted.
   260  func (rp *ResolvingPath) GetComponents(excludeLast bool, emit func(string) bool) {
   261  	// Copy rp state.
   262  	cur := rp.pit
   263  	curPart := rp.curPart
   264  	for cur.Ok() {
   265  		if excludeLast && curPart == 0 && !cur.NextOk() {
   266  			break
   267  		}
   268  		if !emit(cur.String()) {
   269  			break
   270  		}
   271  		cur = cur.Next()
   272  		if !cur.Ok() && curPart > 0 {
   273  			curPart--
   274  			cur = rp.parts[curPart]
   275  		}
   276  	}
   277  }
   278  
   279  // CheckRoot is called before resolving the parent of the Dentry d. If the
   280  // Dentry is contextually a VFS root, such that path resolution should treat
   281  // d's parent as itself, CheckRoot returns (true, nil). If the Dentry is the
   282  // root of a non-root mount, such that path resolution should switch to another
   283  // Mount, CheckRoot returns (unspecified, non-nil error). Otherwise, path
   284  // resolution should resolve d's parent normally, and CheckRoot returns (false,
   285  // nil).
   286  func (rp *ResolvingPath) CheckRoot(ctx context.Context, d *Dentry) (bool, error) {
   287  	if d == rp.root.dentry && rp.mount == rp.root.mount {
   288  		// At contextual VFS root (due to e.g. chroot(2)).
   289  		return true, nil
   290  	} else if d == rp.mount.root {
   291  		// At mount root ...
   292  		vd := rp.vfs.getMountpointAt(ctx, rp.mount, rp.root)
   293  		if vd.Ok() {
   294  			// ... of non-root mount.
   295  			rp.nextMount = vd.mount
   296  			rp.nextStart = vd.dentry
   297  			return false, resolveMountRootOrJumpError{}
   298  		}
   299  		// ... of root mount.
   300  		return true, nil
   301  	}
   302  	return false, nil
   303  }
   304  
   305  // CheckMount is called after resolving the parent or child of another Dentry
   306  // to d. If d is a mount point, such that path resolution should switch to
   307  // another Mount, CheckMount returns a non-nil error. Otherwise, CheckMount
   308  // returns nil.
   309  func (rp *ResolvingPath) CheckMount(ctx context.Context, d *Dentry) error {
   310  	if !d.isMounted() {
   311  		return nil
   312  	}
   313  	if mnt := rp.vfs.getMountAt(ctx, rp.mount, d); mnt != nil {
   314  		rp.nextMount = mnt
   315  		return resolveMountPointError{}
   316  	}
   317  	return nil
   318  }
   319  
   320  // ShouldFollowSymlink returns true if, supposing that the current path
   321  // component in pcs represents a symbolic link, the symbolic link should be
   322  // followed.
   323  //
   324  // If path is terminated with '/', the '/' is considered the last element and
   325  // any symlink before that is followed:
   326  //
   327  //   - For most non-creating walks, the last path component is handled by
   328  //     fs/namei.c:lookup_last(), which sets LOOKUP_FOLLOW if the first byte
   329  //     after the path component is non-NULL (which is only possible if it's '/')
   330  //     and the path component is of type LAST_NORM.
   331  //
   332  //   - For open/openat/openat2 without O_CREAT, the last path component is
   333  //     handled by fs/namei.c:do_last(), which does the same, though without the
   334  //     LAST_NORM check.
   335  //
   336  // Preconditions: !rp.Done().
   337  func (rp *ResolvingPath) ShouldFollowSymlink() bool {
   338  	// Non-final symlinks are always followed. Paths terminated with '/' are also
   339  	// always followed.
   340  	return rp.flags&rpflagsFollowFinalSymlink != 0 || !rp.Final() || rp.MustBeDir()
   341  }
   342  
   343  // HandleSymlink is called when the current path component is a symbolic link
   344  // to the given target. If the calling Filesystem method should continue path
   345  // traversal, HandleSymlink updates the path component stream to reflect the
   346  // symlink target and returns nil. Otherwise it returns a non-nil error. It
   347  // also returns whether the symlink was successfully followed, which can be
   348  // true even when a non-nil error like resolveAbsSymlinkError is returned.
   349  //
   350  // Preconditions: !rp.Done().
   351  //
   352  // Postconditions: If HandleSymlink returns a nil error, then !rp.Done().
   353  func (rp *ResolvingPath) HandleSymlink(target string) (bool, error) {
   354  	if rp.symlinks >= linux.MaxSymlinkTraversals {
   355  		return false, linuxerr.ELOOP
   356  	}
   357  	if len(target) == 0 {
   358  		return false, linuxerr.ENOENT
   359  	}
   360  	rp.symlinks++
   361  	targetPath := fspath.Parse(target)
   362  	if targetPath.Absolute {
   363  		rp.absSymlinkTarget = targetPath
   364  		return true, resolveAbsSymlinkError{}
   365  	}
   366  	// Consume the path component that represented the symlink.
   367  	rp.Advance()
   368  	// Prepend the symlink target to the relative path.
   369  	if checkInvariants {
   370  		if !targetPath.HasComponents() {
   371  			panic(fmt.Sprintf("non-empty pathname %q parsed to relative path with no components", target))
   372  		}
   373  	}
   374  	rp.relpathPrepend(targetPath)
   375  	return true, nil
   376  }
   377  
   378  // Preconditions: path.HasComponents().
   379  func (rp *ResolvingPath) relpathPrepend(path fspath.Path) {
   380  	if rp.pit.Ok() {
   381  		rp.parts[rp.curPart] = rp.pit
   382  		rp.pit = path.Begin
   383  		rp.curPart++
   384  	} else {
   385  		// The symlink was the final path component, so now the symlink target
   386  		// is the whole path.
   387  		rp.pit = path.Begin
   388  		// Symlink targets can set rp.mustBeDir (if they end in a trailing /),
   389  		// but can't unset it.
   390  		if path.Dir {
   391  			rp.mustBeDir = true
   392  		}
   393  	}
   394  }
   395  
   396  // HandleJump is called when the current path component is a "magic" link to
   397  // the given VirtualDentry, like /proc/[pid]/fd/[fd]. If the calling Filesystem
   398  // method should continue path traversal, HandleJump updates the path
   399  // component stream to reflect the magic link target and returns nil. Otherwise
   400  // it returns a non-nil error. It also returns whether the magic link was
   401  // followed, which can be true even when a non-nil error like
   402  // resolveMountRootOrJumpError is returned.
   403  //
   404  // Preconditions: !rp.Done().
   405  func (rp *ResolvingPath) HandleJump(target VirtualDentry) (bool, error) {
   406  	if rp.symlinks >= linux.MaxSymlinkTraversals {
   407  		return false, linuxerr.ELOOP
   408  	}
   409  	rp.symlinks++
   410  	// Consume the path component that represented the magic link.
   411  	rp.Advance()
   412  	// Unconditionally return a resolveMountRootOrJumpError, even if the Mount
   413  	// isn't changing, to force restarting at the new Dentry.
   414  	target.IncRef()
   415  	rp.nextMount = target.mount
   416  	rp.nextStart = target.dentry
   417  	return true, resolveMountRootOrJumpError{}
   418  }
   419  
   420  func (rp *ResolvingPath) handleError(ctx context.Context, err error) bool {
   421  	switch err.(type) {
   422  	case resolveMountRootOrJumpError:
   423  		// Switch to the new Mount. We hold references on the Mount and Dentry.
   424  		rp.decRefStartAndMount(ctx)
   425  		rp.mount = rp.nextMount
   426  		rp.start = rp.nextStart
   427  		rp.flags |= rpflagsHaveMountRef | rpflagsHaveStartRef
   428  		rp.nextMount = nil
   429  		rp.nextStart = nil
   430  		// Don't consume the path component that caused us to traverse
   431  		// through the mount root - i.e. the ".." - because we still need to
   432  		// resolve the mount point's parent in the new FilesystemImpl.
   433  		//
   434  		// Restart path resolution on the new Mount. Don't bother calling
   435  		// rp.releaseErrorState() since we already set nextMount and nextStart
   436  		// to nil above.
   437  		return true
   438  
   439  	case resolveMountPointError:
   440  		// Switch to the new Mount. We hold a reference on the Mount, but
   441  		// borrow the reference on the mount root from the Mount.
   442  		rp.decRefStartAndMount(ctx)
   443  		rp.mount = rp.nextMount
   444  		rp.start = rp.nextMount.root
   445  		rp.flags = rp.flags&^rpflagsHaveStartRef | rpflagsHaveMountRef
   446  		rp.nextMount = nil
   447  		// Consume the path component that represented the mount point.
   448  		rp.Advance()
   449  		// Restart path resolution on the new Mount.
   450  		rp.releaseErrorState(ctx)
   451  		return true
   452  
   453  	case resolveAbsSymlinkError:
   454  		// Switch to the new Mount. References are borrowed from rp.root.
   455  		rp.decRefStartAndMount(ctx)
   456  		rp.mount = rp.root.mount
   457  		rp.start = rp.root.dentry
   458  		rp.flags &^= rpflagsHaveMountRef | rpflagsHaveStartRef
   459  		// Consume the path component that represented the symlink.
   460  		rp.Advance()
   461  		if rp.absSymlinkTarget.HasComponents() {
   462  			// Prepend the symlink target to the relative path.
   463  			rp.relpathPrepend(rp.absSymlinkTarget)
   464  		}
   465  		// Restart path resolution on the new Mount.
   466  		rp.releaseErrorState(ctx)
   467  		return true
   468  
   469  	default:
   470  		// Not an error we can handle.
   471  		return false
   472  	}
   473  }
   474  
   475  // canHandleError returns true if err is an error returned by rp.Resolve*()
   476  // that rp.handleError() may attempt to handle.
   477  func (rp *ResolvingPath) canHandleError(err error) bool {
   478  	switch err.(type) {
   479  	case resolveMountRootOrJumpError, resolveMountPointError, resolveAbsSymlinkError:
   480  		return true
   481  	default:
   482  		return false
   483  	}
   484  }
   485  
   486  // MustBeDir returns true if the file traversed by rp must be a directory.
   487  func (rp *ResolvingPath) MustBeDir() bool {
   488  	return rp.mustBeDir
   489  }