Skip to content

Commit 5a34580

Browse files
committed
integration/container: Add socketcall AF_ALG denial tests
Test that AF_ALG is also denied through the socketcall(2) multiplexer, which is used by glibc on i386 instead of direct socket(2) syscalls. Two subtests: - AF_ALG_socketcall_int80: uses int $0x80 inline assembly from a native 64-bit binary to invoke the ia32 socketcall path, with MAP_32BIT to keep the args pointer below 4 GB (ia32 compat truncates registers). - AF_ALG_socketcall_i386: cross-compiles a static i386 binary using gcc-i686-linux-gnu where glibc naturally routes socket() through socketcall(2). Both are amd64-only. Signed-off-by: Paweł Gronowski <[email protected]>
1 parent d8b0c6a commit 5a34580

2 files changed

Lines changed: 80 additions & 11 deletions

File tree

integration/container/exec_afalg_linux_test.go

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ var (
1919

2020
//go:embed testdata/af_vsock.c
2121
afVSOCKSource string
22+
23+
//go:embed testdata/af_alg_socketcall.c
24+
afALGSocketcallSource string
2225
)
2326

2427
// compileAndExecSocketDenied writes a C source file into the container,
2528
// 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+
// and asserts that socket creation fails with the expected error.
30+
func compileAndExecSocketDenied(ctx context.Context, t *testing.T, apiClient client.APIClient, cID string, name string, src string, cc []string, expectedErr string) {
2931
t.Helper()
3032

3133
binPath := "/tmp/" + name
@@ -50,12 +52,7 @@ func compileAndExecSocketDenied(ctx context.Context, t *testing.T, apiClient cli
5052

5153
out := strings.ToLower(res.Combined())
5254
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())
55+
assert.Check(t, is.Contains(out, expectedErr), "expected %s, got: %s", expectedErr, res.Combined())
5956
}
6057

6158
// TestExecSocketDenied verifies that AF_ALG and AF_VSOCK sockets cannot be
@@ -77,10 +74,39 @@ func TestExecSocketDenied(t *testing.T) {
7774

7875
gcc := []string{"gcc"}
7976

77+
arch := testEnv.DaemonInfo.Architecture
78+
isAmd64 := arch == "amd64" || arch == "x86_64"
79+
8080
t.Run("AF_ALG", func(t *testing.T) {
81-
compileAndExecSocketDenied(ctx, t, apiClient, cID, "AF_ALG", afALGSource, gcc)
81+
compileAndExecSocketDenied(ctx, t, apiClient, cID, "AF_ALG", afALGSource, gcc, "not permitted")
8282
})
8383
t.Run("AF_VSOCK", func(t *testing.T) {
84-
compileAndExecSocketDenied(ctx, t, apiClient, cID, "AF_VSOCK", afVSOCKSource, gcc)
84+
compileAndExecSocketDenied(ctx, t, apiClient, cID, "AF_VSOCK", afVSOCKSource, gcc, "not permitted")
85+
})
86+
87+
// Test AF_ALG via the socketcall(2) multiplexer using int $0x80 to
88+
// invoke the ia32 compat syscall path from a native 64-bit binary.
89+
// MAP_32BIT is used to place the args array below 4 GB, since the
90+
// ia32 compat path truncates all registers to 32 bits.
91+
t.Run("AF_ALG_socketcall_int80", func(t *testing.T) {
92+
skip.If(t, !isAmd64, "int $0x80 ia32 compat only available on amd64")
93+
94+
compileAndExecSocketDenied(ctx, t, apiClient, cID, "AF_ALG_socketcall_int80", afALGSocketcallSource, gcc, "not implemented")
95+
})
96+
97+
// Test AF_ALG with a real i386 binary cross-compiled from amd64. glibc
98+
// on i386 routes socket() through the socketcall(2) multiplexer, which
99+
// is a different seccomp path than the native socket(2) syscall.
100+
t.Run("AF_ALG_socketcall_i386", func(t *testing.T) {
101+
skip.If(t, !isAmd64, "i386 cross-compilation only available on amd64")
102+
103+
res := container.ExecT(ctx, t, apiClient, cID, []string{
104+
"sh", "-c", "apt-get install -y --no-install-recommends gcc-i686-linux-gnu libc6-dev-i386-cross linux-libc-dev-i386-cross",
105+
})
106+
res.AssertSuccess(t)
107+
108+
compileAndExecSocketDenied(ctx, t, apiClient, cID, "AF_ALG_socketcall_i386", afALGSource,
109+
[]string{"i686-linux-gnu-gcc", "-static"}, "not implemented",
110+
)
85111
})
86112
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#include <stdio.h>
2+
#include <errno.h>
3+
#include <sys/mman.h>
4+
5+
#define SYS_SOCKETCALL_I386 102
6+
#define SYS_SOCKET 1
7+
#define AF_ALG 38
8+
#define SOCK_SEQPACKET 5
9+
10+
int main() {
11+
/*
12+
* The int $0x80 ia32 compat path truncates all registers to 32 bits.
13+
* The args pointer must live below 4 GB, so allocate it with MAP_32BIT.
14+
*/
15+
unsigned int *args = mmap(NULL, 4096,
16+
PROT_READ | PROT_WRITE,
17+
MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT,
18+
-1, 0);
19+
if (args == MAP_FAILED) {
20+
perror("mmap");
21+
return 2;
22+
}
23+
args[0] = AF_ALG;
24+
args[1] = SOCK_SEQPACKET;
25+
args[2] = 0;
26+
27+
int ret;
28+
asm volatile (
29+
"int $0x80"
30+
: "=a"(ret)
31+
: "a"(SYS_SOCKETCALL_I386), "b"(SYS_SOCKET), "c"(args)
32+
: "memory"
33+
);
34+
35+
if (ret < 0) {
36+
errno = -ret;
37+
perror("socket");
38+
return 1;
39+
}
40+
41+
printf("AF_ALG socket created via socketcall\n");
42+
return 0;
43+
}

0 commit comments

Comments
 (0)