docker_cli_cp_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io/ioutil"
  6. "os"
  7. "os/exec"
  8. "path"
  9. "path/filepath"
  10. "testing"
  11. )
  12. const (
  13. cpTestPathParent = "/some"
  14. cpTestPath = "/some/path"
  15. cpTestName = "test"
  16. cpFullPath = "/some/path/test"
  17. cpContainerContents = "holla, i am the container"
  18. cpHostContents = "hello, i am the host"
  19. )
  20. // Test for #5656
  21. // Check that garbage paths don't escape the container's rootfs
  22. func TestCpGarbagePath(t *testing.T) {
  23. out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath)
  24. if err != nil || exitCode != 0 {
  25. t.Fatal("failed to create a container", out, err)
  26. }
  27. cleanedContainerID := stripTrailingCharacters(out)
  28. defer deleteContainer(cleanedContainerID)
  29. out, _, err = dockerCmd(t, "wait", cleanedContainerID)
  30. if err != nil || stripTrailingCharacters(out) != "0" {
  31. t.Fatal("failed to set up container", out, err)
  32. }
  33. if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil {
  34. t.Fatal(err)
  35. }
  36. hostFile, err := os.Create(cpFullPath)
  37. if err != nil {
  38. t.Fatal(err)
  39. }
  40. defer hostFile.Close()
  41. defer os.RemoveAll(cpTestPathParent)
  42. fmt.Fprintf(hostFile, "%s", cpHostContents)
  43. tmpdir, err := ioutil.TempDir("", "docker-integration")
  44. if err != nil {
  45. t.Fatal(err)
  46. }
  47. tmpname := filepath.Join(tmpdir, cpTestName)
  48. defer os.RemoveAll(tmpdir)
  49. path := path.Join("../../../../../../../../../../../../", cpFullPath)
  50. _, _, err = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir)
  51. if err != nil {
  52. t.Fatalf("couldn't copy from garbage path: %s:%s %s", cleanedContainerID, path, err)
  53. }
  54. file, _ := os.Open(tmpname)
  55. defer file.Close()
  56. test, err := ioutil.ReadAll(file)
  57. if err != nil {
  58. t.Fatal(err)
  59. }
  60. if string(test) == cpHostContents {
  61. t.Errorf("output matched host file -- garbage path can escape container rootfs")
  62. }
  63. if string(test) != cpContainerContents {
  64. t.Errorf("output doesn't match the input for garbage path")
  65. }
  66. logDone("cp - garbage paths relative to container's rootfs")
  67. }
  68. // Check that relative paths are relative to the container's rootfs
  69. func TestCpRelativePath(t *testing.T) {
  70. out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath)
  71. if err != nil || exitCode != 0 {
  72. t.Fatal("failed to create a container", out, err)
  73. }
  74. cleanedContainerID := stripTrailingCharacters(out)
  75. defer deleteContainer(cleanedContainerID)
  76. out, _, err = dockerCmd(t, "wait", cleanedContainerID)
  77. if err != nil || stripTrailingCharacters(out) != "0" {
  78. t.Fatal("failed to set up container", out, err)
  79. }
  80. if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil {
  81. t.Fatal(err)
  82. }
  83. hostFile, err := os.Create(cpFullPath)
  84. if err != nil {
  85. t.Fatal(err)
  86. }
  87. defer hostFile.Close()
  88. defer os.RemoveAll(cpTestPathParent)
  89. fmt.Fprintf(hostFile, "%s", cpHostContents)
  90. tmpdir, err := ioutil.TempDir("", "docker-integration")
  91. if err != nil {
  92. t.Fatal(err)
  93. }
  94. tmpname := filepath.Join(tmpdir, cpTestName)
  95. defer os.RemoveAll(tmpdir)
  96. var relPath string
  97. if path.IsAbs(cpFullPath) {
  98. // normally this is `filepath.Rel("/", cpFullPath)` but we cannot
  99. // get this unix-path manipulation on windows with filepath.
  100. relPath = cpFullPath[1:]
  101. } else {
  102. t.Fatalf("path %s was assumed to be an absolute path", cpFullPath)
  103. }
  104. _, _, err = dockerCmd(t, "cp", cleanedContainerID+":"+relPath, tmpdir)
  105. if err != nil {
  106. t.Fatalf("couldn't copy from relative path: %s:%s %s", cleanedContainerID, relPath, err)
  107. }
  108. file, _ := os.Open(tmpname)
  109. defer file.Close()
  110. test, err := ioutil.ReadAll(file)
  111. if err != nil {
  112. t.Fatal(err)
  113. }
  114. if string(test) == cpHostContents {
  115. t.Errorf("output matched host file -- relative path can escape container rootfs")
  116. }
  117. if string(test) != cpContainerContents {
  118. t.Errorf("output doesn't match the input for relative path")
  119. }
  120. logDone("cp - relative paths relative to container's rootfs")
  121. }
  122. // Check that absolute paths are relative to the container's rootfs
  123. func TestCpAbsolutePath(t *testing.T) {
  124. out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath)
  125. if err != nil || exitCode != 0 {
  126. t.Fatal("failed to create a container", out, err)
  127. }
  128. cleanedContainerID := stripTrailingCharacters(out)
  129. defer deleteContainer(cleanedContainerID)
  130. out, _, err = dockerCmd(t, "wait", cleanedContainerID)
  131. if err != nil || stripTrailingCharacters(out) != "0" {
  132. t.Fatal("failed to set up container", out, err)
  133. }
  134. if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil {
  135. t.Fatal(err)
  136. }
  137. hostFile, err := os.Create(cpFullPath)
  138. if err != nil {
  139. t.Fatal(err)
  140. }
  141. defer hostFile.Close()
  142. defer os.RemoveAll(cpTestPathParent)
  143. fmt.Fprintf(hostFile, "%s", cpHostContents)
  144. tmpdir, err := ioutil.TempDir("", "docker-integration")
  145. if err != nil {
  146. t.Fatal(err)
  147. }
  148. tmpname := filepath.Join(tmpdir, cpTestName)
  149. defer os.RemoveAll(tmpdir)
  150. path := cpFullPath
  151. _, _, err = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir)
  152. if err != nil {
  153. t.Fatalf("couldn't copy from absolute path: %s:%s %s", cleanedContainerID, path, err)
  154. }
  155. file, _ := os.Open(tmpname)
  156. defer file.Close()
  157. test, err := ioutil.ReadAll(file)
  158. if err != nil {
  159. t.Fatal(err)
  160. }
  161. if string(test) == cpHostContents {
  162. t.Errorf("output matched host file -- absolute path can escape container rootfs")
  163. }
  164. if string(test) != cpContainerContents {
  165. t.Errorf("output doesn't match the input for absolute path")
  166. }
  167. logDone("cp - absolute paths relative to container's rootfs")
  168. }
  169. // Test for #5619
  170. // Check that absolute symlinks are still relative to the container's rootfs
  171. func TestCpAbsoluteSymlink(t *testing.T) {
  172. out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpFullPath+" container_path")
  173. if err != nil || exitCode != 0 {
  174. t.Fatal("failed to create a container", out, err)
  175. }
  176. cleanedContainerID := stripTrailingCharacters(out)
  177. defer deleteContainer(cleanedContainerID)
  178. out, _, err = dockerCmd(t, "wait", cleanedContainerID)
  179. if err != nil || stripTrailingCharacters(out) != "0" {
  180. t.Fatal("failed to set up container", out, err)
  181. }
  182. if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil {
  183. t.Fatal(err)
  184. }
  185. hostFile, err := os.Create(cpFullPath)
  186. if err != nil {
  187. t.Fatal(err)
  188. }
  189. defer hostFile.Close()
  190. defer os.RemoveAll(cpTestPathParent)
  191. fmt.Fprintf(hostFile, "%s", cpHostContents)
  192. tmpdir, err := ioutil.TempDir("", "docker-integration")
  193. if err != nil {
  194. t.Fatal(err)
  195. }
  196. tmpname := filepath.Join(tmpdir, cpTestName)
  197. defer os.RemoveAll(tmpdir)
  198. path := path.Join("/", "container_path")
  199. _, _, err = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir)
  200. if err != nil {
  201. t.Fatalf("couldn't copy from absolute path: %s:%s %s", cleanedContainerID, path, err)
  202. }
  203. file, _ := os.Open(tmpname)
  204. defer file.Close()
  205. test, err := ioutil.ReadAll(file)
  206. if err != nil {
  207. t.Fatal(err)
  208. }
  209. if string(test) == cpHostContents {
  210. t.Errorf("output matched host file -- absolute symlink can escape container rootfs")
  211. }
  212. if string(test) != cpContainerContents {
  213. t.Errorf("output doesn't match the input for absolute symlink")
  214. }
  215. logDone("cp - absolute symlink relative to container's rootfs")
  216. }
  217. // Test for #5619
  218. // Check that symlinks which are part of the resource path are still relative to the container's rootfs
  219. func TestCpSymlinkComponent(t *testing.T) {
  220. out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpTestPath+" container_path")
  221. if err != nil || exitCode != 0 {
  222. t.Fatal("failed to create a container", out, err)
  223. }
  224. cleanedContainerID := stripTrailingCharacters(out)
  225. defer deleteContainer(cleanedContainerID)
  226. out, _, err = dockerCmd(t, "wait", cleanedContainerID)
  227. if err != nil || stripTrailingCharacters(out) != "0" {
  228. t.Fatal("failed to set up container", out, err)
  229. }
  230. if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil {
  231. t.Fatal(err)
  232. }
  233. hostFile, err := os.Create(cpFullPath)
  234. if err != nil {
  235. t.Fatal(err)
  236. }
  237. defer hostFile.Close()
  238. defer os.RemoveAll(cpTestPathParent)
  239. fmt.Fprintf(hostFile, "%s", cpHostContents)
  240. tmpdir, err := ioutil.TempDir("", "docker-integration")
  241. if err != nil {
  242. t.Fatal(err)
  243. }
  244. tmpname := filepath.Join(tmpdir, cpTestName)
  245. defer os.RemoveAll(tmpdir)
  246. path := path.Join("/", "container_path", cpTestName)
  247. _, _, err = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir)
  248. if err != nil {
  249. t.Fatalf("couldn't copy from symlink path component: %s:%s %s", cleanedContainerID, path, err)
  250. }
  251. file, _ := os.Open(tmpname)
  252. defer file.Close()
  253. test, err := ioutil.ReadAll(file)
  254. if err != nil {
  255. t.Fatal(err)
  256. }
  257. if string(test) == cpHostContents {
  258. t.Errorf("output matched host file -- symlink path component can escape container rootfs")
  259. }
  260. if string(test) != cpContainerContents {
  261. t.Errorf("output doesn't match the input for symlink path component")
  262. }
  263. logDone("cp - symlink path components relative to container's rootfs")
  264. }
  265. // Check that cp with unprivileged user doesn't return any error
  266. func TestCpUnprivilegedUser(t *testing.T) {
  267. testRequires(t, UnixCli) // uses chmod/su: not available on windows
  268. out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch "+cpTestName)
  269. if err != nil || exitCode != 0 {
  270. t.Fatal("failed to create a container", out, err)
  271. }
  272. cleanedContainerID := stripTrailingCharacters(out)
  273. defer deleteContainer(cleanedContainerID)
  274. out, _, err = dockerCmd(t, "wait", cleanedContainerID)
  275. if err != nil || stripTrailingCharacters(out) != "0" {
  276. t.Fatal("failed to set up container", out, err)
  277. }
  278. tmpdir, err := ioutil.TempDir("", "docker-integration")
  279. if err != nil {
  280. t.Fatal(err)
  281. }
  282. defer os.RemoveAll(tmpdir)
  283. if err = os.Chmod(tmpdir, 0777); err != nil {
  284. t.Fatal(err)
  285. }
  286. path := cpTestName
  287. _, _, err = runCommandWithOutput(exec.Command("su", "unprivilegeduser", "-c", dockerBinary+" cp "+cleanedContainerID+":"+path+" "+tmpdir))
  288. if err != nil {
  289. t.Fatalf("couldn't copy with unprivileged user: %s:%s %s", cleanedContainerID, path, err)
  290. }
  291. logDone("cp - unprivileged user")
  292. }
  293. func TestCpVolumePath(t *testing.T) {
  294. tmpDir, err := ioutil.TempDir("", "cp-test-volumepath")
  295. if err != nil {
  296. t.Fatal(err)
  297. }
  298. defer os.RemoveAll(tmpDir)
  299. outDir, err := ioutil.TempDir("", "cp-test-volumepath-out")
  300. if err != nil {
  301. t.Fatal(err)
  302. }
  303. defer os.RemoveAll(outDir)
  304. _, err = os.Create(tmpDir + "/test")
  305. if err != nil {
  306. t.Fatal(err)
  307. }
  308. out, exitCode, err := dockerCmd(t, "run", "-d", "-v", "/foo", "-v", tmpDir+"/test:/test", "-v", tmpDir+":/baz", "busybox", "/bin/sh", "-c", "touch /foo/bar")
  309. if err != nil || exitCode != 0 {
  310. t.Fatal("failed to create a container", out, err)
  311. }
  312. cleanedContainerID := stripTrailingCharacters(out)
  313. defer deleteContainer(cleanedContainerID)
  314. out, _, err = dockerCmd(t, "wait", cleanedContainerID)
  315. if err != nil || stripTrailingCharacters(out) != "0" {
  316. t.Fatal("failed to set up container", out, err)
  317. }
  318. // Copy actual volume path
  319. _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/foo", outDir)
  320. if err != nil {
  321. t.Fatalf("couldn't copy from volume path: %s:%s %v", cleanedContainerID, "/foo", err)
  322. }
  323. stat, err := os.Stat(outDir + "/foo")
  324. if err != nil {
  325. t.Fatal(err)
  326. }
  327. if !stat.IsDir() {
  328. t.Fatal("expected copied content to be dir")
  329. }
  330. stat, err = os.Stat(outDir + "/foo/bar")
  331. if err != nil {
  332. t.Fatal(err)
  333. }
  334. if stat.IsDir() {
  335. t.Fatal("Expected file `bar` to be a file")
  336. }
  337. // Copy file nested in volume
  338. _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/foo/bar", outDir)
  339. if err != nil {
  340. t.Fatalf("couldn't copy from volume path: %s:%s %v", cleanedContainerID, "/foo", err)
  341. }
  342. stat, err = os.Stat(outDir + "/bar")
  343. if err != nil {
  344. t.Fatal(err)
  345. }
  346. if stat.IsDir() {
  347. t.Fatal("Expected file `bar` to be a file")
  348. }
  349. // Copy Bind-mounted dir
  350. _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/baz", outDir)
  351. if err != nil {
  352. t.Fatalf("couldn't copy from bind-mounted volume path: %s:%s %v", cleanedContainerID, "/baz", err)
  353. }
  354. stat, err = os.Stat(outDir + "/baz")
  355. if err != nil {
  356. t.Fatal(err)
  357. }
  358. if !stat.IsDir() {
  359. t.Fatal("Expected `baz` to be a dir")
  360. }
  361. // Copy file nested in bind-mounted dir
  362. _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/baz/test", outDir)
  363. fb, err := ioutil.ReadFile(outDir + "/baz/test")
  364. if err != nil {
  365. t.Fatal(err)
  366. }
  367. fb2, err := ioutil.ReadFile(tmpDir + "/test")
  368. if err != nil {
  369. t.Fatal(err)
  370. }
  371. if !bytes.Equal(fb, fb2) {
  372. t.Fatalf("Expected copied file to be duplicate of bind-mounted file")
  373. }
  374. // Copy bind-mounted file
  375. _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/test", outDir)
  376. fb, err = ioutil.ReadFile(outDir + "/test")
  377. if err != nil {
  378. t.Fatal(err)
  379. }
  380. fb2, err = ioutil.ReadFile(tmpDir + "/test")
  381. if err != nil {
  382. t.Fatal(err)
  383. }
  384. if !bytes.Equal(fb, fb2) {
  385. t.Fatalf("Expected copied file to be duplicate of bind-mounted file")
  386. }
  387. logDone("cp - volume path")
  388. }
  389. func TestCpToDot(t *testing.T) {
  390. out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test")
  391. if err != nil || exitCode != 0 {
  392. t.Fatal("failed to create a container", out, err)
  393. }
  394. cleanedContainerID := stripTrailingCharacters(out)
  395. defer deleteContainer(cleanedContainerID)
  396. out, _, err = dockerCmd(t, "wait", cleanedContainerID)
  397. if err != nil || stripTrailingCharacters(out) != "0" {
  398. t.Fatal("failed to set up container", out, err)
  399. }
  400. tmpdir, err := ioutil.TempDir("", "docker-integration")
  401. if err != nil {
  402. t.Fatal(err)
  403. }
  404. defer os.RemoveAll(tmpdir)
  405. cwd, err := os.Getwd()
  406. if err != nil {
  407. t.Fatal(err)
  408. }
  409. defer os.Chdir(cwd)
  410. if err := os.Chdir(tmpdir); err != nil {
  411. t.Fatal(err)
  412. }
  413. _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/test", ".")
  414. if err != nil {
  415. t.Fatalf("couldn't docker cp to \".\" path: %s", err)
  416. }
  417. content, err := ioutil.ReadFile("./test")
  418. if string(content) != "lololol\n" {
  419. t.Fatalf("Wrong content in copied file %q, should be %q", content, "lololol\n")
  420. }
  421. logDone("cp - to dot path")
  422. }