github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/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/ttpreport/gvisor-ligolo/pkg/abi/linux" 27 "github.com/ttpreport/gvisor-ligolo/pkg/context" 28 "github.com/ttpreport/gvisor-ligolo/pkg/fspath" 29 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/kernel/auth" 30 "github.com/ttpreport/gvisor-ligolo/pkg/sentry/vfs" 31 "github.com/ttpreport/gvisor-ligolo/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() 48 root.IncRef() 49 defer root.DecRef(ctx) 50 51 creds := auth.CredentialsFromContext(ctx) 52 53 target := &vfs.PathOperation{ 54 Root: root, 55 Start: root, 56 Path: fspath.Parse("/etc/passwd"), 57 } 58 59 stat, err := root.Mount().Filesystem().VirtualFilesystem().StatAt(ctx, creds, target, &vfs.StatOptions{Mask: linux.STATX_TYPE}) 60 if err != nil { 61 return defaultHome, nil 62 } 63 if stat.Mask&linux.STATX_TYPE == 0 || stat.Mode&linux.FileTypeMask != linux.ModeRegular { 64 return defaultHome, nil 65 } 66 67 opts := &vfs.OpenOptions{ 68 Flags: linux.O_RDONLY, 69 } 70 fd, err := root.Mount().Filesystem().VirtualFilesystem().OpenAt(ctx, creds, target, opts) 71 if err != nil { 72 return defaultHome, nil 73 } 74 defer fd.DecRef(ctx) 75 76 r := &fileReader{ 77 ctx: ctx, 78 fd: fd, 79 } 80 81 homeDir, err := findHomeInPasswd(uint32(uid), r, defaultHome) 82 if err != nil { 83 return "", err 84 } 85 86 return homeDir, nil 87 } 88 89 // MaybeAddExecUserHome returns a new slice with the HOME environment 90 // variable set if the slice does not already contain it, otherwise it returns 91 // the original slice unmodified. 92 func MaybeAddExecUserHome(ctx context.Context, vmns *vfs.MountNamespace, uid auth.KUID, envv []string) ([]string, error) { 93 // Check if the envv already contains HOME. 94 for _, env := range envv { 95 if strings.HasPrefix(env, "HOME=") { 96 // We have it. Return the original slice unmodified. 97 return envv, nil 98 } 99 } 100 101 // Read /etc/passwd for the user's HOME directory and set the HOME 102 // environment variable as required by POSIX if it is not overridden by 103 // the user. 104 homeDir, err := getExecUserHome(ctx, vmns, uid) 105 if err != nil { 106 return nil, fmt.Errorf("error reading exec user: %v", err) 107 } 108 return append(envv, "HOME="+homeDir), nil 109 } 110 111 // findHomeInPasswd parses a passwd file and returns the given user's home 112 // directory. This function does it's best to replicate the runc's behavior. 113 func findHomeInPasswd(uid uint32, passwd io.Reader, defaultHome string) (string, error) { 114 s := bufio.NewScanner(passwd) 115 116 for s.Scan() { 117 if err := s.Err(); err != nil { 118 return "", err 119 } 120 121 line := strings.TrimSpace(s.Text()) 122 if line == "" { 123 continue 124 } 125 126 // Pull out part of passwd entry. Loosely parse the passwd entry as some 127 // passwd files could be poorly written and for compatibility with runc. 128 // 129 // Per 'man 5 passwd' 130 // /etc/passwd contains one line for each user account, with seven 131 // fields delimited by colons (“:”). These fields are: 132 // 133 // - login name 134 // - optional encrypted password 135 // - numerical user ID 136 // - numerical group ID 137 // - user name or comment field 138 // - user home directory 139 // - optional user command interpreter 140 parts := strings.Split(line, ":") 141 142 found := false 143 homeDir := "" 144 for i, p := range parts { 145 switch i { 146 case 2: 147 parsedUID, err := strconv.ParseUint(p, 10, 32) 148 if err == nil && parsedUID == uint64(uid) { 149 found = true 150 } 151 case 5: 152 homeDir = p 153 } 154 } 155 if found { 156 // NOTE: If the uid is present but the home directory is not 157 // present in the /etc/passwd entry we return an empty string. This 158 // is, for better or worse, what runc does. 159 return homeDir, nil 160 } 161 } 162 163 return defaultHome, nil 164 }