gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/fsimpl/user/user_test.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 16 17 import ( 18 "fmt" 19 "strings" 20 "testing" 21 22 "gvisor.dev/gvisor/pkg/abi/linux" 23 "gvisor.dev/gvisor/pkg/context" 24 "gvisor.dev/gvisor/pkg/fspath" 25 "gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs" 26 "gvisor.dev/gvisor/pkg/sentry/kernel/auth" 27 "gvisor.dev/gvisor/pkg/sentry/kernel/contexttest" 28 "gvisor.dev/gvisor/pkg/sentry/vfs" 29 "gvisor.dev/gvisor/pkg/usermem" 30 ) 31 32 // createEtcPasswd creates /etc/passwd with the given contents and mode. If 33 // mode is empty, then no file will be created. If mode is not a regular file 34 // mode, then contents is ignored. 35 func createEtcPasswd(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, root vfs.VirtualDentry, contents string, mode linux.FileMode) error { 36 pop := vfs.PathOperation{ 37 Root: root, 38 Start: root, 39 Path: fspath.Parse("etc"), 40 } 41 if err := vfsObj.MkdirAt(ctx, creds, &pop, &vfs.MkdirOptions{ 42 Mode: 0755, 43 }); err != nil { 44 return fmt.Errorf("failed to create directory etc: %v", err) 45 } 46 47 pop = vfs.PathOperation{ 48 Root: root, 49 Start: root, 50 Path: fspath.Parse("etc/passwd"), 51 } 52 switch mode.FileType() { 53 case 0: 54 // Don't create anything. 55 return nil 56 case linux.S_IFREG: 57 fd, err := vfsObj.OpenAt(ctx, creds, &pop, &vfs.OpenOptions{Flags: linux.O_CREAT | linux.O_WRONLY, Mode: mode}) 58 if err != nil { 59 return err 60 } 61 defer fd.DecRef(ctx) 62 _, err = fd.Write(ctx, usermem.BytesIOSequence([]byte(contents)), vfs.WriteOptions{}) 63 return err 64 case linux.S_IFDIR: 65 return vfsObj.MkdirAt(ctx, creds, &pop, &vfs.MkdirOptions{Mode: mode}) 66 case linux.S_IFIFO: 67 return vfsObj.MknodAt(ctx, creds, &pop, &vfs.MknodOptions{Mode: mode}) 68 default: 69 return fmt.Errorf("unknown file type %x", mode.FileType()) 70 } 71 } 72 73 // TestGetExecUserHome tests the getExecUserHome function. 74 func TestGetExecUserHome(t *testing.T) { 75 tests := map[string]struct { 76 uid auth.KUID 77 passwdContents string 78 passwdMode linux.FileMode 79 expected string 80 }{ 81 "success": { 82 uid: 1000, 83 passwdContents: "adin::1000:1111::/home/adin:/bin/sh", 84 passwdMode: linux.S_IFREG | 0666, 85 expected: "/home/adin", 86 }, 87 "no_perms": { 88 uid: 1000, 89 passwdContents: "adin::1000:1111::/home/adin:/bin/sh", 90 passwdMode: linux.S_IFREG, 91 expected: "/", 92 }, 93 "no_passwd": { 94 uid: 1000, 95 expected: "/", 96 }, 97 "directory": { 98 uid: 1000, 99 passwdMode: linux.S_IFDIR | 0666, 100 expected: "/", 101 }, 102 // Currently we don't allow named pipes. 103 "named_pipe": { 104 uid: 1000, 105 passwdMode: linux.S_IFIFO | 0666, 106 expected: "/", 107 }, 108 } 109 110 for name, tc := range tests { 111 t.Run(name, func(t *testing.T) { 112 ctx := contexttest.Context(t) 113 creds := auth.CredentialsFromContext(ctx) 114 115 // Create VFS. 116 vfsObj := vfs.VirtualFilesystem{} 117 if err := vfsObj.Init(ctx); err != nil { 118 t.Fatalf("VFS init: %v", err) 119 } 120 vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ 121 AllowUserMount: true, 122 }) 123 mns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}, nil) 124 if err != nil { 125 t.Fatalf("failed to create tmpfs root mount: %v", err) 126 } 127 defer mns.DecRef(ctx) 128 root := mns.Root(ctx) 129 defer root.DecRef(ctx) 130 131 if err := createEtcPasswd(ctx, &vfsObj, creds, root, tc.passwdContents, tc.passwdMode); err != nil { 132 t.Fatalf("createEtcPasswd failed: %v", err) 133 } 134 135 got, err := getExecUserHome(ctx, mns, tc.uid) 136 if err != nil { 137 t.Fatalf("failed to get user home: %v", err) 138 } 139 140 if got != tc.expected { 141 t.Fatalf("expected %v, got: %v", tc.expected, got) 142 } 143 }) 144 } 145 } 146 147 // TestFindHomeInPasswd tests the findHomeInPasswd function's passwd file parsing. 148 func TestFindHomeInPasswd(t *testing.T) { 149 tests := map[string]struct { 150 uid uint32 151 passwd string 152 expected string 153 def string 154 }{ 155 "empty": { 156 uid: 1000, 157 passwd: "", 158 expected: "/", 159 def: "/", 160 }, 161 "whitespace": { 162 uid: 1000, 163 passwd: " ", 164 expected: "/", 165 def: "/", 166 }, 167 "full": { 168 uid: 1000, 169 passwd: "adin::1000:1111::/home/adin:/bin/sh", 170 expected: "/home/adin", 171 def: "/", 172 }, 173 // For better or worse, this is how runc works. 174 "partial": { 175 uid: 1000, 176 passwd: "adin::1000:1111:", 177 expected: "", 178 def: "/", 179 }, 180 "multiple": { 181 uid: 1001, 182 passwd: "adin::1000:1111::/home/adin:/bin/sh\nian::1001:1111::/home/ian:/bin/sh", 183 expected: "/home/ian", 184 def: "/", 185 }, 186 "duplicate": { 187 uid: 1000, 188 passwd: "adin::1000:1111::/home/adin:/bin/sh\nian::1000:1111::/home/ian:/bin/sh", 189 expected: "/home/adin", 190 def: "/", 191 }, 192 "empty_lines": { 193 uid: 1001, 194 passwd: "adin::1000:1111::/home/adin:/bin/sh\n\n\nian::1001:1111::/home/ian:/bin/sh", 195 expected: "/home/ian", 196 def: "/", 197 }, 198 } 199 200 for name, tc := range tests { 201 t.Run(name, func(t *testing.T) { 202 got, err := findHomeInPasswd(tc.uid, strings.NewReader(tc.passwd), tc.def) 203 if err != nil { 204 t.Fatalf("error parsing passwd: %v", err) 205 } 206 if tc.expected != got { 207 t.Fatalf("expected %v, got: %v", tc.expected, got) 208 } 209 }) 210 } 211 } 212 213 // TestGetExecUIDGIDFromUser tests the GetExecUIDGIDFromUser function. 214 func TestGetExecUIDGIDFromUser(t *testing.T) { 215 tests := map[string]struct { 216 user string 217 passwdContents string 218 passwdMode linux.FileMode 219 expectedUID auth.KUID 220 expectedGID auth.KGID 221 }{ 222 "success": { 223 user: "user0", 224 passwdContents: "user0::1000:1111:&:/home/user0:/bin/sh", 225 passwdMode: linux.S_IFREG | 0666, 226 expectedUID: 1000, 227 expectedGID: 1111, 228 }, 229 "success_with_uid_only": { 230 user: "1000", 231 passwdContents: "user0::1000:1111:&:/home/user0:/bin/sh", 232 passwdMode: linux.S_IFREG | 0666, 233 expectedUID: 1000, 234 expectedGID: 1111, 235 }, 236 "success_with_uid_and_gid": { 237 user: "1000:1111", 238 passwdContents: "user0::1000:1111:&:/home/user0:/bin/sh", 239 passwdMode: linux.S_IFREG | 0666, 240 expectedUID: 1000, 241 expectedGID: 1111, 242 }, 243 "success_with_uid_and_wrong_gid": { 244 user: "1000:1112", 245 passwdContents: "user0::1000:1111:&:/home/user0:/bin/sh", 246 passwdMode: linux.S_IFREG | 0666, 247 expectedUID: 1000, 248 expectedGID: 1111, 249 }, 250 "no_user": { 251 user: "user1", 252 passwdContents: "user0::1000:1111::/home/user0:/bin/sh", 253 passwdMode: linux.S_IFREG | 0666, 254 expectedUID: 65534, 255 expectedGID: 65534, 256 }, 257 "multiple_user_no_match": { 258 user: "user1", 259 passwdContents: "user0::1000:1111::/home/user0:/bin/sh\nuser2::1002:1112::/home/user2:/bin/sh\nuser3::1003:1113::/home/user3:/bin/sh", 260 passwdMode: linux.S_IFREG | 0666, 261 expectedUID: 65534, 262 expectedGID: 65534, 263 }, 264 "multiple_user_many_match": { 265 user: "user1", 266 passwdContents: "user0::1000:1111::/home/user0:/bin/sh\nuser1::1002:1112::/home/user1:/bin/sh\nuser1::1003:1113::/home/user1:/bin/sh", 267 passwdMode: linux.S_IFREG | 0666, 268 expectedUID: 65534, 269 expectedGID: 65534, 270 }, 271 "invalid_file": { 272 user: "user1", 273 passwdContents: "user0:1000:1111::/home/user0:/bin/sh\nuser1::1001:1111::/home/user1:/bin/sh\nuser2::/home/user2:/bin/sh", 274 passwdMode: linux.S_IFREG | 0666, 275 expectedUID: 65534, 276 expectedGID: 65534, 277 }, 278 "empty_file": { 279 user: "user1", 280 passwdContents: "", 281 passwdMode: linux.S_IFREG | 0666, 282 expectedUID: 65534, 283 expectedGID: 65534, 284 }, 285 "empty_user": { 286 user: "", 287 passwdContents: "user0::1000:1111::/home/user0:/bin/sh\nuser2::1002:1112::/home/user2:/bin/sh\nuser3::1003:1113::/home/user3:/bin/sh", 288 passwdMode: linux.S_IFREG | 0666, 289 expectedUID: 65534, 290 expectedGID: 65534, 291 }, 292 } 293 294 for name, tc := range tests { 295 t.Run(name, func(t *testing.T) { 296 ctx := contexttest.Context(t) 297 creds := auth.CredentialsFromContext(ctx) 298 299 // Create VFS. 300 vfsObj := vfs.VirtualFilesystem{} 301 if err := vfsObj.Init(ctx); err != nil { 302 t.Fatalf("VFS init: %v", err) 303 } 304 vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{ 305 AllowUserMount: true, 306 }) 307 mns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}, nil) 308 if err != nil { 309 t.Fatalf("failed to create tmpfs root mount: %v", err) 310 } 311 defer mns.DecRef(ctx) 312 root := mns.Root(ctx) 313 defer root.DecRef(ctx) 314 315 if err := createEtcPasswd(ctx, &vfsObj, creds, root, tc.passwdContents, tc.passwdMode); err != nil { 316 t.Fatalf("createEtcPasswd failed: %v", err) 317 } 318 319 gotUID, gotGID, err := GetExecUIDGIDFromUser(ctx, mns, tc.user) 320 if strings.HasPrefix(name, "success") { 321 if err != nil { 322 t.Fatalf("failed to get UID and GID from user: %v %v", tc.user, err) 323 } 324 } else { 325 if err == nil { 326 t.Fatalf("retrieved UID and GID when user %v is not in /etc/passwd: %v", tc.user, err) 327 } 328 } 329 if gotUID != tc.expectedUID { 330 t.Fatalf("expectedUID %v, gotUID: %v", tc.expectedUID, gotUID) 331 } 332 if gotGID != tc.expectedGID { 333 t.Fatalf("expectedGID %v, gotGID: %v", tc.expectedGID, gotGID) 334 } 335 }) 336 } 337 }