idtools_unix_test.go 13 KB

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