github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/pkg/idtools/idtools_unix_test.go (about) 1 //go:build !windows 2 3 package idtools // import "github.com/Prakhar-Agarwal-byte/moby/pkg/idtools" 4 5 import ( 6 "fmt" 7 "os" 8 "os/exec" 9 "os/user" 10 "path/filepath" 11 "syscall" 12 "testing" 13 14 "golang.org/x/sys/unix" 15 "gotest.tools/v3/assert" 16 is "gotest.tools/v3/assert/cmp" 17 "gotest.tools/v3/skip" 18 ) 19 20 const ( 21 tempUser = "tempuser" 22 ) 23 24 type node struct { 25 uid int 26 gid int 27 } 28 29 func TestMkdirAllAndChown(t *testing.T) { 30 RequiresRoot(t) 31 dirName, err := os.MkdirTemp("", "mkdirall") 32 if err != nil { 33 t.Fatalf("Couldn't create temp dir: %v", err) 34 } 35 defer os.RemoveAll(dirName) 36 37 testTree := map[string]node{ 38 "usr": {0, 0}, 39 "usr/bin": {0, 0}, 40 "lib": {33, 33}, 41 "lib/x86_64": {45, 45}, 42 "lib/x86_64/share": {1, 1}, 43 } 44 45 if err := buildTree(dirName, testTree); err != nil { 46 t.Fatal(err) 47 } 48 49 // test adding a directory to a pre-existing dir; only the new dir is owned by the uid/gid 50 if err := MkdirAllAndChown(filepath.Join(dirName, "usr", "share"), 0o755, Identity{UID: 99, GID: 99}); err != nil { 51 t.Fatal(err) 52 } 53 testTree["usr/share"] = node{99, 99} 54 verifyTree, err := readTree(dirName, "") 55 if err != nil { 56 t.Fatal(err) 57 } 58 if err := compareTrees(testTree, verifyTree); err != nil { 59 t.Fatal(err) 60 } 61 62 // test 2-deep new directories--both should be owned by the uid/gid pair 63 if err := MkdirAllAndChown(filepath.Join(dirName, "lib", "some", "other"), 0o755, Identity{UID: 101, GID: 101}); err != nil { 64 t.Fatal(err) 65 } 66 testTree["lib/some"] = node{101, 101} 67 testTree["lib/some/other"] = node{101, 101} 68 verifyTree, err = readTree(dirName, "") 69 if err != nil { 70 t.Fatal(err) 71 } 72 if err := compareTrees(testTree, verifyTree); err != nil { 73 t.Fatal(err) 74 } 75 76 // test a directory that already exists; should be chowned, but nothing else 77 if err := MkdirAllAndChown(filepath.Join(dirName, "usr"), 0o755, Identity{UID: 102, GID: 102}); err != nil { 78 t.Fatal(err) 79 } 80 testTree["usr"] = node{102, 102} 81 verifyTree, err = readTree(dirName, "") 82 if err != nil { 83 t.Fatal(err) 84 } 85 if err := compareTrees(testTree, verifyTree); err != nil { 86 t.Fatal(err) 87 } 88 } 89 90 func TestMkdirAllAndChownNew(t *testing.T) { 91 RequiresRoot(t) 92 dirName, err := os.MkdirTemp("", "mkdirnew") 93 assert.NilError(t, err) 94 defer os.RemoveAll(dirName) 95 96 testTree := map[string]node{ 97 "usr": {0, 0}, 98 "usr/bin": {0, 0}, 99 "lib": {33, 33}, 100 "lib/x86_64": {45, 45}, 101 "lib/x86_64/share": {1, 1}, 102 } 103 assert.NilError(t, buildTree(dirName, testTree)) 104 105 // test adding a directory to a pre-existing dir; only the new dir is owned by the uid/gid 106 err = MkdirAllAndChownNew(filepath.Join(dirName, "usr", "share"), 0o755, Identity{UID: 99, GID: 99}) 107 assert.NilError(t, err) 108 109 testTree["usr/share"] = node{99, 99} 110 verifyTree, err := readTree(dirName, "") 111 assert.NilError(t, err) 112 assert.NilError(t, compareTrees(testTree, verifyTree)) 113 114 // test 2-deep new directories--both should be owned by the uid/gid pair 115 err = MkdirAllAndChownNew(filepath.Join(dirName, "lib", "some", "other"), 0o755, Identity{UID: 101, GID: 101}) 116 assert.NilError(t, err) 117 testTree["lib/some"] = node{101, 101} 118 testTree["lib/some/other"] = node{101, 101} 119 verifyTree, err = readTree(dirName, "") 120 assert.NilError(t, err) 121 assert.NilError(t, compareTrees(testTree, verifyTree)) 122 123 // test a directory that already exists; should NOT be chowned 124 err = MkdirAllAndChownNew(filepath.Join(dirName, "usr"), 0o755, Identity{UID: 102, GID: 102}) 125 assert.NilError(t, err) 126 verifyTree, err = readTree(dirName, "") 127 assert.NilError(t, err) 128 assert.NilError(t, compareTrees(testTree, verifyTree)) 129 } 130 131 func TestMkdirAllAndChownNewRelative(t *testing.T) { 132 RequiresRoot(t) 133 134 tests := []struct { 135 in string 136 out []string 137 }{ 138 { 139 in: "dir1", 140 out: []string{"dir1"}, 141 }, 142 { 143 in: "dir2/subdir2", 144 out: []string{"dir2", "dir2/subdir2"}, 145 }, 146 { 147 in: "dir3/subdir3/", 148 out: []string{"dir3", "dir3/subdir3"}, 149 }, 150 { 151 in: "dir4/subdir4/.", 152 out: []string{"dir4", "dir4/subdir4"}, 153 }, 154 { 155 in: "dir5/././subdir5/", 156 out: []string{"dir5", "dir5/subdir5"}, 157 }, 158 { 159 in: "./dir6", 160 out: []string{"dir6"}, 161 }, 162 { 163 in: "./dir7/subdir7", 164 out: []string{"dir7", "dir7/subdir7"}, 165 }, 166 { 167 in: "./dir8/subdir8/", 168 out: []string{"dir8", "dir8/subdir8"}, 169 }, 170 { 171 in: "./dir9/subdir9/.", 172 out: []string{"dir9", "dir9/subdir9"}, 173 }, 174 { 175 in: "./dir10/././subdir10/", 176 out: []string{"dir10", "dir10/subdir10"}, 177 }, 178 } 179 180 // Set the current working directory to the temp-dir, as we're 181 // testing relative paths. 182 tmpDir := t.TempDir() 183 setWorkingDirectory(t, tmpDir) 184 185 const expectedUIDGID = 101 186 187 for _, tc := range tests { 188 tc := tc 189 t.Run(tc.in, func(t *testing.T) { 190 for _, p := range tc.out { 191 _, err := os.Stat(p) 192 assert.ErrorIs(t, err, os.ErrNotExist) 193 } 194 195 err := MkdirAllAndChownNew(tc.in, 0o755, Identity{UID: expectedUIDGID, GID: expectedUIDGID}) 196 assert.Check(t, err) 197 198 for _, p := range tc.out { 199 s := &unix.Stat_t{} 200 err = unix.Stat(p, s) 201 if assert.Check(t, err) { 202 assert.Check(t, is.Equal(uint64(s.Uid), uint64(expectedUIDGID))) 203 assert.Check(t, is.Equal(uint64(s.Gid), uint64(expectedUIDGID))) 204 } 205 } 206 }) 207 } 208 } 209 210 // Change the current working directory for the duration of the test. This may 211 // break if tests are run in parallel. 212 func setWorkingDirectory(t *testing.T, dir string) { 213 t.Helper() 214 cwd, err := os.Getwd() 215 assert.NilError(t, err) 216 t.Cleanup(func() { 217 assert.NilError(t, os.Chdir(cwd)) 218 }) 219 err = os.Chdir(dir) 220 assert.NilError(t, err) 221 } 222 223 func TestMkdirAndChown(t *testing.T) { 224 RequiresRoot(t) 225 dirName, err := os.MkdirTemp("", "mkdir") 226 if err != nil { 227 t.Fatalf("Couldn't create temp dir: %v", err) 228 } 229 defer os.RemoveAll(dirName) 230 231 testTree := map[string]node{ 232 "usr": {0, 0}, 233 } 234 if err := buildTree(dirName, testTree); err != nil { 235 t.Fatal(err) 236 } 237 238 // test a directory that already exists; should just chown to the requested uid/gid 239 if err := MkdirAndChown(filepath.Join(dirName, "usr"), 0o755, Identity{UID: 99, GID: 99}); err != nil { 240 t.Fatal(err) 241 } 242 testTree["usr"] = node{99, 99} 243 verifyTree, err := readTree(dirName, "") 244 if err != nil { 245 t.Fatal(err) 246 } 247 if err := compareTrees(testTree, verifyTree); err != nil { 248 t.Fatal(err) 249 } 250 251 // create a subdir under a dir which doesn't exist--should fail 252 if err := MkdirAndChown(filepath.Join(dirName, "usr", "bin", "subdir"), 0o755, Identity{UID: 102, GID: 102}); err == nil { 253 t.Fatalf("Trying to create a directory with Mkdir where the parent doesn't exist should have failed") 254 } 255 256 // create a subdir under an existing dir; should only change the ownership of the new subdir 257 if err := MkdirAndChown(filepath.Join(dirName, "usr", "bin"), 0o755, Identity{UID: 102, GID: 102}); err != nil { 258 t.Fatal(err) 259 } 260 testTree["usr/bin"] = node{102, 102} 261 verifyTree, err = readTree(dirName, "") 262 if err != nil { 263 t.Fatal(err) 264 } 265 if err := compareTrees(testTree, verifyTree); err != nil { 266 t.Fatal(err) 267 } 268 } 269 270 func buildTree(base string, tree map[string]node) error { 271 for path, node := range tree { 272 fullPath := filepath.Join(base, path) 273 if err := os.MkdirAll(fullPath, 0o755); err != nil { 274 return fmt.Errorf("couldn't create path: %s; error: %v", fullPath, err) 275 } 276 if err := os.Chown(fullPath, node.uid, node.gid); err != nil { 277 return fmt.Errorf("couldn't chown path: %s; error: %v", fullPath, err) 278 } 279 } 280 return nil 281 } 282 283 func readTree(base, root string) (map[string]node, error) { 284 tree := make(map[string]node) 285 286 dirInfos, err := os.ReadDir(base) 287 if err != nil { 288 return nil, fmt.Errorf("couldn't read directory entries for %q: %v", base, err) 289 } 290 291 for _, info := range dirInfos { 292 s := &unix.Stat_t{} 293 if err := unix.Stat(filepath.Join(base, info.Name()), s); err != nil { 294 return nil, fmt.Errorf("can't stat file %q: %v", filepath.Join(base, info.Name()), err) 295 } 296 tree[filepath.Join(root, info.Name())] = node{int(s.Uid), int(s.Gid)} 297 if info.IsDir() { 298 // read the subdirectory 299 subtree, err := readTree(filepath.Join(base, info.Name()), filepath.Join(root, info.Name())) 300 if err != nil { 301 return nil, err 302 } 303 for path, nodeinfo := range subtree { 304 tree[path] = nodeinfo 305 } 306 } 307 } 308 return tree, nil 309 } 310 311 func compareTrees(left, right map[string]node) error { 312 if len(left) != len(right) { 313 return fmt.Errorf("trees aren't the same size") 314 } 315 for path, nodeLeft := range left { 316 if nodeRight, ok := right[path]; ok { 317 if nodeRight.uid != nodeLeft.uid || nodeRight.gid != nodeLeft.gid { 318 // mismatch 319 return fmt.Errorf("mismatched ownership for %q: expected: %d:%d, got: %d:%d", path, 320 nodeLeft.uid, nodeLeft.gid, nodeRight.uid, nodeRight.gid) 321 } 322 continue 323 } 324 return fmt.Errorf("right tree didn't contain path %q", path) 325 } 326 return nil 327 } 328 329 func delUser(t *testing.T, name string) { 330 out, err := exec.Command("userdel", name).CombinedOutput() 331 assert.Check(t, err, out) 332 } 333 334 func TestParseSubidFileWithNewlinesAndComments(t *testing.T) { 335 tmpDir, err := os.MkdirTemp("", "parsesubid") 336 if err != nil { 337 t.Fatal(err) 338 } 339 fnamePath := filepath.Join(tmpDir, "testsubuid") 340 fcontent := `tss:100000:65536 341 # empty default subuid/subgid file 342 343 dockremap:231072:65536` 344 if err := os.WriteFile(fnamePath, []byte(fcontent), 0o644); err != nil { 345 t.Fatal(err) 346 } 347 ranges, err := parseSubidFile(fnamePath, "dockremap") 348 if err != nil { 349 t.Fatal(err) 350 } 351 if len(ranges) != 1 { 352 t.Fatalf("wanted 1 element in ranges, got %d instead", len(ranges)) 353 } 354 if ranges[0].Start != 231072 { 355 t.Fatalf("wanted 231072, got %d instead", ranges[0].Start) 356 } 357 if ranges[0].Length != 65536 { 358 t.Fatalf("wanted 65536, got %d instead", ranges[0].Length) 359 } 360 } 361 362 func TestGetRootUIDGID(t *testing.T) { 363 uidMap := []IDMap{ 364 { 365 ContainerID: 0, 366 HostID: os.Getuid(), 367 Size: 1, 368 }, 369 } 370 gidMap := []IDMap{ 371 { 372 ContainerID: 0, 373 HostID: os.Getgid(), 374 Size: 1, 375 }, 376 } 377 378 uid, gid, err := GetRootUIDGID(uidMap, gidMap) 379 assert.Check(t, err) 380 assert.Check(t, is.Equal(os.Geteuid(), uid)) 381 assert.Check(t, is.Equal(os.Getegid(), gid)) 382 383 uidMapError := []IDMap{ 384 { 385 ContainerID: 1, 386 HostID: os.Getuid(), 387 Size: 1, 388 }, 389 } 390 _, _, err = GetRootUIDGID(uidMapError, gidMap) 391 assert.Check(t, is.Error(err, "Container ID 0 cannot be mapped to a host ID")) 392 } 393 394 func TestToContainer(t *testing.T) { 395 uidMap := []IDMap{ 396 { 397 ContainerID: 2, 398 HostID: 2, 399 Size: 1, 400 }, 401 } 402 403 containerID, err := toContainer(2, uidMap) 404 assert.Check(t, err) 405 assert.Check(t, is.Equal(uidMap[0].ContainerID, containerID)) 406 } 407 408 func TestNewIDMappings(t *testing.T) { 409 RequiresRoot(t) 410 _, _, err := AddNamespaceRangesUser(tempUser) 411 assert.Check(t, err) 412 defer delUser(t, tempUser) 413 414 tempUser, err := user.Lookup(tempUser) 415 assert.Check(t, err) 416 417 idMapping, err := LoadIdentityMapping(tempUser.Username) 418 assert.Check(t, err) 419 420 rootUID, rootGID, err := GetRootUIDGID(idMapping.UIDMaps, idMapping.GIDMaps) 421 assert.Check(t, err) 422 423 dirName, err := os.MkdirTemp("", "mkdirall") 424 assert.Check(t, err, "Couldn't create temp directory") 425 defer os.RemoveAll(dirName) 426 427 err = MkdirAllAndChown(dirName, 0o700, Identity{UID: rootUID, GID: rootGID}) 428 assert.Check(t, err, "Couldn't change ownership of file path. Got error") 429 cmd := exec.Command("ls", "-la", dirName) 430 cmd.SysProcAttr = &syscall.SysProcAttr{ 431 Credential: &syscall.Credential{Uid: uint32(rootUID), Gid: uint32(rootGID)}, 432 } 433 out, err := cmd.CombinedOutput() 434 assert.Check(t, err, "Unable to access %s directory with user UID:%d and GID:%d:\n%s", dirName, rootUID, rootGID, string(out)) 435 } 436 437 func TestLookupUserAndGroup(t *testing.T) { 438 RequiresRoot(t) 439 uid, gid, err := AddNamespaceRangesUser(tempUser) 440 assert.Check(t, err) 441 defer delUser(t, tempUser) 442 443 fetchedUser, err := LookupUser(tempUser) 444 assert.Check(t, err) 445 446 fetchedUserByID, err := LookupUID(uid) 447 assert.Check(t, err) 448 assert.Check(t, is.DeepEqual(fetchedUserByID, fetchedUser)) 449 450 fetchedGroup, err := LookupGroup(tempUser) 451 assert.Check(t, err) 452 453 fetchedGroupByID, err := LookupGID(gid) 454 assert.Check(t, err) 455 assert.Check(t, is.DeepEqual(fetchedGroupByID, fetchedGroup)) 456 } 457 458 func TestLookupUserAndGroupThatDoesNotExist(t *testing.T) { 459 fakeUser := "fakeuser" 460 _, err := LookupUser(fakeUser) 461 assert.Check(t, is.Error(err, `getent unable to find entry "fakeuser" in passwd database`)) 462 463 _, err = LookupUID(-1) 464 assert.Check(t, is.ErrorContains(err, "")) 465 466 fakeGroup := "fakegroup" 467 _, err = LookupGroup(fakeGroup) 468 assert.Check(t, is.Error(err, `getent unable to find entry "fakegroup" in group database`)) 469 470 _, err = LookupGID(-1) 471 assert.Check(t, is.ErrorContains(err, "")) 472 } 473 474 // TestMkdirIsNotDir checks that mkdirAs() function (used by MkdirAll...) 475 // returns a correct error in case a directory which it is about to create 476 // already exists but is a file (rather than a directory). 477 func TestMkdirIsNotDir(t *testing.T) { 478 file, err := os.CreateTemp("", t.Name()) 479 if err != nil { 480 t.Fatalf("Couldn't create temp dir: %v", err) 481 } 482 defer os.Remove(file.Name()) 483 484 err = mkdirAs(file.Name(), 0o755, Identity{UID: 0, GID: 0}, false, false) 485 assert.Check(t, is.Error(err, "mkdir "+file.Name()+": not a directory")) 486 } 487 488 func RequiresRoot(t *testing.T) { 489 skip.If(t, os.Getuid() != 0, "skipping test that requires root") 490 }