github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/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/SagerNet/gvisor/pkg/abi/linux" 23 "github.com/SagerNet/gvisor/pkg/context" 24 "github.com/SagerNet/gvisor/pkg/errors/linuxerr" 25 "github.com/SagerNet/gvisor/pkg/fspath" 26 "github.com/SagerNet/gvisor/pkg/log" 27 "github.com/SagerNet/gvisor/pkg/sentry/fs" 28 "github.com/SagerNet/gvisor/pkg/sentry/kernel" 29 "github.com/SagerNet/gvisor/pkg/sentry/kernel/auth" 30 "github.com/SagerNet/gvisor/pkg/sentry/vfs" 31 "github.com/SagerNet/gvisor/pkg/syserror" 32 ) 33 34 // ResolveExecutablePath resolves the given executable name given the working 35 // dir and environment. 36 func ResolveExecutablePath(ctx context.Context, args *kernel.CreateProcessArgs) (string, error) { 37 name := args.Filename 38 if len(name) == 0 { 39 if len(args.Argv) == 0 { 40 return "", fmt.Errorf("no filename or command provided") 41 } 42 name = args.Argv[0] 43 } 44 45 // Absolute paths can be used directly. 46 if path.IsAbs(name) { 47 return name, nil 48 } 49 50 // Paths with '/' in them should be joined to the working directory, or 51 // to the root if working directory is not set. 52 if strings.IndexByte(name, '/') > 0 { 53 wd := args.WorkingDirectory 54 if wd == "" { 55 wd = "/" 56 } 57 if !path.IsAbs(wd) { 58 return "", fmt.Errorf("working directory %q must be absolute", wd) 59 } 60 return path.Join(wd, name), nil 61 } 62 63 // Otherwise, We must lookup the name in the paths. 64 paths := getPath(args.Envv) 65 if kernel.VFS2Enabled { 66 f, err := resolveVFS2(ctx, args.Credentials, args.MountNamespaceVFS2, paths, name) 67 if err != nil { 68 return "", fmt.Errorf("error finding executable %q in PATH %v: %v", name, paths, err) 69 } 70 return f, nil 71 } 72 73 f, err := resolve(ctx, args.MountNamespace, paths, name) 74 if err != nil { 75 return "", fmt.Errorf("error finding executable %q in PATH %v: %v", name, paths, err) 76 } 77 return f, nil 78 } 79 80 func resolve(ctx context.Context, mns *fs.MountNamespace, paths []string, name string) (string, error) { 81 root := fs.RootFromContext(ctx) 82 if root == nil { 83 // Caller has no root. Don't bother traversing anything. 84 return "", syserror.ENOENT 85 } 86 defer root.DecRef(ctx) 87 for _, p := range paths { 88 if !path.IsAbs(p) { 89 // Relative paths aren't safe, no one should be using them. 90 log.Warningf("Skipping relative path %q in $PATH", p) 91 continue 92 } 93 94 binPath := path.Join(p, name) 95 traversals := uint(linux.MaxSymlinkTraversals) 96 d, err := mns.FindInode(ctx, root, nil, binPath, &traversals) 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 defer d.DecRef(ctx) 105 106 // Check that it is a regular file. 107 if !fs.IsRegular(d.Inode.StableAttr) { 108 continue 109 } 110 111 // Check whether we can read and execute the found file. 112 if err := d.Inode.CheckPermission(ctx, fs.PermMask{Read: true, Execute: true}); err != nil { 113 log.Infof("Found executable at %q, but user cannot execute it: %v", binPath, err) 114 continue 115 } 116 return path.Join("/", p, name), nil 117 } 118 119 // Couldn't find it. 120 return "", syserror.ENOENT 121 } 122 123 func resolveVFS2(ctx context.Context, creds *auth.Credentials, mns *vfs.MountNamespace, paths []string, name string) (string, error) { 124 root := mns.Root() 125 root.IncRef() 126 defer root.DecRef(ctx) 127 for _, p := range paths { 128 if !path.IsAbs(p) { 129 // Relative paths aren't safe, no one should be using them. 130 log.Warningf("Skipping relative path %q in $PATH", p) 131 continue 132 } 133 134 binPath := path.Join(p, name) 135 pop := &vfs.PathOperation{ 136 Root: root, 137 Start: root, 138 Path: fspath.Parse(binPath), 139 FollowFinalSymlink: true, 140 } 141 opts := &vfs.OpenOptions{ 142 FileExec: true, 143 Flags: linux.O_RDONLY, 144 } 145 dentry, err := root.Mount().Filesystem().VirtualFilesystem().OpenAt(ctx, creds, pop, opts) 146 if linuxerr.Equals(linuxerr.ENOENT, err) || linuxerr.Equals(linuxerr.EACCES, err) { 147 // Didn't find it here. 148 continue 149 } 150 if err != nil { 151 return "", err 152 } 153 dentry.DecRef(ctx) 154 155 return binPath, nil 156 } 157 158 // Couldn't find it. 159 return "", syserror.ENOENT 160 } 161 162 // getPath returns the PATH as a slice of strings given the environment 163 // variables. 164 func getPath(env []string) []string { 165 const prefix = "PATH=" 166 for _, e := range env { 167 if strings.HasPrefix(e, prefix) { 168 return strings.Split(strings.TrimPrefix(e, prefix), ":") 169 } 170 } 171 return nil 172 }