github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/pkg/sentry/fsimpl/user/path.go (about) 1 // Copyright 2020 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 user 16 17 import ( 18 "fmt" 19 "path" 20 "strings" 21 22 "github.com/metacubex/gvisor/pkg/abi/linux" 23 "github.com/metacubex/gvisor/pkg/context" 24 "github.com/metacubex/gvisor/pkg/errors/linuxerr" 25 "github.com/metacubex/gvisor/pkg/fspath" 26 "github.com/metacubex/gvisor/pkg/log" 27 "github.com/metacubex/gvisor/pkg/sentry/kernel" 28 "github.com/metacubex/gvisor/pkg/sentry/kernel/auth" 29 "github.com/metacubex/gvisor/pkg/sentry/vfs" 30 ) 31 32 // ExecutableResolveError represents a failure to resolve the executable 33 // in ResolveExecutablePath. 34 type ExecutableResolveError struct{ error } 35 36 // ResolveExecutablePath resolves the given executable name given the working 37 // dir and environment. 38 // Returns *ExecutableResolveError when the executable cannot be resolved. 39 func ResolveExecutablePath(ctx context.Context, args *kernel.CreateProcessArgs) (string, error) { 40 name := args.Filename 41 if len(name) == 0 { 42 if len(args.Argv) == 0 { 43 return "", fmt.Errorf("no filename or command provided") 44 } 45 name = args.Argv[0] 46 } 47 48 // Absolute paths can be used directly. 49 if path.IsAbs(name) { 50 return name, nil 51 } 52 53 // Paths with '/' in them should be joined to the working directory, or 54 // to the root if working directory is not set. 55 if strings.IndexByte(name, '/') > 0 { 56 wd := args.WorkingDirectory 57 if wd == "" { 58 wd = "/" 59 } 60 if !path.IsAbs(wd) { 61 return "", fmt.Errorf("working directory %q must be absolute", wd) 62 } 63 return path.Join(wd, name), nil 64 } 65 66 // Otherwise, We must lookup the name in the paths. 67 paths := getPath(args.Envv) 68 f, err := resolve(ctx, args.Credentials, args.MountNamespace, paths, name) 69 if err != nil { 70 return "", &ExecutableResolveError{fmt.Errorf("error finding executable %q in PATH %v: %v", name, paths, err)} 71 } 72 return f, nil 73 } 74 75 func resolve(ctx context.Context, creds *auth.Credentials, mns *vfs.MountNamespace, paths []string, name string) (string, error) { 76 root := mns.Root(ctx) 77 defer root.DecRef(ctx) 78 for _, p := range paths { 79 if !path.IsAbs(p) { 80 // Relative paths aren't safe, no one should be using them. 81 log.Warningf("Skipping relative path %q in $PATH", p) 82 continue 83 } 84 85 binPath := path.Join(p, name) 86 pop := &vfs.PathOperation{ 87 Root: root, 88 Start: root, 89 Path: fspath.Parse(binPath), 90 FollowFinalSymlink: true, 91 } 92 opts := &vfs.OpenOptions{ 93 FileExec: true, 94 Flags: linux.O_RDONLY, 95 } 96 dentry, err := root.Mount().Filesystem().VirtualFilesystem().OpenAt(ctx, creds, pop, opts) 97 if linuxerr.Equals(linuxerr.ENOENT, err) || linuxerr.Equals(linuxerr.EACCES, err) { 98 // Didn't find it here. 99 continue 100 } 101 if err != nil { 102 return "", err 103 } 104 dentry.DecRef(ctx) 105 106 return binPath, nil 107 } 108 109 // Couldn't find it. 110 return "", linuxerr.ENOENT 111 } 112 113 // getPath returns the PATH as a slice of strings given the environment 114 // variables. 115 func getPath(env []string) []string { 116 const prefix = "PATH=" 117 for _, e := range env { 118 if strings.HasPrefix(e, prefix) { 119 return strings.Split(strings.TrimPrefix(e, prefix), ":") 120 } 121 } 122 return nil 123 }