github.com/lalyos/docker@v1.14.0-dev/integration-cli/docker_cli_cp_test.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "os/exec" 9 "path" 10 "path/filepath" 11 "strings" 12 13 "github.com/go-check/check" 14 ) 15 16 const ( 17 cpTestPathParent = "/some" 18 cpTestPath = "/some/path" 19 cpTestName = "test" 20 cpFullPath = "/some/path/test" 21 22 cpContainerContents = "holla, i am the container" 23 cpHostContents = "hello, i am the host" 24 ) 25 26 // Test for #5656 27 // Check that garbage paths don't escape the container's rootfs 28 func (s *DockerSuite) TestCpGarbagePath(c *check.C) { 29 out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) 30 if exitCode != 0 { 31 c.Fatal("failed to create a container", out) 32 } 33 34 cleanedContainerID := strings.TrimSpace(out) 35 36 out, _ = dockerCmd(c, "wait", cleanedContainerID) 37 if strings.TrimSpace(out) != "0" { 38 c.Fatal("failed to set up container", out) 39 } 40 41 if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { 42 c.Fatal(err) 43 } 44 45 hostFile, err := os.Create(cpFullPath) 46 if err != nil { 47 c.Fatal(err) 48 } 49 defer hostFile.Close() 50 defer os.RemoveAll(cpTestPathParent) 51 52 fmt.Fprintf(hostFile, "%s", cpHostContents) 53 54 tmpdir, err := ioutil.TempDir("", "docker-integration") 55 if err != nil { 56 c.Fatal(err) 57 } 58 59 tmpname := filepath.Join(tmpdir, cpTestName) 60 defer os.RemoveAll(tmpdir) 61 62 path := path.Join("../../../../../../../../../../../../", cpFullPath) 63 64 _, _ = dockerCmd(c, "cp", cleanedContainerID+":"+path, tmpdir) 65 66 file, _ := os.Open(tmpname) 67 defer file.Close() 68 69 test, err := ioutil.ReadAll(file) 70 if err != nil { 71 c.Fatal(err) 72 } 73 74 if string(test) == cpHostContents { 75 c.Errorf("output matched host file -- garbage path can escape container rootfs") 76 } 77 78 if string(test) != cpContainerContents { 79 c.Errorf("output doesn't match the input for garbage path") 80 } 81 82 } 83 84 // Check that relative paths are relative to the container's rootfs 85 func (s *DockerSuite) TestCpRelativePath(c *check.C) { 86 out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) 87 if exitCode != 0 { 88 c.Fatal("failed to create a container", out) 89 } 90 91 cleanedContainerID := strings.TrimSpace(out) 92 93 out, _ = dockerCmd(c, "wait", cleanedContainerID) 94 if strings.TrimSpace(out) != "0" { 95 c.Fatal("failed to set up container", out) 96 } 97 98 if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { 99 c.Fatal(err) 100 } 101 102 hostFile, err := os.Create(cpFullPath) 103 if err != nil { 104 c.Fatal(err) 105 } 106 defer hostFile.Close() 107 defer os.RemoveAll(cpTestPathParent) 108 109 fmt.Fprintf(hostFile, "%s", cpHostContents) 110 111 tmpdir, err := ioutil.TempDir("", "docker-integration") 112 113 if err != nil { 114 c.Fatal(err) 115 } 116 117 tmpname := filepath.Join(tmpdir, cpTestName) 118 defer os.RemoveAll(tmpdir) 119 120 var relPath string 121 if path.IsAbs(cpFullPath) { 122 // normally this is `filepath.Rel("/", cpFullPath)` but we cannot 123 // get this unix-path manipulation on windows with filepath. 124 relPath = cpFullPath[1:] 125 } else { 126 c.Fatalf("path %s was assumed to be an absolute path", cpFullPath) 127 } 128 129 _, _ = dockerCmd(c, "cp", cleanedContainerID+":"+relPath, tmpdir) 130 131 file, _ := os.Open(tmpname) 132 defer file.Close() 133 134 test, err := ioutil.ReadAll(file) 135 if err != nil { 136 c.Fatal(err) 137 } 138 139 if string(test) == cpHostContents { 140 c.Errorf("output matched host file -- relative path can escape container rootfs") 141 } 142 143 if string(test) != cpContainerContents { 144 c.Errorf("output doesn't match the input for relative path") 145 } 146 147 } 148 149 // Check that absolute paths are relative to the container's rootfs 150 func (s *DockerSuite) TestCpAbsolutePath(c *check.C) { 151 out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) 152 if exitCode != 0 { 153 c.Fatal("failed to create a container", out) 154 } 155 156 cleanedContainerID := strings.TrimSpace(out) 157 158 out, _ = dockerCmd(c, "wait", cleanedContainerID) 159 if strings.TrimSpace(out) != "0" { 160 c.Fatal("failed to set up container", out) 161 } 162 163 if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { 164 c.Fatal(err) 165 } 166 167 hostFile, err := os.Create(cpFullPath) 168 if err != nil { 169 c.Fatal(err) 170 } 171 defer hostFile.Close() 172 defer os.RemoveAll(cpTestPathParent) 173 174 fmt.Fprintf(hostFile, "%s", cpHostContents) 175 176 tmpdir, err := ioutil.TempDir("", "docker-integration") 177 178 if err != nil { 179 c.Fatal(err) 180 } 181 182 tmpname := filepath.Join(tmpdir, cpTestName) 183 defer os.RemoveAll(tmpdir) 184 185 path := cpFullPath 186 187 _, _ = dockerCmd(c, "cp", cleanedContainerID+":"+path, tmpdir) 188 189 file, _ := os.Open(tmpname) 190 defer file.Close() 191 192 test, err := ioutil.ReadAll(file) 193 if err != nil { 194 c.Fatal(err) 195 } 196 197 if string(test) == cpHostContents { 198 c.Errorf("output matched host file -- absolute path can escape container rootfs") 199 } 200 201 if string(test) != cpContainerContents { 202 c.Errorf("output doesn't match the input for absolute path") 203 } 204 205 } 206 207 // Test for #5619 208 // Check that absolute symlinks are still relative to the container's rootfs 209 func (s *DockerSuite) TestCpAbsoluteSymlink(c *check.C) { 210 out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpFullPath+" container_path") 211 if exitCode != 0 { 212 c.Fatal("failed to create a container", out) 213 } 214 215 cleanedContainerID := strings.TrimSpace(out) 216 217 out, _ = dockerCmd(c, "wait", cleanedContainerID) 218 if strings.TrimSpace(out) != "0" { 219 c.Fatal("failed to set up container", out) 220 } 221 222 if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { 223 c.Fatal(err) 224 } 225 226 hostFile, err := os.Create(cpFullPath) 227 if err != nil { 228 c.Fatal(err) 229 } 230 defer hostFile.Close() 231 defer os.RemoveAll(cpTestPathParent) 232 233 fmt.Fprintf(hostFile, "%s", cpHostContents) 234 235 tmpdir, err := ioutil.TempDir("", "docker-integration") 236 237 if err != nil { 238 c.Fatal(err) 239 } 240 241 tmpname := filepath.Join(tmpdir, cpTestName) 242 defer os.RemoveAll(tmpdir) 243 244 path := path.Join("/", "container_path") 245 246 _, _ = dockerCmd(c, "cp", cleanedContainerID+":"+path, tmpdir) 247 248 file, _ := os.Open(tmpname) 249 defer file.Close() 250 251 test, err := ioutil.ReadAll(file) 252 if err != nil { 253 c.Fatal(err) 254 } 255 256 if string(test) == cpHostContents { 257 c.Errorf("output matched host file -- absolute symlink can escape container rootfs") 258 } 259 260 if string(test) != cpContainerContents { 261 c.Errorf("output doesn't match the input for absolute symlink") 262 } 263 264 } 265 266 // Test for #5619 267 // Check that symlinks which are part of the resource path are still relative to the container's rootfs 268 func (s *DockerSuite) TestCpSymlinkComponent(c *check.C) { 269 out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpTestPath+" container_path") 270 if exitCode != 0 { 271 c.Fatal("failed to create a container", out) 272 } 273 274 cleanedContainerID := strings.TrimSpace(out) 275 276 out, _ = dockerCmd(c, "wait", cleanedContainerID) 277 if strings.TrimSpace(out) != "0" { 278 c.Fatal("failed to set up container", out) 279 } 280 281 if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { 282 c.Fatal(err) 283 } 284 285 hostFile, err := os.Create(cpFullPath) 286 if err != nil { 287 c.Fatal(err) 288 } 289 defer hostFile.Close() 290 defer os.RemoveAll(cpTestPathParent) 291 292 fmt.Fprintf(hostFile, "%s", cpHostContents) 293 294 tmpdir, err := ioutil.TempDir("", "docker-integration") 295 296 if err != nil { 297 c.Fatal(err) 298 } 299 300 tmpname := filepath.Join(tmpdir, cpTestName) 301 defer os.RemoveAll(tmpdir) 302 303 path := path.Join("/", "container_path", cpTestName) 304 305 _, _ = dockerCmd(c, "cp", cleanedContainerID+":"+path, tmpdir) 306 307 file, _ := os.Open(tmpname) 308 defer file.Close() 309 310 test, err := ioutil.ReadAll(file) 311 if err != nil { 312 c.Fatal(err) 313 } 314 315 if string(test) == cpHostContents { 316 c.Errorf("output matched host file -- symlink path component can escape container rootfs") 317 } 318 319 if string(test) != cpContainerContents { 320 c.Errorf("output doesn't match the input for symlink path component") 321 } 322 323 } 324 325 // Check that cp with unprivileged user doesn't return any error 326 func (s *DockerSuite) TestCpUnprivilegedUser(c *check.C) { 327 testRequires(c, UnixCli) // uses chmod/su: not available on windows 328 329 out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "touch "+cpTestName) 330 if exitCode != 0 { 331 c.Fatal("failed to create a container", out) 332 } 333 334 cleanedContainerID := strings.TrimSpace(out) 335 336 out, _ = dockerCmd(c, "wait", cleanedContainerID) 337 if strings.TrimSpace(out) != "0" { 338 c.Fatal("failed to set up container", out) 339 } 340 341 tmpdir, err := ioutil.TempDir("", "docker-integration") 342 if err != nil { 343 c.Fatal(err) 344 } 345 346 defer os.RemoveAll(tmpdir) 347 348 if err = os.Chmod(tmpdir, 0777); err != nil { 349 c.Fatal(err) 350 } 351 352 path := cpTestName 353 354 _, _, err = runCommandWithOutput(exec.Command("su", "unprivilegeduser", "-c", dockerBinary+" cp "+cleanedContainerID+":"+path+" "+tmpdir)) 355 if err != nil { 356 c.Fatalf("couldn't copy with unprivileged user: %s:%s %s", cleanedContainerID, path, err) 357 } 358 359 } 360 361 func (s *DockerSuite) TestCpSpecialFiles(c *check.C) { 362 testRequires(c, SameHostDaemon) 363 364 outDir, err := ioutil.TempDir("", "cp-test-special-files") 365 if err != nil { 366 c.Fatal(err) 367 } 368 defer os.RemoveAll(outDir) 369 370 out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "touch /foo") 371 if exitCode != 0 { 372 c.Fatal("failed to create a container", out) 373 } 374 375 cleanedContainerID := strings.TrimSpace(out) 376 377 out, _ = dockerCmd(c, "wait", cleanedContainerID) 378 if strings.TrimSpace(out) != "0" { 379 c.Fatal("failed to set up container", out) 380 } 381 382 // Copy actual /etc/resolv.conf 383 _, _ = dockerCmd(c, "cp", cleanedContainerID+":/etc/resolv.conf", outDir) 384 385 expected, err := ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/resolv.conf") 386 actual, err := ioutil.ReadFile(outDir + "/resolv.conf") 387 388 if !bytes.Equal(actual, expected) { 389 c.Fatalf("Expected copied file to be duplicate of the container resolvconf") 390 } 391 392 // Copy actual /etc/hosts 393 _, _ = dockerCmd(c, "cp", cleanedContainerID+":/etc/hosts", outDir) 394 395 expected, err = ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/hosts") 396 actual, err = ioutil.ReadFile(outDir + "/hosts") 397 398 if !bytes.Equal(actual, expected) { 399 c.Fatalf("Expected copied file to be duplicate of the container hosts") 400 } 401 402 // Copy actual /etc/resolv.conf 403 _, _ = dockerCmd(c, "cp", cleanedContainerID+":/etc/hostname", outDir) 404 405 expected, err = ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/hostname") 406 actual, err = ioutil.ReadFile(outDir + "/hostname") 407 408 if !bytes.Equal(actual, expected) { 409 c.Fatalf("Expected copied file to be duplicate of the container resolvconf") 410 } 411 412 } 413 414 func (s *DockerSuite) TestCpVolumePath(c *check.C) { 415 testRequires(c, SameHostDaemon) 416 417 tmpDir, err := ioutil.TempDir("", "cp-test-volumepath") 418 if err != nil { 419 c.Fatal(err) 420 } 421 defer os.RemoveAll(tmpDir) 422 outDir, err := ioutil.TempDir("", "cp-test-volumepath-out") 423 if err != nil { 424 c.Fatal(err) 425 } 426 defer os.RemoveAll(outDir) 427 _, err = os.Create(tmpDir + "/test") 428 if err != nil { 429 c.Fatal(err) 430 } 431 432 out, exitCode := dockerCmd(c, "run", "-d", "-v", "/foo", "-v", tmpDir+"/test:/test", "-v", tmpDir+":/baz", "busybox", "/bin/sh", "-c", "touch /foo/bar") 433 if exitCode != 0 { 434 c.Fatal("failed to create a container", out) 435 } 436 437 cleanedContainerID := strings.TrimSpace(out) 438 defer dockerCmd(c, "rm", "-fv", cleanedContainerID) 439 440 out, _ = dockerCmd(c, "wait", cleanedContainerID) 441 if strings.TrimSpace(out) != "0" { 442 c.Fatal("failed to set up container", out) 443 } 444 445 // Copy actual volume path 446 _, _ = dockerCmd(c, "cp", cleanedContainerID+":/foo", outDir) 447 448 stat, err := os.Stat(outDir + "/foo") 449 if err != nil { 450 c.Fatal(err) 451 } 452 if !stat.IsDir() { 453 c.Fatal("expected copied content to be dir") 454 } 455 stat, err = os.Stat(outDir + "/foo/bar") 456 if err != nil { 457 c.Fatal(err) 458 } 459 if stat.IsDir() { 460 c.Fatal("Expected file `bar` to be a file") 461 } 462 463 // Copy file nested in volume 464 _, _ = dockerCmd(c, "cp", cleanedContainerID+":/foo/bar", outDir) 465 466 stat, err = os.Stat(outDir + "/bar") 467 if err != nil { 468 c.Fatal(err) 469 } 470 if stat.IsDir() { 471 c.Fatal("Expected file `bar` to be a file") 472 } 473 474 // Copy Bind-mounted dir 475 _, _ = dockerCmd(c, "cp", cleanedContainerID+":/baz", outDir) 476 stat, err = os.Stat(outDir + "/baz") 477 if err != nil { 478 c.Fatal(err) 479 } 480 if !stat.IsDir() { 481 c.Fatal("Expected `baz` to be a dir") 482 } 483 484 // Copy file nested in bind-mounted dir 485 _, _ = dockerCmd(c, "cp", cleanedContainerID+":/baz/test", outDir) 486 fb, err := ioutil.ReadFile(outDir + "/baz/test") 487 if err != nil { 488 c.Fatal(err) 489 } 490 fb2, err := ioutil.ReadFile(tmpDir + "/test") 491 if err != nil { 492 c.Fatal(err) 493 } 494 if !bytes.Equal(fb, fb2) { 495 c.Fatalf("Expected copied file to be duplicate of bind-mounted file") 496 } 497 498 // Copy bind-mounted file 499 _, _ = dockerCmd(c, "cp", cleanedContainerID+":/test", outDir) 500 fb, err = ioutil.ReadFile(outDir + "/test") 501 if err != nil { 502 c.Fatal(err) 503 } 504 fb2, err = ioutil.ReadFile(tmpDir + "/test") 505 if err != nil { 506 c.Fatal(err) 507 } 508 if !bytes.Equal(fb, fb2) { 509 c.Fatalf("Expected copied file to be duplicate of bind-mounted file") 510 } 511 512 } 513 514 func (s *DockerSuite) TestCpToDot(c *check.C) { 515 out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test") 516 if exitCode != 0 { 517 c.Fatal("failed to create a container", out) 518 } 519 520 cleanedContainerID := strings.TrimSpace(out) 521 522 out, _ = dockerCmd(c, "wait", cleanedContainerID) 523 if strings.TrimSpace(out) != "0" { 524 c.Fatal("failed to set up container", out) 525 } 526 527 tmpdir, err := ioutil.TempDir("", "docker-integration") 528 if err != nil { 529 c.Fatal(err) 530 } 531 defer os.RemoveAll(tmpdir) 532 cwd, err := os.Getwd() 533 if err != nil { 534 c.Fatal(err) 535 } 536 defer os.Chdir(cwd) 537 if err := os.Chdir(tmpdir); err != nil { 538 c.Fatal(err) 539 } 540 _, _ = dockerCmd(c, "cp", cleanedContainerID+":/test", ".") 541 content, err := ioutil.ReadFile("./test") 542 if string(content) != "lololol\n" { 543 c.Fatalf("Wrong content in copied file %q, should be %q", content, "lololol\n") 544 } 545 } 546 547 func (s *DockerSuite) TestCpToStdout(c *check.C) { 548 out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test") 549 if exitCode != 0 { 550 c.Fatalf("failed to create a container:%s\n", out) 551 } 552 553 cID := strings.TrimSpace(out) 554 555 out, _ = dockerCmd(c, "wait", cID) 556 if strings.TrimSpace(out) != "0" { 557 c.Fatalf("failed to set up container:%s\n", out) 558 } 559 560 out, _, err := runCommandPipelineWithOutput( 561 exec.Command(dockerBinary, "cp", cID+":/test", "-"), 562 exec.Command("tar", "-vtf", "-")) 563 564 if err != nil { 565 c.Fatalf("Failed to run commands: %s", err) 566 } 567 568 if !strings.Contains(out, "test") || !strings.Contains(out, "-rw") { 569 c.Fatalf("Missing file from tar TOC:\n%s", out) 570 } 571 } 572 573 func (s *DockerSuite) TestCpNameHasColon(c *check.C) { 574 testRequires(c, SameHostDaemon) 575 576 out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /te:s:t") 577 if exitCode != 0 { 578 c.Fatal("failed to create a container", out) 579 } 580 581 cleanedContainerID := strings.TrimSpace(out) 582 583 out, _ = dockerCmd(c, "wait", cleanedContainerID) 584 if strings.TrimSpace(out) != "0" { 585 c.Fatal("failed to set up container", out) 586 } 587 588 tmpdir, err := ioutil.TempDir("", "docker-integration") 589 if err != nil { 590 c.Fatal(err) 591 } 592 defer os.RemoveAll(tmpdir) 593 _, _ = dockerCmd(c, "cp", cleanedContainerID+":/te:s:t", tmpdir) 594 content, err := ioutil.ReadFile(tmpdir + "/te:s:t") 595 if string(content) != "lololol\n" { 596 c.Fatalf("Wrong content in copied file %q, should be %q", content, "lololol\n") 597 } 598 }