diff --git a/Dockerfile b/Dockerfile index 891d84b57c..f49794751b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -197,6 +197,7 @@ RUN ln -sv $PWD/contrib/completion/bash/docker /etc/bash_completion.d/docker COPY contrib/download-frozen-image-v2.sh /go/src/github.com/docker/docker/contrib/ RUN ./contrib/download-frozen-image-v2.sh /docker-frozen-images \ busybox:latest@sha256:eb3c0d4680f9213ee5f348ea6d39489a1f85a318a2ae09e012c426f78252a6d2 \ + debian:jessie@sha256:24a900d1671b269d6640b4224e7b63801880d8e3cb2bcbfaa10a5dddcf4469ed \ hello-world:latest@sha256:8be990ef2aeb16dbcb9271ddfe2610fa6658d13f6dfb8bc72074cc1ca36966a7 \ jess/unshare:latest@sha256:2e3a8c0591c4690b82d4eba7e5ef8f49f2ddfe9f867f3e865198db9bd1436c5b # see also "hack/make/.ensure-frozen-images" (which needs to be updated any time this list is) diff --git a/contrib/userns-test/Dockerfile b/contrib/userns-test/Dockerfile new file mode 100644 index 0000000000..90eb5092ce --- /dev/null +++ b/contrib/userns-test/Dockerfile @@ -0,0 +1,3 @@ +FROM debian:jessie +COPY userns-test . +ENTRYPOINT ["./userns-test"] diff --git a/contrib/userns-test/main.c b/contrib/userns-test/main.c new file mode 100644 index 0000000000..9f4d93aaab --- /dev/null +++ b/contrib/userns-test/main.c @@ -0,0 +1,54 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STACKSIZE (1024*1024) +static char child_stack[STACKSIZE]; + +struct clone_args { + char **argv; +}; + +// child_exec is the func that will be executed as the result of clone +static int child_exec(void *stuff) +{ + struct clone_args *args = (struct clone_args *)stuff; + if (execvp(args->argv[0], args->argv) != 0) { + fprintf(stderr, "failed to execvp argments %s\n", + strerror(errno)); + exit(-1); + } + // we should never reach here! + exit(EXIT_FAILURE); +} + +int main(int argc, char **argv) +{ + struct clone_args args; + args.argv = &argv[1]; + + int clone_flags = CLONE_NEWUSER | SIGCHLD; + + // the result of this call is that our child_exec will be run in another + // process returning it's pid + pid_t pid = + clone(child_exec, child_stack + STACKSIZE, clone_flags, &args); + if (pid < 0) { + fprintf(stderr, "clone failed: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + // lets wait on our child process here before we, the parent, exits + if (waitpid(pid, NULL, 0) == -1) { + fprintf(stderr, "failed to wait pid %d\n", pid); + exit(EXIT_FAILURE); + } + exit(EXIT_SUCCESS); +} diff --git a/hack/make/.ensure-frozen-images b/hack/make/.ensure-frozen-images index a73c12f06f..eef951a0c2 100644 --- a/hack/make/.ensure-frozen-images +++ b/hack/make/.ensure-frozen-images @@ -27,6 +27,7 @@ case "$DOCKER_ENGINE_OSARCH" in *) images=( busybox:latest + debian:jessie hello-world:latest jess/unshare:latest ) diff --git a/hack/make/.ensure-userns-test b/hack/make/.ensure-userns-test new file mode 100644 index 0000000000..5934236ee6 --- /dev/null +++ b/hack/make/.ensure-userns-test @@ -0,0 +1,18 @@ +#!/bin/bash +set -e + +# Build a C binary for cloning a userns for seccomp tests +# and compile it for target daemon + +dir="$DEST/userns-test" +mkdir -p "$dir" +( + GOOS=${DOCKER_ENGINE_GOOS:="linux"} + if [ "$GOOS" = "linux" ]; then + cd "$dir" + gcc -g -Wall -static ../../../../contrib/userns-test/main.c -o ./userns-test + cp ../../../../contrib/userns-test/Dockerfile . + docker build -qt userns-test . > /dev/null + fi +) +rm -rf "$dir" diff --git a/hack/make/.integration-daemon-setup b/hack/make/.integration-daemon-setup index ab9d45c32c..318489bf3f 100644 --- a/hack/make/.integration-daemon-setup +++ b/hack/make/.integration-daemon-setup @@ -3,3 +3,4 @@ bundle .ensure-emptyfs bundle .ensure-frozen-images bundle .ensure-httpserver +bundle .ensure-userns-test diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index cfa04f4d4c..f2eabf23c0 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -2858,7 +2858,7 @@ func (s *DockerSuite) TestRunUnshareProc(c *check.C) { testRequires(c, Apparmor, DaemonIsLinux, NotUserNamespace) name := "acidburn" - out, _, err := dockerCmdWithError("run", "--name", name, "jess/unshare", "unshare", "-p", "-m", "-f", "-r", "--mount-proc=/proc", "mount") + out, _, err := dockerCmdWithError("run", "--name", name, "--security-opt", "seccomp:unconfined", "jess/unshare", "unshare", "-p", "-m", "-f", "-r", "--mount-proc=/proc", "mount") if err == nil || !(strings.Contains(strings.ToLower(out), "permission denied") || strings.Contains(strings.ToLower(out), "operation not permitted")) { @@ -2866,7 +2866,7 @@ func (s *DockerSuite) TestRunUnshareProc(c *check.C) { } name = "cereal" - out, _, err = dockerCmdWithError("run", "--name", name, "jess/unshare", "unshare", "-p", "-m", "-f", "-r", "mount", "-t", "proc", "none", "/proc") + out, _, err = dockerCmdWithError("run", "--name", name, "--security-opt", "seccomp:unconfined", "jess/unshare", "unshare", "-p", "-m", "-f", "-r", "mount", "-t", "proc", "none", "/proc") if err == nil || !(strings.Contains(strings.ToLower(out), "permission denied") || strings.Contains(strings.ToLower(out), "operation not permitted")) { @@ -2875,7 +2875,7 @@ func (s *DockerSuite) TestRunUnshareProc(c *check.C) { /* Ensure still fails if running privileged with the default policy */ name = "crashoverride" - out, _, err = dockerCmdWithError("run", "--privileged", "--security-opt", "apparmor:docker-default", "--name", name, "jess/unshare", "unshare", "-p", "-m", "-f", "-r", "mount", "-t", "proc", "none", "/proc") + out, _, err = dockerCmdWithError("run", "--privileged", "--security-opt", "seccomp:unconfined", "--security-opt", "apparmor:docker-default", "--name", name, "jess/unshare", "unshare", "-p", "-m", "-f", "-r", "mount", "-t", "proc", "none", "/proc") if err == nil || !(strings.Contains(strings.ToLower(out), "permission denied") || strings.Contains(strings.ToLower(out), "operation not permitted")) { c.Fatalf("privileged unshare with apparmor should have failed with permission denied, got: %s, %v", out, err) } diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go index 1b44cfed64..58e44690a7 100644 --- a/integration-cli/docker_cli_run_unix_test.go +++ b/integration-cli/docker_cli_run_unix_test.go @@ -514,7 +514,7 @@ func (s *DockerSuite) TestRunSeccompProfileDenyUnshare(c *check.C) { if _, err := tmpFile.Write([]byte(jsonData)); err != nil { c.Fatal(err) } - runCmd := exec.Command(dockerBinary, "run", "--security-opt", "seccomp:"+tmpFile.Name(), "jess/unshare", "unshare", "-p", "-m", "-f", "-r", "mount", "-t", "proc", "none", "/proc") + runCmd := exec.Command(dockerBinary, "run", "--security-opt", "apparmor:unconfined", "--security-opt", "seccomp:"+tmpFile.Name(), "debian:jessie", "unshare", "-p", "-m", "-f", "-r", "mount", "-t", "proc", "none", "/proc") out, _, _ := runCommandWithOutput(runCmd) if !strings.Contains(out, "Operation not permitted") { c.Fatalf("expected unshare with seccomp profile denied to fail, got %s", out) @@ -549,8 +549,9 @@ func (s *DockerSuite) TestRunSeccompProfileDenyChmod(c *check.C) { } } -// TestRunSeccompProfileDenyUserns checks that 'docker run jess/unshare unshare --map-root-user --user sh -c whoami' exits with operation not permitted. -func (s *DockerSuite) TestRunSeccompProfileDenyUserns(c *check.C) { +// TestRunSeccompProfileDenyUnshareUserns checks that 'docker run jess/unshare unshare --map-root-user --user sh -c whoami' with a specific profile to +// deny unhare of a userns exits with operation not permitted. +func (s *DockerSuite) TestRunSeccompProfileDenyUnshareUserns(c *check.C) { testRequires(c, SameHostDaemon, seccompEnabled) // from sched.h jsonData := fmt.Sprintf(`{ @@ -578,9 +579,44 @@ func (s *DockerSuite) TestRunSeccompProfileDenyUserns(c *check.C) { if _, err := tmpFile.Write([]byte(jsonData)); err != nil { c.Fatal(err) } - runCmd := exec.Command(dockerBinary, "run", "--security-opt", "seccomp:"+tmpFile.Name(), "jess/unshare", "unshare", "--map-root-user", "--user", "sh", "-c", "whoami") + runCmd := exec.Command(dockerBinary, "run", "--security-opt", "apparmor:unconfined", "--security-opt", "seccomp:"+tmpFile.Name(), "debian:jessie", "unshare", "--map-root-user", "--user", "sh", "-c", "whoami") out, _, _ := runCommandWithOutput(runCmd) if !strings.Contains(out, "Operation not permitted") { c.Fatalf("expected unshare userns with seccomp profile denied to fail, got %s", out) } } + +// TestRunSeccompProfileDenyCloneUserns checks that 'docker run userns-test' +// with a the default seccomp profile exits with operation not permitted. +func (s *DockerSuite) TestRunSeccompProfileDenyCloneUserns(c *check.C) { + testRequires(c, SameHostDaemon, seccompEnabled) + + runCmd := exec.Command(dockerBinary, "run", "userns-test", "id") + out, _, err := runCommandWithOutput(runCmd) + if err == nil || !strings.Contains(out, "clone failed: Operation not permitted") { + c.Fatalf("expected clone userns with default seccomp profile denied to fail, got %s: %v", out, err) + } +} + +// TestRunSeccompAllowPrivCloneUserns checks that 'docker run userns-test' +// with a the default seccomp profile exits with operation not permitted. +func (s *DockerSuite) TestRunSeccompAllowPrivCloneUserns(c *check.C) { + testRequires(c, SameHostDaemon, seccompEnabled, NotUserNamespace) + + // make sure running w privileged is ok + runCmd := exec.Command(dockerBinary, "run", "--privileged", "userns-test", "id") + if out, _, err := runCommandWithOutput(runCmd); err != nil || !strings.Contains(out, "nobody") { + c.Fatalf("expected clone userns with --privileged to succeed, got %s: %v", out, err) + } +} + +// TestRunSeccompAllowAptKey checks that 'docker run debian:jessie apt-key' succeeds. +func (s *DockerSuite) TestRunSeccompAllowAptKey(c *check.C) { + testRequires(c, SameHostDaemon, seccompEnabled) + + // apt-key uses setrlimit & getrlimit, so we want to make sure we don't break it + runCmd := exec.Command(dockerBinary, "run", "debian:jessie", "apt-key", "adv", "--keyserver", "hkp://p80.pool.sks-keyservers.net:80", "--recv-keys", "E871F18B51E0147C77796AC81196BA81F6B0FC61") + if out, _, err := runCommandWithOutput(runCmd); err != nil { + c.Fatalf("expected apt-key with seccomp to succeed, got %s: %v", out, err) + } +}