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