Verify that AF_ALG and AF_VSOCK sockets cannot be created inside a
container running with the default seccomp profile.
The test compiles small C programs inside a debian:trixie-slim container
that attempt to create sockets with these address families, then runs
them as a non-root user (uid 1000) and asserts that socket creation is
denied with EPERM or EAFNOSUPPORT.
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
| 1 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,86 @@ |
| 0 |
+package container |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "context" |
|
| 4 |
+ _ "embed" |
|
| 5 |
+ "strings" |
|
| 6 |
+ "testing" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/moby/moby/client" |
|
| 9 |
+ "github.com/moby/moby/v2/integration/internal/container" |
|
| 10 |
+ "gotest.tools/v3/assert" |
|
| 11 |
+ is "gotest.tools/v3/assert/cmp" |
|
| 12 |
+ "gotest.tools/v3/skip" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+var ( |
|
| 16 |
+ //go:embed testdata/af_alg.c |
|
| 17 |
+ afALGSource string |
|
| 18 |
+ |
|
| 19 |
+ //go:embed testdata/af_vsock.c |
|
| 20 |
+ afVSOCKSource string |
|
| 21 |
+) |
|
| 22 |
+ |
|
| 23 |
+// compileAndExecSocketDenied writes a C source file into the container, |
|
| 24 |
+// compiles it with the given compiler command, runs the binary as uid 1000, |
|
| 25 |
+// and asserts that socket creation fails with a permission or |
|
| 26 |
+// address-family error (not EFAULT or other unrelated failures). |
|
| 27 |
+func compileAndExecSocketDenied(ctx context.Context, t *testing.T, apiClient client.APIClient, cID string, name string, src string, cc []string) {
|
|
| 28 |
+ t.Helper() |
|
| 29 |
+ |
|
| 30 |
+ binPath := "/tmp/" + name |
|
| 31 |
+ srcPath := binPath + ".c" |
|
| 32 |
+ |
|
| 33 |
+ res := container.ExecT(ctx, t, apiClient, cID, []string{
|
|
| 34 |
+ "sh", "-c", "cat > " + srcPath + " << 'CEOF'\n" + src + "\nCEOF", |
|
| 35 |
+ }) |
|
| 36 |
+ res.AssertSuccess(t) |
|
| 37 |
+ |
|
| 38 |
+ compileCmd := append(cc, srcPath, "-o", binPath) |
|
| 39 |
+ res = container.ExecT(ctx, t, apiClient, cID, compileCmd) |
|
| 40 |
+ res.AssertSuccess(t) |
|
| 41 |
+ |
|
| 42 |
+ res, err := container.Exec(ctx, apiClient, cID, []string{binPath},
|
|
| 43 |
+ func(ec *client.ExecCreateOptions) {
|
|
| 44 |
+ ec.User = "1000" |
|
| 45 |
+ }, |
|
| 46 |
+ ) |
|
| 47 |
+ assert.NilError(t, err) |
|
| 48 |
+ assert.Check(t, is.Equal(res.ExitCode, 1), "expected %s socket program to fail", name) |
|
| 49 |
+ |
|
| 50 |
+ out := strings.ToLower(res.Combined()) |
|
| 51 |
+ assert.Check(t, is.Contains(out, "socket"), "expected socket-related error message") |
|
| 52 |
+ |
|
| 53 |
+ // Seccomp blocks return either EPERM ("operation not permitted") or
|
|
| 54 |
+ // EAFNOSUPPORT ("address family not supported"). Make sure the failure
|
|
| 55 |
+ // is from seccomp, not from a bogus pointer (EFAULT) or other issue. |
|
| 56 |
+ permErr := strings.Contains(out, "not permitted") || strings.Contains(out, "not supported") |
|
| 57 |
+ assert.Check(t, permErr, "expected EPERM or EAFNOSUPPORT, got: %s", res.Combined()) |
|
| 58 |
+} |
|
| 59 |
+ |
|
| 60 |
+// TestExecSocketDenied verifies that AF_ALG and AF_VSOCK sockets cannot be |
|
| 61 |
+// created inside a container. These address families are blocked by the |
|
| 62 |
+// default seccomp profile. |
|
| 63 |
+func TestExecSocketDenied(t *testing.T) {
|
|
| 64 |
+ skip.If(t, testEnv.DaemonInfo.OSType != "linux") |
|
| 65 |
+ |
|
| 66 |
+ ctx := setupTest(t) |
|
| 67 |
+ apiClient := testEnv.APIClient() |
|
| 68 |
+ |
|
| 69 |
+ cID := container.Run(ctx, t, apiClient, container.WithImage("debian:trixie-slim"), container.WithCmd("sleep", "infinity"))
|
|
| 70 |
+ |
|
| 71 |
+ // Install build dependencies as root. |
|
| 72 |
+ res := container.ExecT(ctx, t, apiClient, cID, []string{
|
|
| 73 |
+ "sh", "-c", "apt-get update && apt-get install -y --no-install-recommends gcc libc-dev linux-libc-dev", |
|
| 74 |
+ }) |
|
| 75 |
+ res.AssertSuccess(t) |
|
| 76 |
+ |
|
| 77 |
+ gcc := []string{"gcc"}
|
|
| 78 |
+ |
|
| 79 |
+ t.Run("AF_ALG", func(t *testing.T) {
|
|
| 80 |
+ compileAndExecSocketDenied(ctx, t, apiClient, cID, "AF_ALG", afALGSource, gcc) |
|
| 81 |
+ }) |
|
| 82 |
+ t.Run("AF_VSOCK", func(t *testing.T) {
|
|
| 83 |
+ compileAndExecSocketDenied(ctx, t, apiClient, cID, "AF_VSOCK", afVSOCKSource, gcc) |
|
| 84 |
+ }) |
|
| 85 |
+} |
| 0 | 86 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,45 @@ |
| 0 |
+#include <sys/socket.h> |
|
| 1 |
+#include <linux/if_alg.h> |
|
| 2 |
+#include <unistd.h> |
|
| 3 |
+#include <string.h> |
|
| 4 |
+#include <stdio.h> |
|
| 5 |
+ |
|
| 6 |
+int main() {
|
|
| 7 |
+ int sockfd, opfd; |
|
| 8 |
+ struct sockaddr_alg sa = {
|
|
| 9 |
+ .salg_family = AF_ALG, |
|
| 10 |
+ .salg_type = "hash", |
|
| 11 |
+ .salg_name = "sha1" |
|
| 12 |
+ }; |
|
| 13 |
+ |
|
| 14 |
+ sockfd = socket(AF_ALG, SOCK_SEQPACKET, 0); |
|
| 15 |
+ if (sockfd < 0) {
|
|
| 16 |
+ perror("socket");
|
|
| 17 |
+ return 1; |
|
| 18 |
+ } |
|
| 19 |
+ |
|
| 20 |
+ if (bind(sockfd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
|
|
| 21 |
+ perror("bind");
|
|
| 22 |
+ close(sockfd); |
|
| 23 |
+ return 1; |
|
| 24 |
+ } |
|
| 25 |
+ |
|
| 26 |
+ opfd = accept(sockfd, NULL, 0); |
|
| 27 |
+ if (opfd < 0) {
|
|
| 28 |
+ perror("accept");
|
|
| 29 |
+ close(sockfd); |
|
| 30 |
+ return 1; |
|
| 31 |
+ } |
|
| 32 |
+ |
|
| 33 |
+ char data[] = "hello world"; |
|
| 34 |
+ write(opfd, data, strlen(data)); |
|
| 35 |
+ |
|
| 36 |
+ char hash[20]; |
|
| 37 |
+ read(opfd, hash, sizeof(hash)); |
|
| 38 |
+ |
|
| 39 |
+ printf("SHA1 hash computed\n");
|
|
| 40 |
+ |
|
| 41 |
+ close(opfd); |
|
| 42 |
+ close(sockfd); |
|
| 43 |
+ return 0; |
|
| 44 |
+} |
| 0 | 45 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,16 @@ |
| 0 |
+#include <sys/socket.h> |
|
| 1 |
+#include <linux/vm_sockets.h> |
|
| 2 |
+#include <unistd.h> |
|
| 3 |
+#include <stdio.h> |
|
| 4 |
+ |
|
| 5 |
+int main() {
|
|
| 6 |
+ int sockfd = socket(AF_VSOCK, SOCK_STREAM, 0); |
|
| 7 |
+ if (sockfd < 0) {
|
|
| 8 |
+ perror("socket");
|
|
| 9 |
+ return 1; |
|
| 10 |
+ } |
|
| 11 |
+ |
|
| 12 |
+ printf("AF_VSOCK socket created\n");
|
|
| 13 |
+ close(sockfd); |
|
| 14 |
+ return 0; |
|
| 15 |
+} |