idtools_unix_test.go 13 KB

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