github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/user/user.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 user contains methods for resolving filesystem paths based on the 16 // user and their environment. 17 package user 18 19 import ( 20 "bufio" 21 "fmt" 22 "io" 23 "strconv" 24 "strings" 25 26 "github.com/SagerNet/gvisor/pkg/abi/linux" 27 "github.com/SagerNet/gvisor/pkg/context" 28 "github.com/SagerNet/gvisor/pkg/fspath" 29 "github.com/SagerNet/gvisor/pkg/sentry/fs" 30 "github.com/SagerNet/gvisor/pkg/sentry/kernel/auth" 31 "github.com/SagerNet/gvisor/pkg/sentry/vfs" 32 "github.com/SagerNet/gvisor/pkg/usermem" 33 ) 34 35 type fileReader struct { 36 // Ctx is the context for the file reader. 37 Ctx context.Context 38 39 // File is the file to read from. 40 File *fs.File 41 } 42 43 // Read implements io.Reader.Read. 44 func (r *fileReader) Read(buf []byte) (int, error) { 45 n, err := r.File.Readv(r.Ctx, usermem.BytesIOSequence(buf)) 46 return int(n), err 47 } 48 49 // getExecUserHome returns the home directory of the executing user read from 50 // /etc/passwd as read from the container filesystem. 51 func getExecUserHome(ctx context.Context, rootMns *fs.MountNamespace, uid auth.KUID) (string, error) { 52 // The default user home directory to return if no user matching the user 53 // if found in the /etc/passwd found in the image. 54 const defaultHome = "/" 55 56 // Open the /etc/passwd file from the dirent via the root mount namespace. 57 mnsRoot := rootMns.Root() 58 maxTraversals := uint(linux.MaxSymlinkTraversals) 59 dirent, err := rootMns.FindInode(ctx, mnsRoot, nil, "/etc/passwd", &maxTraversals) 60 if err != nil { 61 // NOTE: Ignore errors opening the passwd file. If the passwd file 62 // doesn't exist we will return the default home directory. 63 return defaultHome, nil 64 } 65 defer dirent.DecRef(ctx) 66 67 // Check read permissions on the file. 68 if err := dirent.Inode.CheckPermission(ctx, fs.PermMask{Read: true}); err != nil { 69 // NOTE: Ignore permissions errors here and return default root dir. 70 return defaultHome, nil 71 } 72 73 // Only open regular files. We don't open other files like named pipes as 74 // they may block and might present some attack surface to the container. 75 // Note that runc does not seem to do this kind of checking. 76 if !fs.IsRegular(dirent.Inode.StableAttr) { 77 return defaultHome, nil 78 } 79 80 f, err := dirent.Inode.GetFile(ctx, dirent, fs.FileFlags{Read: true, Directory: false}) 81 if err != nil { 82 return "", err 83 } 84 defer f.DecRef(ctx) 85 86 r := &fileReader{ 87 Ctx: ctx, 88 File: f, 89 } 90 91 return findHomeInPasswd(uint32(uid), r, defaultHome) 92 } 93 94 type fileReaderVFS2 struct { 95 ctx context.Context 96 fd *vfs.FileDescription 97 } 98 99 func (r *fileReaderVFS2) Read(buf []byte) (int, error) { 100 n, err := r.fd.Read(r.ctx, usermem.BytesIOSequence(buf), vfs.ReadOptions{}) 101 return int(n), err 102 } 103 104 func getExecUserHomeVFS2(ctx context.Context, mns *vfs.MountNamespace, uid auth.KUID) (string, error) { 105 const defaultHome = "/" 106 107 root := mns.Root() 108 root.IncRef() 109 defer root.DecRef(ctx) 110 111 creds := auth.CredentialsFromContext(ctx) 112 113 target := &vfs.PathOperation{ 114 Root: root, 115 Start: root, 116 Path: fspath.Parse("/etc/passwd"), 117 } 118 119 opts := &vfs.OpenOptions{ 120 Flags: linux.O_RDONLY, 121 } 122 123 fd, err := root.Mount().Filesystem().VirtualFilesystem().OpenAt(ctx, creds, target, opts) 124 if err != nil { 125 return defaultHome, nil 126 } 127 defer fd.DecRef(ctx) 128 129 r := &fileReaderVFS2{ 130 ctx: ctx, 131 fd: fd, 132 } 133 134 homeDir, err := findHomeInPasswd(uint32(uid), r, defaultHome) 135 if err != nil { 136 return "", err 137 } 138 139 return homeDir, nil 140 } 141 142 // MaybeAddExecUserHome returns a new slice with the HOME enviroment variable 143 // set if the slice does not already contain it, otherwise it returns the 144 // original slice unmodified. 145 func MaybeAddExecUserHome(ctx context.Context, mns *fs.MountNamespace, uid auth.KUID, envv []string) ([]string, error) { 146 // Check if the envv already contains HOME. 147 for _, env := range envv { 148 if strings.HasPrefix(env, "HOME=") { 149 // We have it. Return the original slice unmodified. 150 return envv, nil 151 } 152 } 153 154 // Read /etc/passwd for the user's HOME directory and set the HOME 155 // environment variable as required by POSIX if it is not overridden by 156 // the user. 157 homeDir, err := getExecUserHome(ctx, mns, uid) 158 if err != nil { 159 return nil, fmt.Errorf("error reading exec user: %v", err) 160 } 161 162 return append(envv, "HOME="+homeDir), nil 163 } 164 165 // MaybeAddExecUserHomeVFS2 returns a new slice with the HOME enviroment 166 // variable set if the slice does not already contain it, otherwise it returns 167 // the original slice unmodified. 168 func MaybeAddExecUserHomeVFS2(ctx context.Context, vmns *vfs.MountNamespace, uid auth.KUID, envv []string) ([]string, error) { 169 // Check if the envv already contains HOME. 170 for _, env := range envv { 171 if strings.HasPrefix(env, "HOME=") { 172 // We have it. Return the original slice unmodified. 173 return envv, nil 174 } 175 } 176 177 // Read /etc/passwd for the user's HOME directory and set the HOME 178 // environment variable as required by POSIX if it is not overridden by 179 // the user. 180 homeDir, err := getExecUserHomeVFS2(ctx, vmns, uid) 181 if err != nil { 182 return nil, fmt.Errorf("error reading exec user: %v", err) 183 } 184 return append(envv, "HOME="+homeDir), nil 185 } 186 187 // findHomeInPasswd parses a passwd file and returns the given user's home 188 // directory. This function does it's best to replicate the runc's behavior. 189 func findHomeInPasswd(uid uint32, passwd io.Reader, defaultHome string) (string, error) { 190 s := bufio.NewScanner(passwd) 191 192 for s.Scan() { 193 if err := s.Err(); err != nil { 194 return "", err 195 } 196 197 line := strings.TrimSpace(s.Text()) 198 if line == "" { 199 continue 200 } 201 202 // Pull out part of passwd entry. Loosely parse the passwd entry as some 203 // passwd files could be poorly written and for compatibility with runc. 204 // 205 // Per 'man 5 passwd' 206 // /etc/passwd contains one line for each user account, with seven 207 // fields delimited by colons (“:”). These fields are: 208 // 209 // - login name 210 // - optional encrypted password 211 // - numerical user ID 212 // - numerical group ID 213 // - user name or comment field 214 // - user home directory 215 // - optional user command interpreter 216 parts := strings.Split(line, ":") 217 218 found := false 219 homeDir := "" 220 for i, p := range parts { 221 switch i { 222 case 2: 223 parsedUID, err := strconv.ParseUint(p, 10, 32) 224 if err == nil && parsedUID == uint64(uid) { 225 found = true 226 } 227 case 5: 228 homeDir = p 229 } 230 } 231 if found { 232 // NOTE: If the uid is present but the home directory is not 233 // present in the /etc/passwd entry we return an empty string. This 234 // is, for better or worse, what runc does. 235 return homeDir, nil 236 } 237 } 238 239 return defaultHome, nil 240 }