gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/fsimpl/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 "gvisor.dev/gvisor/pkg/abi/linux" 27 "gvisor.dev/gvisor/pkg/context" 28 "gvisor.dev/gvisor/pkg/fspath" 29 "gvisor.dev/gvisor/pkg/sentry/kernel/auth" 30 "gvisor.dev/gvisor/pkg/sentry/vfs" 31 "gvisor.dev/gvisor/pkg/usermem" 32 ) 33 34 type fileReader struct { 35 ctx context.Context 36 fd *vfs.FileDescription 37 } 38 39 func (r *fileReader) Read(buf []byte) (int, error) { 40 n, err := r.fd.Read(r.ctx, usermem.BytesIOSequence(buf), vfs.ReadOptions{}) 41 return int(n), err 42 } 43 44 func getExecUserHome(ctx context.Context, mns *vfs.MountNamespace, uid auth.KUID) (string, error) { 45 const defaultHome = "/" 46 47 root := mns.Root(ctx) 48 defer root.DecRef(ctx) 49 50 creds := auth.CredentialsFromContext(ctx) 51 52 target := &vfs.PathOperation{ 53 Root: root, 54 Start: root, 55 Path: fspath.Parse("/etc/passwd"), 56 } 57 58 stat, err := root.Mount().Filesystem().VirtualFilesystem().StatAt(ctx, creds, target, &vfs.StatOptions{Mask: linux.STATX_TYPE}) 59 if err != nil { 60 return defaultHome, nil 61 } 62 if stat.Mask&linux.STATX_TYPE == 0 || stat.Mode&linux.FileTypeMask != linux.ModeRegular { 63 return defaultHome, nil 64 } 65 66 opts := &vfs.OpenOptions{ 67 Flags: linux.O_RDONLY, 68 } 69 fd, err := root.Mount().Filesystem().VirtualFilesystem().OpenAt(ctx, creds, target, opts) 70 if err != nil { 71 return defaultHome, nil 72 } 73 defer fd.DecRef(ctx) 74 75 r := &fileReader{ 76 ctx: ctx, 77 fd: fd, 78 } 79 80 homeDir, err := findHomeInPasswd(uint32(uid), r, defaultHome) 81 if err != nil { 82 return "", err 83 } 84 85 return homeDir, nil 86 } 87 88 // MaybeAddExecUserHome returns a new slice with the HOME environment 89 // variable set if the slice does not already contain it, otherwise it returns 90 // the original slice unmodified. 91 func MaybeAddExecUserHome(ctx context.Context, vmns *vfs.MountNamespace, uid auth.KUID, envv []string) ([]string, error) { 92 // Check if the envv already contains HOME. 93 for _, env := range envv { 94 if strings.HasPrefix(env, "HOME=") { 95 // We have it. Return the original slice unmodified. 96 return envv, nil 97 } 98 } 99 100 // Read /etc/passwd for the user's HOME directory and set the HOME 101 // environment variable as required by POSIX if it is not overridden by 102 // the user. 103 homeDir, err := getExecUserHome(ctx, vmns, uid) 104 if err != nil { 105 return nil, fmt.Errorf("error reading exec user: %v", err) 106 } 107 return append(envv, "HOME="+homeDir), nil 108 } 109 110 // findHomeInPasswd parses a passwd file and returns the given user's home 111 // directory. This function does it's best to replicate the runc's behavior. 112 func findHomeInPasswd(uid uint32, passwd io.Reader, defaultHome string) (string, error) { 113 s := bufio.NewScanner(passwd) 114 115 for s.Scan() { 116 if err := s.Err(); err != nil { 117 return "", err 118 } 119 120 line := strings.TrimSpace(s.Text()) 121 if line == "" { 122 continue 123 } 124 125 // Pull out part of passwd entry. Loosely parse the passwd entry as some 126 // passwd files could be poorly written and for compatibility with runc. 127 // 128 // Per 'man 5 passwd' 129 // /etc/passwd contains one line for each user account, with seven 130 // fields delimited by colons (“:”). These fields are: 131 // 132 // - login name 133 // - optional encrypted password 134 // - numerical user ID 135 // - numerical group ID 136 // - user name or comment field 137 // - user home directory 138 // - optional user command interpreter 139 parts := strings.Split(line, ":") 140 141 found := false 142 homeDir := "" 143 for i, p := range parts { 144 switch i { 145 case 2: 146 parsedUID, err := strconv.ParseUint(p, 10, 32) 147 if err == nil && parsedUID == uint64(uid) { 148 found = true 149 } 150 case 5: 151 homeDir = p 152 } 153 } 154 if found { 155 // NOTE: If the uid is present but the home directory is not 156 // present in the /etc/passwd entry we return an empty string. This 157 // is, for better or worse, what runc does. 158 return homeDir, nil 159 } 160 } 161 162 return defaultHome, nil 163 } 164 165 func findUIDGIDInPasswd(passwd io.Reader, user string) (auth.KUID, auth.KGID, error) { 166 defaultUID := auth.KUID(auth.OverflowUID) 167 defaultGID := auth.KGID(auth.OverflowGID) 168 uid := defaultUID 169 gid := defaultGID 170 171 // Per 'man 5 passwd' 172 // /etc/passwd contains one line for each user account, with seven 173 // fields delimited by colons (“:”). These fields are: 174 // 175 // - login name 176 // - optional encrypted password 177 // - numerical user ID 178 // - numerical group ID 179 // - user name or comment field 180 // - user home directory 181 // - optional user command interpreter 182 const ( 183 numFields = 7 184 userIdx = 0 185 passwdIdx = 1 186 uidIdx = 2 187 gidIdx = 3 188 shellIdx = 6 189 ) 190 usergroup := strings.SplitN(user, ":", 2) 191 uStringOrID := usergroup[0] 192 193 // Check if we have a uid or string for user. 194 idxToMatch := uidIdx 195 _, err := strconv.Atoi(uStringOrID) 196 if err != nil { 197 idxToMatch = userIdx 198 } 199 200 s := bufio.NewScanner(passwd) 201 for s.Scan() { 202 if err := s.Err(); err != nil { 203 return defaultUID, defaultGID, err 204 } 205 206 line := strings.TrimSpace(s.Text()) 207 if line == "" { 208 continue 209 } 210 211 parts := strings.Split(line, ":") 212 if len(parts) != numFields { 213 // Return error if the format is invalid. 214 return defaultUID, defaultGID, fmt.Errorf("invalid line found in /etc/passwd") 215 } 216 for i := 0; i < numFields; i++ { 217 // The password and user command interpreter fields are 218 // optional, no need to check if they are empty. 219 if i == passwdIdx || i == shellIdx { 220 continue 221 } 222 if parts[i] == "" { 223 // Return error if the format is invalid. 224 return defaultUID, defaultGID, fmt.Errorf("invalid line found in /etc/passwd") 225 } 226 } 227 228 if parts[idxToMatch] == uStringOrID { 229 parseUID, err := strconv.ParseUint(parts[uidIdx], 10, 32) 230 if err != nil { 231 return defaultUID, defaultGID, err 232 } 233 parseGID, err := strconv.ParseUint(parts[gidIdx], 10, 32) 234 if err != nil { 235 return defaultUID, defaultGID, err 236 } 237 238 if uid != defaultUID || gid != defaultGID { 239 return defaultUID, defaultGID, fmt.Errorf("multiple matches for the user: %v", user) 240 } 241 uid = auth.KUID(parseUID) 242 gid = auth.KGID(parseGID) 243 } 244 } 245 if uid == defaultUID || gid == defaultGID { 246 return defaultUID, defaultGID, fmt.Errorf("couldn't retrieve UID/GID from user: %v", user) 247 } 248 return uid, gid, nil 249 } 250 251 func getExecUIDGID(ctx context.Context, mns *vfs.MountNamespace, user string) (auth.KUID, auth.KGID, error) { 252 root := mns.Root(ctx) 253 defer root.DecRef(ctx) 254 255 creds := auth.CredentialsFromContext(ctx) 256 257 target := &vfs.PathOperation{ 258 Root: root, 259 Start: root, 260 Path: fspath.Parse("/etc/passwd"), 261 } 262 263 fd, err := root.Mount().Filesystem().VirtualFilesystem().OpenAt(ctx, creds, target, &vfs.OpenOptions{Flags: linux.O_RDONLY}) 264 if err != nil { 265 return auth.KUID(auth.OverflowUID), auth.KGID(auth.OverflowGID), fmt.Errorf("couldn't retrieve UID/GID from user: %v, err: %v", user, err) 266 } 267 defer fd.DecRef(ctx) 268 269 r := &fileReader{ 270 ctx: ctx, 271 fd: fd, 272 } 273 274 return findUIDGIDInPasswd(r, user) 275 } 276 277 // GetExecUIDGIDFromUser retrieves the UID and GID from /etc/passwd file for 278 // the given user. 279 func GetExecUIDGIDFromUser(ctx context.Context, vmns *vfs.MountNamespace, user string) (auth.KUID, auth.KGID, error) { 280 // Read /etc/passwd and retrieve the UID/GID based on the user string. 281 uid, gid, err := getExecUIDGID(ctx, vmns, user) 282 if err != nil { 283 return uid, gid, fmt.Errorf("error reading /etc/passwd: %v", err) 284 } 285 return uid, gid, nil 286 }