github.com/rish1988/moby@v25.0.2+incompatible/pkg/idtools/idtools_unix_test.go (about)

     1  //go:build !windows
     2  
     3  package idtools // import "github.com/docker/docker/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  }