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