mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
ef98fe0763
Fixes #1992 Right now when you `docker cp` a path which is in a volume, the cp itself works, however you end up getting files that are in the container's fs rather than the files in the volume (which is not in the container's fs). This makes it so when you `docker cp` a path that is in a volume it follows the volume to the real path on the host. archive.go has been modified so that when you do `docker cp mydata:/foo .`, and /foo is the volume, the outputed folder is called "foo" instead of the volume ID (because we are telling it to tar up `/var/lib/docker/vfs/dir/<some id>` and not "foo", but the user would be expecting "foo", not the ID Signed-off-by: Brian Goff <cpuguy83@gmail.com>
480 lines
12 KiB
Go
480 lines
12 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
const (
|
|
cpTestPathParent = "/some"
|
|
cpTestPath = "/some/path"
|
|
cpTestName = "test"
|
|
cpFullPath = "/some/path/test"
|
|
|
|
cpContainerContents = "holla, i am the container"
|
|
cpHostContents = "hello, i am the host"
|
|
)
|
|
|
|
// Test for #5656
|
|
// Check that garbage paths don't escape the container's rootfs
|
|
func TestCpGarbagePath(t *testing.T) {
|
|
out, exitCode, err := cmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath)
|
|
if err != nil || exitCode != 0 {
|
|
t.Fatal("failed to create a container", out, err)
|
|
}
|
|
|
|
cleanedContainerID := stripTrailingCharacters(out)
|
|
defer deleteContainer(cleanedContainerID)
|
|
|
|
out, _, err = cmd(t, "wait", cleanedContainerID)
|
|
if err != nil || stripTrailingCharacters(out) != "0" {
|
|
t.Fatal("failed to set up container", out, err)
|
|
}
|
|
|
|
if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
hostFile, err := os.Create(cpFullPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer hostFile.Close()
|
|
defer os.RemoveAll(cpTestPathParent)
|
|
|
|
fmt.Fprintf(hostFile, "%s", cpHostContents)
|
|
|
|
tmpdir, err := ioutil.TempDir("", "docker-integration")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
tmpname := filepath.Join(tmpdir, cpTestName)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
path := filepath.Join("../../../../../../../../../../../../", cpFullPath)
|
|
|
|
_, _, err = cmd(t, "cp", cleanedContainerID+":"+path, tmpdir)
|
|
if err != nil {
|
|
t.Fatalf("couldn't copy from garbage path: %s:%s %s", cleanedContainerID, path, err)
|
|
}
|
|
|
|
file, _ := os.Open(tmpname)
|
|
defer file.Close()
|
|
|
|
test, err := ioutil.ReadAll(file)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if string(test) == cpHostContents {
|
|
t.Errorf("output matched host file -- garbage path can escape container rootfs")
|
|
}
|
|
|
|
if string(test) != cpContainerContents {
|
|
t.Errorf("output doesn't match the input for garbage path")
|
|
}
|
|
|
|
logDone("cp - garbage paths relative to container's rootfs")
|
|
}
|
|
|
|
// Check that relative paths are relative to the container's rootfs
|
|
func TestCpRelativePath(t *testing.T) {
|
|
out, exitCode, err := cmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath)
|
|
if err != nil || exitCode != 0 {
|
|
t.Fatal("failed to create a container", out, err)
|
|
}
|
|
|
|
cleanedContainerID := stripTrailingCharacters(out)
|
|
defer deleteContainer(cleanedContainerID)
|
|
|
|
out, _, err = cmd(t, "wait", cleanedContainerID)
|
|
if err != nil || stripTrailingCharacters(out) != "0" {
|
|
t.Fatal("failed to set up container", out, err)
|
|
}
|
|
|
|
if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
hostFile, err := os.Create(cpFullPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer hostFile.Close()
|
|
defer os.RemoveAll(cpTestPathParent)
|
|
|
|
fmt.Fprintf(hostFile, "%s", cpHostContents)
|
|
|
|
tmpdir, err := ioutil.TempDir("", "docker-integration")
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
tmpname := filepath.Join(tmpdir, cpTestName)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
path, _ := filepath.Rel("/", cpFullPath)
|
|
|
|
_, _, err = cmd(t, "cp", cleanedContainerID+":"+path, tmpdir)
|
|
if err != nil {
|
|
t.Fatalf("couldn't copy from relative path: %s:%s %s", cleanedContainerID, path, err)
|
|
}
|
|
|
|
file, _ := os.Open(tmpname)
|
|
defer file.Close()
|
|
|
|
test, err := ioutil.ReadAll(file)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if string(test) == cpHostContents {
|
|
t.Errorf("output matched host file -- relative path can escape container rootfs")
|
|
}
|
|
|
|
if string(test) != cpContainerContents {
|
|
t.Errorf("output doesn't match the input for relative path")
|
|
}
|
|
|
|
logDone("cp - relative paths relative to container's rootfs")
|
|
}
|
|
|
|
// Check that absolute paths are relative to the container's rootfs
|
|
func TestCpAbsolutePath(t *testing.T) {
|
|
out, exitCode, err := cmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath)
|
|
if err != nil || exitCode != 0 {
|
|
t.Fatal("failed to create a container", out, err)
|
|
}
|
|
|
|
cleanedContainerID := stripTrailingCharacters(out)
|
|
defer deleteContainer(cleanedContainerID)
|
|
|
|
out, _, err = cmd(t, "wait", cleanedContainerID)
|
|
if err != nil || stripTrailingCharacters(out) != "0" {
|
|
t.Fatal("failed to set up container", out, err)
|
|
}
|
|
|
|
if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
hostFile, err := os.Create(cpFullPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer hostFile.Close()
|
|
defer os.RemoveAll(cpTestPathParent)
|
|
|
|
fmt.Fprintf(hostFile, "%s", cpHostContents)
|
|
|
|
tmpdir, err := ioutil.TempDir("", "docker-integration")
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
tmpname := filepath.Join(tmpdir, cpTestName)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
path := cpFullPath
|
|
|
|
_, _, err = cmd(t, "cp", cleanedContainerID+":"+path, tmpdir)
|
|
if err != nil {
|
|
t.Fatalf("couldn't copy from absolute path: %s:%s %s", cleanedContainerID, path, err)
|
|
}
|
|
|
|
file, _ := os.Open(tmpname)
|
|
defer file.Close()
|
|
|
|
test, err := ioutil.ReadAll(file)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if string(test) == cpHostContents {
|
|
t.Errorf("output matched host file -- absolute path can escape container rootfs")
|
|
}
|
|
|
|
if string(test) != cpContainerContents {
|
|
t.Errorf("output doesn't match the input for absolute path")
|
|
}
|
|
|
|
logDone("cp - absolute paths relative to container's rootfs")
|
|
}
|
|
|
|
// Test for #5619
|
|
// Check that absolute symlinks are still relative to the container's rootfs
|
|
func TestCpAbsoluteSymlink(t *testing.T) {
|
|
out, exitCode, err := cmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpFullPath+" container_path")
|
|
if err != nil || exitCode != 0 {
|
|
t.Fatal("failed to create a container", out, err)
|
|
}
|
|
|
|
cleanedContainerID := stripTrailingCharacters(out)
|
|
defer deleteContainer(cleanedContainerID)
|
|
|
|
out, _, err = cmd(t, "wait", cleanedContainerID)
|
|
if err != nil || stripTrailingCharacters(out) != "0" {
|
|
t.Fatal("failed to set up container", out, err)
|
|
}
|
|
|
|
if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
hostFile, err := os.Create(cpFullPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer hostFile.Close()
|
|
defer os.RemoveAll(cpTestPathParent)
|
|
|
|
fmt.Fprintf(hostFile, "%s", cpHostContents)
|
|
|
|
tmpdir, err := ioutil.TempDir("", "docker-integration")
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
tmpname := filepath.Join(tmpdir, cpTestName)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
path := filepath.Join("/", "container_path")
|
|
|
|
_, _, err = cmd(t, "cp", cleanedContainerID+":"+path, tmpdir)
|
|
if err != nil {
|
|
t.Fatalf("couldn't copy from absolute path: %s:%s %s", cleanedContainerID, path, err)
|
|
}
|
|
|
|
file, _ := os.Open(tmpname)
|
|
defer file.Close()
|
|
|
|
test, err := ioutil.ReadAll(file)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if string(test) == cpHostContents {
|
|
t.Errorf("output matched host file -- absolute symlink can escape container rootfs")
|
|
}
|
|
|
|
if string(test) != cpContainerContents {
|
|
t.Errorf("output doesn't match the input for absolute symlink")
|
|
}
|
|
|
|
logDone("cp - absolute symlink relative to container's rootfs")
|
|
}
|
|
|
|
// Test for #5619
|
|
// Check that symlinks which are part of the resource path are still relative to the container's rootfs
|
|
func TestCpSymlinkComponent(t *testing.T) {
|
|
out, exitCode, err := cmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpTestPath+" container_path")
|
|
if err != nil || exitCode != 0 {
|
|
t.Fatal("failed to create a container", out, err)
|
|
}
|
|
|
|
cleanedContainerID := stripTrailingCharacters(out)
|
|
defer deleteContainer(cleanedContainerID)
|
|
|
|
out, _, err = cmd(t, "wait", cleanedContainerID)
|
|
if err != nil || stripTrailingCharacters(out) != "0" {
|
|
t.Fatal("failed to set up container", out, err)
|
|
}
|
|
|
|
if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
hostFile, err := os.Create(cpFullPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer hostFile.Close()
|
|
defer os.RemoveAll(cpTestPathParent)
|
|
|
|
fmt.Fprintf(hostFile, "%s", cpHostContents)
|
|
|
|
tmpdir, err := ioutil.TempDir("", "docker-integration")
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
tmpname := filepath.Join(tmpdir, cpTestName)
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
path := filepath.Join("/", "container_path", cpTestName)
|
|
|
|
_, _, err = cmd(t, "cp", cleanedContainerID+":"+path, tmpdir)
|
|
if err != nil {
|
|
t.Fatalf("couldn't copy from symlink path component: %s:%s %s", cleanedContainerID, path, err)
|
|
}
|
|
|
|
file, _ := os.Open(tmpname)
|
|
defer file.Close()
|
|
|
|
test, err := ioutil.ReadAll(file)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if string(test) == cpHostContents {
|
|
t.Errorf("output matched host file -- symlink path component can escape container rootfs")
|
|
}
|
|
|
|
if string(test) != cpContainerContents {
|
|
t.Errorf("output doesn't match the input for symlink path component")
|
|
}
|
|
|
|
logDone("cp - symlink path components relative to container's rootfs")
|
|
}
|
|
|
|
// Check that cp with unprivileged user doesn't return any error
|
|
func TestCpUnprivilegedUser(t *testing.T) {
|
|
out, exitCode, err := cmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch "+cpTestName)
|
|
if err != nil || exitCode != 0 {
|
|
t.Fatal("failed to create a container", out, err)
|
|
}
|
|
|
|
cleanedContainerID := stripTrailingCharacters(out)
|
|
defer deleteContainer(cleanedContainerID)
|
|
|
|
out, _, err = cmd(t, "wait", cleanedContainerID)
|
|
if err != nil || stripTrailingCharacters(out) != "0" {
|
|
t.Fatal("failed to set up container", out, err)
|
|
}
|
|
|
|
tmpdir, err := ioutil.TempDir("", "docker-integration")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
if err = os.Chmod(tmpdir, 0777); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
path := cpTestName
|
|
|
|
_, _, err = runCommandWithOutput(exec.Command("su", "unprivilegeduser", "-c", dockerBinary+" cp "+cleanedContainerID+":"+path+" "+tmpdir))
|
|
if err != nil {
|
|
t.Fatalf("couldn't copy with unprivileged user: %s:%s %s", cleanedContainerID, path, err)
|
|
}
|
|
|
|
logDone("cp - unprivileged user")
|
|
}
|
|
|
|
func TestCpVolumePath(t *testing.T) {
|
|
tmpDir, err := ioutil.TempDir("", "cp-test-volumepath")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
outDir, err := ioutil.TempDir("", "cp-test-volumepath-out")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(outDir)
|
|
_, err = os.Create(tmpDir + "/test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
out, exitCode, err := cmd(t, "run", "-d", "-v", "/foo", "-v", tmpDir+"/test:/test", "-v", tmpDir+":/baz", "busybox", "/bin/sh", "-c", "touch /foo/bar")
|
|
if err != nil || exitCode != 0 {
|
|
t.Fatal("failed to create a container", out, err)
|
|
}
|
|
|
|
cleanedContainerID := stripTrailingCharacters(out)
|
|
defer deleteContainer(cleanedContainerID)
|
|
|
|
out, _, err = cmd(t, "wait", cleanedContainerID)
|
|
if err != nil || stripTrailingCharacters(out) != "0" {
|
|
t.Fatal("failed to set up container", out, err)
|
|
}
|
|
|
|
// Copy actual volume path
|
|
_, _, err = cmd(t, "cp", cleanedContainerID+":/foo", outDir)
|
|
if err != nil {
|
|
t.Fatalf("couldn't copy from volume path: %s:%s %v", cleanedContainerID, "/foo", err)
|
|
}
|
|
stat, err := os.Stat(outDir + "/foo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !stat.IsDir() {
|
|
t.Fatal("expected copied content to be dir")
|
|
}
|
|
stat, err = os.Stat(outDir + "/foo/bar")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if stat.IsDir() {
|
|
t.Fatal("Expected file `bar` to be a file")
|
|
}
|
|
|
|
// Copy file nested in volume
|
|
_, _, err = cmd(t, "cp", cleanedContainerID+":/foo/bar", outDir)
|
|
if err != nil {
|
|
t.Fatalf("couldn't copy from volume path: %s:%s %v", cleanedContainerID, "/foo", err)
|
|
}
|
|
stat, err = os.Stat(outDir + "/bar")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if stat.IsDir() {
|
|
t.Fatal("Expected file `bar` to be a file")
|
|
}
|
|
|
|
// Copy Bind-mounted dir
|
|
_, _, err = cmd(t, "cp", cleanedContainerID+":/baz", outDir)
|
|
if err != nil {
|
|
t.Fatalf("couldn't copy from bind-mounted volume path: %s:%s %v", cleanedContainerID, "/baz", err)
|
|
}
|
|
stat, err = os.Stat(outDir + "/baz")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !stat.IsDir() {
|
|
t.Fatal("Expected `baz` to be a dir")
|
|
}
|
|
|
|
// Copy file nested in bind-mounted dir
|
|
_, _, err = cmd(t, "cp", cleanedContainerID+":/baz/test", outDir)
|
|
fb, err := ioutil.ReadFile(outDir + "/baz/test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fb2, err := ioutil.ReadFile(tmpDir + "/test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !bytes.Equal(fb, fb2) {
|
|
t.Fatalf("Expected copied file to be duplicate of bind-mounted file")
|
|
}
|
|
|
|
// Copy bind-mounted file
|
|
_, _, err = cmd(t, "cp", cleanedContainerID+":/test", outDir)
|
|
fb, err = ioutil.ReadFile(outDir + "/test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fb2, err = ioutil.ReadFile(tmpDir + "/test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !bytes.Equal(fb, fb2) {
|
|
t.Fatalf("Expected copied file to be duplicate of bind-mounted file")
|
|
}
|
|
|
|
logDone("cp - volume path")
|
|
}
|