archive_linux_test.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. package archive // import "github.com/docker/docker/pkg/archive"
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "os"
  6. "os/exec"
  7. "path/filepath"
  8. "syscall"
  9. "testing"
  10. "github.com/docker/docker/pkg/mount"
  11. "github.com/docker/docker/pkg/reexec"
  12. "github.com/docker/docker/pkg/system"
  13. rsystem "github.com/opencontainers/runc/libcontainer/system"
  14. "github.com/pkg/errors"
  15. "golang.org/x/sys/unix"
  16. "gotest.tools/v3/assert"
  17. "gotest.tools/v3/skip"
  18. )
  19. // setupOverlayTestDir creates files in a directory with overlay whiteouts
  20. // Tree layout
  21. // .
  22. // ├── d1 # opaque, 0700
  23. // │   └── f1 # empty file, 0600
  24. // ├── d2 # opaque, 0750
  25. // │   └── f1 # empty file, 0660
  26. // └── d3 # 0700
  27. // └── f1 # whiteout, 0644
  28. func setupOverlayTestDir(t *testing.T, src string) {
  29. skip.If(t, os.Getuid() != 0, "skipping test that requires root")
  30. skip.If(t, rsystem.RunningInUserNS(), "skipping test that requires initial userns (trusted.overlay.opaque xattr cannot be set in userns, even with Ubuntu kernel)")
  31. // Create opaque directory containing single file and permission 0700
  32. err := os.Mkdir(filepath.Join(src, "d1"), 0700)
  33. assert.NilError(t, err)
  34. err = system.Lsetxattr(filepath.Join(src, "d1"), "trusted.overlay.opaque", []byte("y"), 0)
  35. assert.NilError(t, err)
  36. err = ioutil.WriteFile(filepath.Join(src, "d1", "f1"), []byte{}, 0600)
  37. assert.NilError(t, err)
  38. // Create another opaque directory containing single file but with permission 0750
  39. err = os.Mkdir(filepath.Join(src, "d2"), 0750)
  40. assert.NilError(t, err)
  41. err = system.Lsetxattr(filepath.Join(src, "d2"), "trusted.overlay.opaque", []byte("y"), 0)
  42. assert.NilError(t, err)
  43. err = ioutil.WriteFile(filepath.Join(src, "d2", "f1"), []byte{}, 0660)
  44. assert.NilError(t, err)
  45. // Create regular directory with deleted file
  46. err = os.Mkdir(filepath.Join(src, "d3"), 0700)
  47. assert.NilError(t, err)
  48. err = system.Mknod(filepath.Join(src, "d3", "f1"), unix.S_IFCHR, 0)
  49. assert.NilError(t, err)
  50. }
  51. func checkOpaqueness(t *testing.T, path string, opaque string) {
  52. xattrOpaque, err := system.Lgetxattr(path, "trusted.overlay.opaque")
  53. assert.NilError(t, err)
  54. if string(xattrOpaque) != opaque {
  55. t.Fatalf("Unexpected opaque value: %q, expected %q", string(xattrOpaque), opaque)
  56. }
  57. }
  58. func checkOverlayWhiteout(t *testing.T, path string) {
  59. stat, err := os.Stat(path)
  60. assert.NilError(t, err)
  61. statT, ok := stat.Sys().(*syscall.Stat_t)
  62. if !ok {
  63. t.Fatalf("Unexpected type: %t, expected *syscall.Stat_t", stat.Sys())
  64. }
  65. if statT.Rdev != 0 {
  66. t.Fatalf("Non-zero device number for whiteout")
  67. }
  68. }
  69. func checkFileMode(t *testing.T, path string, perm os.FileMode) {
  70. stat, err := os.Stat(path)
  71. assert.NilError(t, err)
  72. if stat.Mode() != perm {
  73. t.Fatalf("Unexpected file mode for %s: %o, expected %o", path, stat.Mode(), perm)
  74. }
  75. }
  76. func TestOverlayTarUntar(t *testing.T) {
  77. oldmask, err := system.Umask(0)
  78. assert.NilError(t, err)
  79. defer system.Umask(oldmask)
  80. src, err := ioutil.TempDir("", "docker-test-overlay-tar-src")
  81. assert.NilError(t, err)
  82. defer os.RemoveAll(src)
  83. setupOverlayTestDir(t, src)
  84. dst, err := ioutil.TempDir("", "docker-test-overlay-tar-dst")
  85. assert.NilError(t, err)
  86. defer os.RemoveAll(dst)
  87. options := &TarOptions{
  88. Compression: Uncompressed,
  89. WhiteoutFormat: OverlayWhiteoutFormat,
  90. }
  91. archive, err := TarWithOptions(src, options)
  92. assert.NilError(t, err)
  93. defer archive.Close()
  94. err = Untar(archive, dst, options)
  95. assert.NilError(t, err)
  96. checkFileMode(t, filepath.Join(dst, "d1"), 0700|os.ModeDir)
  97. checkFileMode(t, filepath.Join(dst, "d2"), 0750|os.ModeDir)
  98. checkFileMode(t, filepath.Join(dst, "d3"), 0700|os.ModeDir)
  99. checkFileMode(t, filepath.Join(dst, "d1", "f1"), 0600)
  100. checkFileMode(t, filepath.Join(dst, "d2", "f1"), 0660)
  101. checkFileMode(t, filepath.Join(dst, "d3", "f1"), os.ModeCharDevice|os.ModeDevice)
  102. checkOpaqueness(t, filepath.Join(dst, "d1"), "y")
  103. checkOpaqueness(t, filepath.Join(dst, "d2"), "y")
  104. checkOpaqueness(t, filepath.Join(dst, "d3"), "")
  105. checkOverlayWhiteout(t, filepath.Join(dst, "d3", "f1"))
  106. }
  107. func TestOverlayTarAUFSUntar(t *testing.T) {
  108. oldmask, err := system.Umask(0)
  109. assert.NilError(t, err)
  110. defer system.Umask(oldmask)
  111. src, err := ioutil.TempDir("", "docker-test-overlay-tar-src")
  112. assert.NilError(t, err)
  113. defer os.RemoveAll(src)
  114. setupOverlayTestDir(t, src)
  115. dst, err := ioutil.TempDir("", "docker-test-overlay-tar-dst")
  116. assert.NilError(t, err)
  117. defer os.RemoveAll(dst)
  118. archive, err := TarWithOptions(src, &TarOptions{
  119. Compression: Uncompressed,
  120. WhiteoutFormat: OverlayWhiteoutFormat,
  121. })
  122. assert.NilError(t, err)
  123. defer archive.Close()
  124. err = Untar(archive, dst, &TarOptions{
  125. Compression: Uncompressed,
  126. WhiteoutFormat: AUFSWhiteoutFormat,
  127. })
  128. assert.NilError(t, err)
  129. checkFileMode(t, filepath.Join(dst, "d1"), 0700|os.ModeDir)
  130. checkFileMode(t, filepath.Join(dst, "d1", WhiteoutOpaqueDir), 0700)
  131. checkFileMode(t, filepath.Join(dst, "d2"), 0750|os.ModeDir)
  132. checkFileMode(t, filepath.Join(dst, "d2", WhiteoutOpaqueDir), 0750)
  133. checkFileMode(t, filepath.Join(dst, "d3"), 0700|os.ModeDir)
  134. checkFileMode(t, filepath.Join(dst, "d1", "f1"), 0600)
  135. checkFileMode(t, filepath.Join(dst, "d2", "f1"), 0660)
  136. checkFileMode(t, filepath.Join(dst, "d3", WhiteoutPrefix+"f1"), 0600)
  137. }
  138. func unshareCmd(cmd *exec.Cmd) {
  139. cmd.SysProcAttr = &syscall.SysProcAttr{
  140. Cloneflags: syscall.CLONE_NEWUSER | syscall.CLONE_NEWNS,
  141. UidMappings: []syscall.SysProcIDMap{
  142. {
  143. ContainerID: 0,
  144. HostID: os.Geteuid(),
  145. Size: 1,
  146. },
  147. },
  148. GidMappings: []syscall.SysProcIDMap{
  149. {
  150. ContainerID: 0,
  151. HostID: os.Getegid(),
  152. Size: 1,
  153. },
  154. },
  155. }
  156. }
  157. const (
  158. reexecSupportsUserNSOverlay = "docker-test-supports-userns-overlay"
  159. reexecMknodChar0 = "docker-test-userns-mknod-char0"
  160. reexecSetOpaque = "docker-test-userns-set-opaque"
  161. )
  162. func supportsOverlay(dir string) error {
  163. lower := filepath.Join(dir, "l")
  164. upper := filepath.Join(dir, "u")
  165. work := filepath.Join(dir, "w")
  166. merged := filepath.Join(dir, "m")
  167. for _, s := range []string{lower, upper, work, merged} {
  168. if err := os.MkdirAll(s, 0700); err != nil {
  169. return err
  170. }
  171. }
  172. mOpts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lower, upper, work)
  173. if err := mount.Mount("overlay", merged, "overlay", mOpts); err != nil {
  174. return err
  175. }
  176. if err := mount.Unmount(merged); err != nil {
  177. return err
  178. }
  179. return nil
  180. }
  181. // supportsUserNSOverlay returns nil error if overlay is supported in userns.
  182. // Only Ubuntu and a few distros support overlay in userns (by patching the kernel).
  183. // https://lists.ubuntu.com/archives/kernel-team/2014-February/038091.html
  184. // As of kernel 4.19, the patch is not merged to the upstream.
  185. func supportsUserNSOverlay() error {
  186. tmp, err := ioutil.TempDir("", "docker-test-supports-userns-overlay")
  187. if err != nil {
  188. return err
  189. }
  190. defer os.RemoveAll(tmp)
  191. cmd := reexec.Command(reexecSupportsUserNSOverlay, tmp)
  192. unshareCmd(cmd)
  193. out, err := cmd.CombinedOutput()
  194. if err != nil {
  195. return errors.Wrapf(err, "output: %q", string(out))
  196. }
  197. return nil
  198. }
  199. // isOpaque returns nil error if the dir has trusted.overlay.opaque=y.
  200. // isOpaque needs to be called in the initial userns.
  201. func isOpaque(dir string) error {
  202. xattrOpaque, err := system.Lgetxattr(dir, "trusted.overlay.opaque")
  203. if err != nil {
  204. return errors.Wrapf(err, "failed to read opaque flag of %s", dir)
  205. }
  206. if string(xattrOpaque) != "y" {
  207. return errors.Errorf("expected \"y\", got %q", string(xattrOpaque))
  208. }
  209. return nil
  210. }
  211. func TestReexecUserNSOverlayWhiteoutConverter(t *testing.T) {
  212. skip.If(t, os.Getuid() != 0, "skipping test that requires root")
  213. skip.If(t, rsystem.RunningInUserNS(), "skipping test that requires initial userns")
  214. if err := supportsUserNSOverlay(); err != nil {
  215. t.Skipf("skipping test that requires kernel support for overlay-in-userns: %v", err)
  216. }
  217. tmp, err := ioutil.TempDir("", "docker-test-userns-overlay")
  218. assert.NilError(t, err)
  219. defer os.RemoveAll(tmp)
  220. char0 := filepath.Join(tmp, "char0")
  221. cmd := reexec.Command(reexecMknodChar0, char0)
  222. unshareCmd(cmd)
  223. out, err := cmd.CombinedOutput()
  224. assert.NilError(t, err, string(out))
  225. assert.NilError(t, isChar0(char0))
  226. opaqueDir := filepath.Join(tmp, "opaquedir")
  227. err = os.MkdirAll(opaqueDir, 0755)
  228. assert.NilError(t, err, string(out))
  229. cmd = reexec.Command(reexecSetOpaque, opaqueDir)
  230. unshareCmd(cmd)
  231. out, err = cmd.CombinedOutput()
  232. assert.NilError(t, err, string(out))
  233. assert.NilError(t, isOpaque(opaqueDir))
  234. }
  235. func init() {
  236. reexec.Register(reexecSupportsUserNSOverlay, func() {
  237. if err := supportsOverlay(os.Args[1]); err != nil {
  238. panic(err)
  239. }
  240. })
  241. reexec.Register(reexecMknodChar0, func() {
  242. if err := mknodChar0Overlay(os.Args[1]); err != nil {
  243. panic(err)
  244. }
  245. })
  246. reexec.Register(reexecSetOpaque, func() {
  247. if err := replaceDirWithOverlayOpaque(os.Args[1]); err != nil {
  248. panic(err)
  249. }
  250. })
  251. if reexec.Init() {
  252. os.Exit(0)
  253. }
  254. }