github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/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 "github.com/metacubex/gvisor/pkg/abi/linux" 27 "github.com/metacubex/gvisor/pkg/context" 28 "github.com/metacubex/gvisor/pkg/fspath" 29 "github.com/metacubex/gvisor/pkg/sentry/kernel/auth" 30 "github.com/metacubex/gvisor/pkg/sentry/vfs" 31 "github.com/metacubex/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 }