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