Browse code

integration/container: Add test for denied socket address families

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>

Paweł Gronowski authored on 2026/05/01 08:01:18
Showing 3 changed files
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
+}