diff --git a/Dockerfile b/Dockerfile index 183ec89dbd..41f0f92947 100644 --- a/Dockerfile +++ b/Dockerfile @@ -91,6 +91,8 @@ RUN git config --global user.email 'docker-dummy@example.com' # Add an unprivileged user to be used for tests which need it RUN adduser unprivilegeduser +RUN groupadd docker +RUN gpasswd -a unprivilegeduser docker VOLUME /var/lib/docker WORKDIR /go/src/github.com/dotcloud/docker diff --git a/api/client/commands.go b/api/client/commands.go index 6562709f3e..28c1a9c515 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -2058,7 +2058,7 @@ func (cli *DockerCli) CmdCp(args ...string) error { } if statusCode == 200 { - if err := archive.Untar(stream, copyData.Get("HostPath"), nil); err != nil { + if err := archive.Untar(stream, copyData.Get("HostPath"), &archive.TarOptions{NoLchown: true}); err != nil { return err } } diff --git a/archive/archive.go b/archive/archive.go index 160ea0737e..76c6e31289 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -28,6 +28,7 @@ type ( TarOptions struct { Includes []string Compression Compression + NoLchown bool } ) @@ -179,7 +180,7 @@ func addTarFile(path, name string, tw *tar.Writer) error { return nil } -func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader) error { +func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, Lchown bool) error { // hdr.Mode is in linux format, which we can use for sycalls, // but for os.Foo() calls we need the mode converted to os.FileMode, // so use hdrInfo.Mode() (they differ for e.g. setuid bits) @@ -240,7 +241,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader) e return fmt.Errorf("Unhandled tar header type %d\n", hdr.Typeflag) } - if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil { + if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil && Lchown { return err } @@ -415,8 +416,7 @@ func Untar(archive io.Reader, dest string, options *TarOptions) error { } } } - - if err := createTarFile(path, dest, hdr, tr); err != nil { + if err := createTarFile(path, dest, hdr, tr, options == nil || !options.NoLchown); err != nil { return err } diff --git a/archive/archive_test.go b/archive/archive_test.go index e959a2b073..72ffd99565 100644 --- a/archive/archive_test.go +++ b/archive/archive_test.go @@ -3,7 +3,6 @@ package archive import ( "bytes" "fmt" - "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" "io" "io/ioutil" "os" @@ -11,6 +10,8 @@ import ( "path" "testing" "time" + + "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) func TestCmdStreamLargeStderr(t *testing.T) { @@ -132,7 +133,7 @@ func TestTarUntar(t *testing.T) { // Failing prevents the archives from being uncompressed during ADD func TestTypeXGlobalHeaderDoesNotFail(t *testing.T) { hdr := tar.Header{Typeflag: tar.TypeXGlobalHeader} - err := createTarFile("pax_global_header", "some_dir", &hdr, nil) + err := createTarFile("pax_global_header", "some_dir", &hdr, nil, true) if err != nil { t.Fatal(err) } diff --git a/archive/diff.go b/archive/diff.go index 49d8cb4984..d169669126 100644 --- a/archive/diff.go +++ b/archive/diff.go @@ -2,13 +2,14 @@ package archive import ( "fmt" - "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" "io" "io/ioutil" "os" "path/filepath" "strings" "syscall" + + "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) // Linux device nodes are a bit weird due to backwards compat with 16 bit device nodes. @@ -79,7 +80,7 @@ func ApplyLayer(dest string, layer ArchiveReader) error { } defer os.RemoveAll(aufsTempdir) } - if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr); err != nil { + if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr, true); err != nil { return err } } @@ -126,7 +127,7 @@ func ApplyLayer(dest string, layer ArchiveReader) error { srcData = tmpFile } - if err := createTarFile(path, dest, srcHdr, srcData); err != nil { + if err := createTarFile(path, dest, srcHdr, srcData, true); err != nil { return err } diff --git a/integration-cli/docker_cli_cp_test.go b/integration-cli/docker_cli_cp_test.go index b5a70a45ed..7421ed0fa1 100644 --- a/integration-cli/docker_cli_cp_test.go +++ b/integration-cli/docker_cli_cp_test.go @@ -4,6 +4,7 @@ import ( "fmt" "io/ioutil" "os" + "os/exec" "path/filepath" "testing" ) @@ -206,3 +207,39 @@ func TestCpAbsolutePath(t *testing.T) { logDone("cp - absolute paths 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") +}