idtools_unix_test.go 13 KB

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