From 5c0c9e4587c645efb6ceb8dff26ff5418bf13feb Mon Sep 17 00:00:00 2001 From: Justin Cormack Date: Fri, 21 Oct 2016 13:34:37 +0100 Subject: [PATCH] Test that non root user cannot use default capabilities Test for #27590 Signed-off-by: Justin Cormack --- contrib/syscall-test/Dockerfile | 6 +- contrib/syscall-test/raw.c | 14 ++ contrib/syscall-test/setgid.c | 11 ++ contrib/syscall-test/setuid.c | 11 ++ contrib/syscall-test/socket.c | 30 ++++ hack/make/.ensure-syscall-test | 4 + integration-cli/docker_cli_run_unix_test.go | 170 ++++++++++++++++++++ 7 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 contrib/syscall-test/raw.c create mode 100644 contrib/syscall-test/setgid.c create mode 100644 contrib/syscall-test/setuid.c create mode 100644 contrib/syscall-test/socket.c diff --git a/contrib/syscall-test/Dockerfile b/contrib/syscall-test/Dockerfile index 8cd6bebf3d..ada3a8fc19 100644 --- a/contrib/syscall-test/Dockerfile +++ b/contrib/syscall-test/Dockerfile @@ -6,4 +6,8 @@ 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 + && gcc -g -Wall -static acct.c -o /usr/bin/acct-test \ + && gcc -g -Wall -static setuid.c -o /usr/bin/setuid-test \ + && gcc -g -Wall -static setgid.c -o /usr/bin/setgid-test \ + && gcc -g -Wall -static socket.c -o /usr/bin/socket-test \ + && gcc -g -Wall -static raw.c -o /usr/bin/raw-test diff --git a/contrib/syscall-test/raw.c b/contrib/syscall-test/raw.c new file mode 100644 index 0000000000..7995a0d3a5 --- /dev/null +++ b/contrib/syscall-test/raw.c @@ -0,0 +1,14 @@ +#include +#include +#include +#include +#include + +int main() { + if (socket(PF_INET, SOCK_RAW, IPPROTO_UDP) == -1) { + perror("socket"); + return 1; + } + + return 0; +} diff --git a/contrib/syscall-test/setgid.c b/contrib/syscall-test/setgid.c new file mode 100644 index 0000000000..df9680c869 --- /dev/null +++ b/contrib/syscall-test/setgid.c @@ -0,0 +1,11 @@ +#include +#include +#include + +int main() { + if (setgid(1) == -1) { + perror("setgid"); + return 1; + } + return 0; +} diff --git a/contrib/syscall-test/setuid.c b/contrib/syscall-test/setuid.c new file mode 100644 index 0000000000..5b939677e9 --- /dev/null +++ b/contrib/syscall-test/setuid.c @@ -0,0 +1,11 @@ +#include +#include +#include + +int main() { + if (setuid(1) == -1) { + perror("setuid"); + return 1; + } + return 0; +} diff --git a/contrib/syscall-test/socket.c b/contrib/syscall-test/socket.c new file mode 100644 index 0000000000..d26c82f00f --- /dev/null +++ b/contrib/syscall-test/socket.c @@ -0,0 +1,30 @@ +#include +#include +#include +#include +#include +#include + +int main() { + int s; + struct sockaddr_in sin; + + s = socket(AF_INET, SOCK_STREAM, 0); + if (s == -1) { + perror("socket"); + return 1; + } + + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + sin.sin_port = htons(80); + + if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) == -1) { + perror("bind"); + return 1; + } + + close(s); + + return 0; +} diff --git a/hack/make/.ensure-syscall-test b/hack/make/.ensure-syscall-test index 376fef1cf0..e5e9b53111 100644 --- a/hack/make/.ensure-syscall-test +++ b/hack/make/.ensure-syscall-test @@ -9,6 +9,10 @@ if [ "$DOCKER_ENGINE_GOOS" = "linux" ]; then gcc -g -Wall -static contrib/syscall-test/userns.c -o "${tmpdir}/userns-test" gcc -g -Wall -static contrib/syscall-test/ns.c -o "${tmpdir}/ns-test" gcc -g -Wall -static contrib/syscall-test/acct.c -o "${tmpdir}/acct-test" + gcc -g -Wall -static contrib/syscall-test/setuid.c -o "${tmpdir}/setuid-test" + gcc -g -Wall -static contrib/syscall-test/setgid.c -o "${tmpdir}/setgid-test" + gcc -g -Wall -static contrib/syscall-test/socket.c -o "${tmpdir}/socket-test" + gcc -g -Wall -static contrib/syscall-test/raw.c -o "${tmpdir}/raw-test" dockerfile="${tmpdir}/Dockerfile" cat <<-EOF > "$dockerfile" diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go index b0974f43ad..f27c670877 100644 --- a/integration-cli/docker_cli_run_unix_test.go +++ b/integration-cli/docker_cli_run_unix_test.go @@ -1138,6 +1138,176 @@ func (s *DockerSuite) TestRunNoNewPrivSetuid(c *check.C) { } } +func (s *DockerSuite) TestUserNoEffectiveCapabilitiesChown(c *check.C) { + testRequires(c, DaemonIsLinux) + + // test that a root user has default capability CAP_CHOWN + runCmd := exec.Command(dockerBinary, "run", "busybox", "chown", "100", "/tmp") + _, _, err := runCommandWithOutput(runCmd) + c.Assert(err, check.IsNil) + // test that non root user does not have default capability CAP_CHOWN + runCmd = exec.Command(dockerBinary, "run", "--user", "1000:1000", "busybox", "chown", "100", "/tmp") + out, _, err := runCommandWithOutput(runCmd) + c.Assert(err, checker.NotNil, check.Commentf(out)) + c.Assert(out, checker.Contains, "Operation not permitted") + // test that root user can drop default capability CAP_CHOWN + runCmd = exec.Command(dockerBinary, "run", "--cap-drop", "chown", "busybox", "chown", "100", "/tmp") + out, _, err = runCommandWithOutput(runCmd) + c.Assert(err, checker.NotNil, check.Commentf(out)) + c.Assert(out, checker.Contains, "Operation not permitted") +} + +func (s *DockerSuite) TestUserNoEffectiveCapabilitiesDacOverride(c *check.C) { + testRequires(c, DaemonIsLinux) + + // test that a root user has default capability CAP_DAC_OVERRIDE + runCmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "echo test > /etc/passwd") + _, _, err := runCommandWithOutput(runCmd) + c.Assert(err, check.IsNil) + // test that non root user does not have default capability CAP_DAC_OVERRIDE + runCmd = exec.Command(dockerBinary, "run", "--user", "1000:1000", "busybox", "sh", "-c", "echo test > /etc/passwd") + out, _, err := runCommandWithOutput(runCmd) + c.Assert(err, checker.NotNil, check.Commentf(out)) + c.Assert(out, checker.Contains, "Permission denied") + // TODO test that root user can drop default capability CAP_DAC_OVERRIDE +} + +func (s *DockerSuite) TestUserNoEffectiveCapabilitiesFowner(c *check.C) { + testRequires(c, DaemonIsLinux) + + // test that a root user has default capability CAP_FOWNER + runCmd := exec.Command(dockerBinary, "run", "busybox", "chmod", "777", "/etc/passwd") + _, _, err := runCommandWithOutput(runCmd) + c.Assert(err, check.IsNil) + // test that non root user does not have default capability CAP_FOWNER + runCmd = exec.Command(dockerBinary, "run", "--user", "1000:1000", "busybox", "chmod", "777", "/etc/passwd") + out, _, err := runCommandWithOutput(runCmd) + c.Assert(err, checker.NotNil, check.Commentf(out)) + c.Assert(out, checker.Contains, "Operation not permitted") + // TODO test that root user can drop default capability CAP_FOWNER +} + +// TODO CAP_KILL + +func (s *DockerSuite) TestUserNoEffectiveCapabilitiesSetuid(c *check.C) { + testRequires(c, DaemonIsLinux) + + // test that a root user has default capability CAP_SETUID + runCmd := exec.Command(dockerBinary, "run", "syscall-test", "setuid-test") + _, _, err := runCommandWithOutput(runCmd) + c.Assert(err, check.IsNil) + // test that non root user does not have default capability CAP_SETUID + runCmd = exec.Command(dockerBinary, "run", "--user", "1000:1000", "syscall-test", "setuid-test") + out, _, err := runCommandWithOutput(runCmd) + c.Assert(err, checker.NotNil, check.Commentf(out)) + c.Assert(out, checker.Contains, "Operation not permitted") + // test that root user can drop default capability CAP_SETUID + runCmd = exec.Command(dockerBinary, "run", "--cap-drop", "setuid", "syscall-test", "setuid-test") + out, _, err = runCommandWithOutput(runCmd) + c.Assert(err, checker.NotNil, check.Commentf(out)) + c.Assert(out, checker.Contains, "Operation not permitted") +} + +func (s *DockerSuite) TestUserNoEffectiveCapabilitiesSetgid(c *check.C) { + testRequires(c, DaemonIsLinux) + + // test that a root user has default capability CAP_SETGID + runCmd := exec.Command(dockerBinary, "run", "syscall-test", "setgid-test") + _, _, err := runCommandWithOutput(runCmd) + c.Assert(err, check.IsNil) + // test that non root user does not have default capability CAP_SETGID + runCmd = exec.Command(dockerBinary, "run", "--user", "1000:1000", "syscall-test", "setgid-test") + out, _, err := runCommandWithOutput(runCmd) + c.Assert(err, checker.NotNil, check.Commentf(out)) + c.Assert(out, checker.Contains, "Operation not permitted") + // test that root user can drop default capability CAP_SETGID + runCmd = exec.Command(dockerBinary, "run", "--cap-drop", "setgid", "syscall-test", "setgid-test") + out, _, err = runCommandWithOutput(runCmd) + c.Assert(err, checker.NotNil, check.Commentf(out)) + c.Assert(out, checker.Contains, "Operation not permitted") +} + +// TODO CAP_SETPCAP + +func (s *DockerSuite) TestUserNoEffectiveCapabilitiesNetBindService(c *check.C) { + testRequires(c, DaemonIsLinux) + + // test that a root user has default capability CAP_NET_BIND_SERVICE + runCmd := exec.Command(dockerBinary, "run", "syscall-test", "socket-test") + _, _, err := runCommandWithOutput(runCmd) + c.Assert(err, check.IsNil) + // test that non root user does not have default capability CAP_NET_BIND_SERVICE + runCmd = exec.Command(dockerBinary, "run", "--user", "1000:1000", "syscall-test", "socket-test") + out, _, err := runCommandWithOutput(runCmd) + c.Assert(err, checker.NotNil, check.Commentf(out)) + c.Assert(out, checker.Contains, "Permission denied") + // test that root user can drop default capability CAP_NET_BIND_SERVICE + runCmd = exec.Command(dockerBinary, "run", "--cap-drop", "net_bind_service", "syscall-test", "socket-test") + out, _, err = runCommandWithOutput(runCmd) + c.Assert(err, checker.NotNil, check.Commentf(out)) + c.Assert(out, checker.Contains, "Permission denied") +} + +func (s *DockerSuite) TestUserNoEffectiveCapabilitiesNetRaw(c *check.C) { + testRequires(c, DaemonIsLinux) + + // test that a root user has default capability CAP_NET_RAW + runCmd := exec.Command(dockerBinary, "run", "syscall-test", "raw-test") + _, _, err := runCommandWithOutput(runCmd) + c.Assert(err, check.IsNil) + // test that non root user does not have default capability CAP_NET_RAW + runCmd = exec.Command(dockerBinary, "run", "--user", "1000:1000", "syscall-test", "raw-test") + out, _, err := runCommandWithOutput(runCmd) + c.Assert(err, checker.NotNil, check.Commentf(out)) + c.Assert(out, checker.Contains, "Operation not permitted") + // test non root can drop default capability CAP_NET_RAW + runCmd = exec.Command(dockerBinary, "run", "--cap-drop", "net_raw", "syscall-test", "raw-test") + out, _, err = runCommandWithOutput(runCmd) + c.Assert(err, checker.NotNil, check.Commentf(out)) + c.Assert(out, checker.Contains, "Operation not permitted") +} + +func (s *DockerSuite) TestUserNoEffectiveCapabilitiesChroot(c *check.C) { + testRequires(c, DaemonIsLinux) + + // test that a root user has default capability CAP_SYS_CHROOT + runCmd := exec.Command(dockerBinary, "run", "busybox", "chroot", "/", "/bin/true") + _, _, err := runCommandWithOutput(runCmd) + c.Assert(err, check.IsNil) + // test that non root user does not have default capability CAP_SYS_CHROOT + runCmd = exec.Command(dockerBinary, "run", "--user", "1000:1000", "busybox", "chroot", "/", "/bin/true") + out, _, err := runCommandWithOutput(runCmd) + c.Assert(err, checker.NotNil, check.Commentf(out)) + c.Assert(out, checker.Contains, "Operation not permitted") + // test that root user can drop default capability CAP_SYS_CHROOT + runCmd = exec.Command(dockerBinary, "run", "--cap-drop", "sys_chroot", "busybox", "chroot", "/", "/bin/true") + out, _, err = runCommandWithOutput(runCmd) + c.Assert(err, checker.NotNil, check.Commentf(out)) + c.Assert(out, checker.Contains, "Operation not permitted") +} + +func (s *DockerSuite) TestUserNoEffectiveCapabilitiesMknod(c *check.C) { + testRequires(c, DaemonIsLinux) + + // test that a root user has default capability CAP_MKNOD + runCmd := exec.Command(dockerBinary, "run", "busybox", "mknod", "/tmp/node", "b", "1", "2") + _, _, err := runCommandWithOutput(runCmd) + c.Assert(err, check.IsNil) + // test that non root user does not have default capability CAP_MKNOD + runCmd = exec.Command(dockerBinary, "run", "--user", "1000:1000", "busybox", "mknod", "/tmp/node", "b", "1", "2") + out, _, err := runCommandWithOutput(runCmd) + c.Assert(err, checker.NotNil, check.Commentf(out)) + c.Assert(out, checker.Contains, "Operation not permitted") + // test that root user can drop default capability CAP_MKNOD + runCmd = exec.Command(dockerBinary, "run", "--cap-drop", "mknod", "busybox", "mknod", "/tmp/node", "b", "1", "2") + out, _, err = runCommandWithOutput(runCmd) + c.Assert(err, checker.NotNil, check.Commentf(out)) + c.Assert(out, checker.Contains, "Operation not permitted") +} + +// TODO CAP_AUDIT_WRITE +// TODO CAP_SETFCAP + func (s *DockerSuite) TestRunApparmorProcDirectory(c *check.C) { testRequires(c, SameHostDaemon, Apparmor)