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  }