diff --git a/Dockerfile b/Dockerfile index 9710ebfce5..ff46194188 100644 --- a/Dockerfile +++ b/Dockerfile @@ -198,8 +198,7 @@ COPY contrib/download-frozen-image-v2.sh /go/src/github.com/docker/docker/contri 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 + hello-world:latest@sha256:8be990ef2aeb16dbcb9271ddfe2610fa6658d13f6dfb8bc72074cc1ca36966a7 # see also "hack/make/.ensure-frozen-images" (which needs to be updated any time this list is) # Download man page generator diff --git a/contrib/syscall-test/Dockerfile b/contrib/syscall-test/Dockerfile new file mode 100644 index 0000000000..eb3e92c3a0 --- /dev/null +++ b/contrib/syscall-test/Dockerfile @@ -0,0 +1,15 @@ +FROM debian:jessie + +RUN apt-get update && apt-get install -y \ + gcc \ + libc6-dev \ + --no-install-recommends \ + && rm -rf /var/lib/apt/lists/* + +COPY . /usr/src/ + +WORKDIR /usr/src/ + +RUN gcc -g -Wall -static userns.c -o /usr/bin/userns-test \ + && gcc -g -Wall -static ns.c -o /usr/bin/ns-test \ + && gcc -g -Wall -static acct.c -o /usr/bin/acct-test diff --git a/contrib/syscall-test/acct.c b/contrib/syscall-test/acct.c new file mode 100644 index 0000000000..88ac287966 --- /dev/null +++ b/contrib/syscall-test/acct.c @@ -0,0 +1,16 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + int err = acct("/tmp/t"); + if (err == -1) { + fprintf(stderr, "acct failed: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + exit(EXIT_SUCCESS); +} diff --git a/contrib/syscall-test/ns.c b/contrib/syscall-test/ns.c new file mode 100644 index 0000000000..bc897add9e --- /dev/null +++ b/contrib/syscall-test/ns.c @@ -0,0 +1,63 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STACK_SIZE (1024 * 1024) /* Stack size for cloned child */ + +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_NEWNS | CLONE_NEWPID | SIGCHLD; + + // allocate stack for child + char *stack; /* Start of stack buffer */ + char *child_stack; /* End of stack buffer */ + stack = + mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANON | MAP_STACK, -1, 0); + if (stack == MAP_FAILED) { + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + child_stack = stack + STACK_SIZE; /* Assume stack grows downward */ + + // 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, 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/contrib/userns-test/main.c b/contrib/syscall-test/userns.c similarity index 66% rename from contrib/userns-test/main.c rename to contrib/syscall-test/userns.c index 9f4d93aaab..6ec553fe74 100644 --- a/contrib/userns-test/main.c +++ b/contrib/syscall-test/userns.c @@ -1,17 +1,15 @@ #define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include -#define STACKSIZE (1024*1024) -static char child_stack[STACKSIZE]; +#define STACK_SIZE (1024 * 1024) /* Stack size for cloned child */ struct clone_args { char **argv; @@ -37,10 +35,21 @@ int main(int argc, char **argv) int clone_flags = CLONE_NEWUSER | SIGCHLD; + // allocate stack for child + char *stack; /* Start of stack buffer */ + char *child_stack; /* End of stack buffer */ + stack = + mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANON | MAP_STACK, -1, 0); + if (stack == MAP_FAILED) { + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + child_stack = stack + STACK_SIZE; /* Assume stack grows downward */ + // 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); + pid_t pid = clone(child_exec, child_stack, clone_flags, &args); if (pid < 0) { fprintf(stderr, "clone failed: %s\n", strerror(errno)); exit(EXIT_FAILURE); diff --git a/contrib/userns-test/Dockerfile b/contrib/userns-test/Dockerfile deleted file mode 100644 index 90eb5092ce..0000000000 --- a/contrib/userns-test/Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM debian:jessie -COPY userns-test . -ENTRYPOINT ["./userns-test"] diff --git a/hack/make/.ensure-frozen-images b/hack/make/.ensure-frozen-images index 4977b79437..98acdc82c0 100644 --- a/hack/make/.ensure-frozen-images +++ b/hack/make/.ensure-frozen-images @@ -29,7 +29,6 @@ case "$DOCKER_ENGINE_OSARCH" in busybox:latest debian:jessie hello-world:latest - jess/unshare:latest ) ;; esac diff --git a/hack/make/.ensure-syscall-test b/hack/make/.ensure-syscall-test new file mode 100644 index 0000000000..9b54e0495e --- /dev/null +++ b/hack/make/.ensure-syscall-test @@ -0,0 +1,8 @@ +#!/bin/bash +set -e + +# Build a C binary for cloning a userns for seccomp tests +# and compile it for target daemon +if [ "$DOCKER_ENGINE_GOOS" = "linux" ]; then + docker build -qt syscall-test contrib/syscall-test > /dev/null +fi diff --git a/hack/make/.ensure-userns-test b/hack/make/.ensure-userns-test deleted file mode 100644 index a43a76e6f8..0000000000 --- a/hack/make/.ensure-userns-test +++ /dev/null @@ -1,17 +0,0 @@ -#!/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" -( - if [ "$(go env 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 a455abcdcd..4703685ca4 100644 --- a/hack/make/.integration-daemon-setup +++ b/hack/make/.integration-daemon-setup @@ -10,4 +10,4 @@ export DOCKER_ENGINE_GOARCH=$(echo $DOCKER_ENGINE_OSARCH | cut -d'/' -f2) bundle .ensure-emptyfs bundle .ensure-frozen-images bundle .ensure-httpserver -bundle .ensure-userns-test +bundle .ensure-syscall-test diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index f2eabf23c0..edc331dc05 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -2858,26 +2858,28 @@ func (s *DockerSuite) TestRunUnshareProc(c *check.C) { testRequires(c, Apparmor, DaemonIsLinux, NotUserNamespace) name := "acidburn" - out, _, err := dockerCmdWithError("run", "--name", name, "--security-opt", "seccomp:unconfined", "jess/unshare", "unshare", "-p", "-m", "-f", "-r", "--mount-proc=/proc", "mount") + out, _, err := dockerCmdWithError("run", "--name", name, "--security-opt", "seccomp:unconfined", "debian:jessie", "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")) { - c.Fatalf("unshare with --mount-proc should have failed with permission denied, got: %s, %v", out, err) + c.Fatalf("unshare with --mount-proc should have failed with 'permission denied' or 'operation not permitted', got: %s, %v", out, err) } name = "cereal" - out, _, err = dockerCmdWithError("run", "--name", name, "--security-opt", "seccomp:unconfined", "jess/unshare", "unshare", "-p", "-m", "-f", "-r", "mount", "-t", "proc", "none", "/proc") + out, _, err = dockerCmdWithError("run", "--name", name, "--security-opt", "seccomp:unconfined", "debian:jessie", "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("unshare and mount of /proc should have failed with permission denied, got: %s, %v", out, err) + !(strings.Contains(strings.ToLower(out), "mount: cannot mount none") || + strings.Contains(strings.ToLower(out), "permission denied")) { + c.Fatalf("unshare and mount of /proc should have failed with 'mount: cannot mount none' or 'permission denied', got: %s, %v", out, err) } /* Ensure still fails if running privileged with the default policy */ name = "crashoverride" - 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) + out, _, err = dockerCmdWithError("run", "--privileged", "--security-opt", "seccomp:unconfined", "--security-opt", "apparmor:docker-default", "--name", name, "debian:jessie", "unshare", "-p", "-m", "-f", "-r", "mount", "-t", "proc", "none", "/proc") + if err == nil || + !(strings.Contains(strings.ToLower(out), "mount: cannot mount none") || + strings.Contains(strings.ToLower(out), "permission denied")) { + c.Fatalf("privileged unshare with apparmor should have failed with 'mount: cannot mount none' or '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 0075e89b0c..1f98cf3042 100644 --- a/integration-cli/docker_cli_run_unix_test.go +++ b/integration-cli/docker_cli_run_unix_test.go @@ -724,7 +724,7 @@ func (s *DockerSuite) TestRunTmpfsMounts(c *check.C) { } } -// TestRunSeccompProfileDenyUnshare checks that 'docker run --security-opt seccomp:/tmp/profile.json jess/unshare unshare' exits with operation not permitted. +// TestRunSeccompProfileDenyUnshare checks that 'docker run --security-opt seccomp:/tmp/profile.json debian:jessie unshare' exits with operation not permitted. func (s *DockerSuite) TestRunSeccompProfileDenyUnshare(c *check.C) { testRequires(c, SameHostDaemon, seccompEnabled) jsonData := `{ @@ -780,7 +780,7 @@ func (s *DockerSuite) TestRunSeccompProfileDenyChmod(c *check.C) { } } -// TestRunSeccompProfileDenyUnshareUserns checks that 'docker run jess/unshare unshare --map-root-user --user sh -c whoami' with a specific profile to +// TestRunSeccompProfileDenyUnshareUserns checks that 'docker run debian:jessie 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) @@ -817,12 +817,12 @@ func (s *DockerSuite) TestRunSeccompProfileDenyUnshareUserns(c *check.C) { } } -// TestRunSeccompProfileDenyCloneUserns checks that 'docker run userns-test' +// TestRunSeccompProfileDenyCloneUserns checks that 'docker run syscall-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") + runCmd := exec.Command(dockerBinary, "run", "syscall-test", "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) @@ -830,24 +830,24 @@ func (s *DockerSuite) TestRunSeccompProfileDenyCloneUserns(c *check.C) { } // TestRunSeccompUnconfinedCloneUserns checks that -// 'docker run --security-opt seccomp:unconfined userns-test' allows creating a userns. +// 'docker run --security-opt seccomp:unconfined syscall-test' allows creating a userns. func (s *DockerSuite) TestRunSeccompUnconfinedCloneUserns(c *check.C) { testRequires(c, SameHostDaemon, seccompEnabled, NotUserNamespace) // make sure running w privileged is ok - runCmd := exec.Command(dockerBinary, "run", "--security-opt", "seccomp:unconfined", "userns-test", "id") + runCmd := exec.Command(dockerBinary, "run", "--security-opt", "seccomp:unconfined", "syscall-test", "userns-test", "id") if out, _, err := runCommandWithOutput(runCmd); err != nil || !strings.Contains(out, "nobody") { c.Fatalf("expected clone userns with --security-opt seccomp:unconfined to succeed, got %s: %v", out, err) } } -// TestRunSeccompAllowPrivCloneUserns checks that 'docker run --privileged userns-test' +// TestRunSeccompAllowPrivCloneUserns checks that 'docker run --privileged syscall-test' // allows creating a userns. 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") + runCmd := exec.Command(dockerBinary, "run", "--privileged", "syscall-test", "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) } @@ -855,7 +855,7 @@ func (s *DockerSuite) TestRunSeccompAllowPrivCloneUserns(c *check.C) { // TestRunSeccompAllowAptKey checks that 'docker run debian:jessie apt-key' succeeds. func (s *DockerSuite) TestRunSeccompAllowAptKey(c *check.C) { - testRequires(c, SameHostDaemon, seccompEnabled) + testRequires(c, SameHostDaemon, seccompEnabled, Network) // 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") @@ -863,3 +863,27 @@ func (s *DockerSuite) TestRunSeccompAllowAptKey(c *check.C) { c.Fatalf("expected apt-key with seccomp to succeed, got %s: %v", out, err) } } + +func (s *DockerSuite) TestRunSeccompDefaultProfile(c *check.C) { + testRequires(c, SameHostDaemon, seccompEnabled, NotUserNamespace) + + out, _, err := dockerCmdWithError("run", "--cap-add", "ALL", "syscall-test", "acct-test") + if err == nil || !strings.Contains(out, "Operation not permitted") { + c.Fatalf("expected Operation not permitted, got: %s", out) + } + + out, _, err = dockerCmdWithError("run", "--cap-add", "ALL", "syscall-test", "ns-test", "echo", "hello") + if err == nil || !strings.Contains(out, "Operation not permitted") { + c.Fatalf("expected Operation not permitted, got: %s", out) + } + + out, _, err = dockerCmdWithError("run", "--cap-add", "ALL", "--security-opt", "seccomp:unconfined", "syscall-test", "acct-test") + if err == nil || !strings.Contains(out, "No such file or directory") { + c.Fatalf("expected No such file or directory, got: %s", out) + } + + out, _, err = dockerCmdWithError("run", "--cap-add", "ALL", "--security-opt", "seccomp:unconfined", "syscall-test", "ns-test", "echo", "hello") + if err != nil || !strings.Contains(out, "hello") { + c.Fatalf("expected hello, got: %s, %v", out, err) + } +}