Skip to content

Commit ccabd78

Browse files
committed
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 <[email protected]>
1 parent 433336c commit ccabd78

3 files changed

Lines changed: 147 additions & 0 deletions

File tree

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

0 commit comments

Comments
 (0)