github.com/rawahars/moby@v24.0.4+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/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  }