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 <pawel.gronowski@docker.com>
| ... | ... |
@@ -19,13 +19,15 @@ var ( |
| 19 | 19 |
|
| 20 | 20 |
//go:embed testdata/af_vsock.c |
| 21 | 21 |
afVSOCKSource string |
| 22 |
+ |
|
| 23 |
+ //go:embed testdata/af_alg_socketcall.c |
|
| 24 |
+ afALGSocketcallSource string |
|
| 22 | 25 |
) |
| 23 | 26 |
|
| 24 | 27 |
// compileAndExecSocketDenied writes a C source file into the container, |
| 25 | 28 |
// 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) {
|
|
| 29 | 31 |
t.Helper() |
| 30 | 32 |
|
| 31 | 33 |
binPath := "/tmp/" + name |
| ... | ... |
@@ -50,12 +52,7 @@ func compileAndExecSocketDenied(ctx context.Context, t *testing.T, apiClient cli |
| 50 | 50 |
|
| 51 | 51 |
out := strings.ToLower(res.Combined()) |
| 52 | 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()) |
|
| 53 |
+ assert.Check(t, is.Contains(out, expectedErr), "expected %s, got: %s", expectedErr, res.Combined()) |
|
| 59 | 54 |
} |
| 60 | 55 |
|
| 61 | 56 |
// TestExecSocketDenied verifies that AF_ALG and AF_VSOCK sockets cannot be |
| ... | ... |
@@ -77,10 +74,39 @@ func TestExecSocketDenied(t *testing.T) {
|
| 77 | 77 |
|
| 78 | 78 |
gcc := []string{"gcc"}
|
| 79 | 79 |
|
| 80 |
+ arch := testEnv.DaemonInfo.Architecture |
|
| 81 |
+ isAmd64 := arch == "amd64" || arch == "x86_64" |
|
| 82 |
+ |
|
| 80 | 83 |
t.Run("AF_ALG", func(t *testing.T) {
|
| 81 |
- compileAndExecSocketDenied(ctx, t, apiClient, cID, "AF_ALG", afALGSource, gcc) |
|
| 84 |
+ compileAndExecSocketDenied(ctx, t, apiClient, cID, "AF_ALG", afALGSource, gcc, "not permitted") |
|
| 82 | 85 |
}) |
| 83 | 86 |
t.Run("AF_VSOCK", func(t *testing.T) {
|
| 84 |
- compileAndExecSocketDenied(ctx, t, apiClient, cID, "AF_VSOCK", afVSOCKSource, gcc) |
|
| 87 |
+ compileAndExecSocketDenied(ctx, t, apiClient, cID, "AF_VSOCK", afVSOCKSource, gcc, "not permitted") |
|
| 88 |
+ }) |
|
| 89 |
+ |
|
| 90 |
+ // Test AF_ALG via the socketcall(2) multiplexer using int $0x80 to |
|
| 91 |
+ // invoke the ia32 compat syscall path from a native 64-bit binary. |
|
| 92 |
+ // MAP_32BIT is used to place the args array below 4 GB, since the |
|
| 93 |
+ // ia32 compat path truncates all registers to 32 bits. |
|
| 94 |
+ t.Run("AF_ALG_socketcall_int80", func(t *testing.T) {
|
|
| 95 |
+ skip.If(t, !isAmd64, "int $0x80 ia32 compat only available on amd64") |
|
| 96 |
+ |
|
| 97 |
+ compileAndExecSocketDenied(ctx, t, apiClient, cID, "AF_ALG_socketcall_int80", afALGSocketcallSource, gcc, "not implemented") |
|
| 98 |
+ }) |
|
| 99 |
+ |
|
| 100 |
+ // Test AF_ALG with a real i386 binary cross-compiled from amd64. glibc |
|
| 101 |
+ // on i386 routes socket() through the socketcall(2) multiplexer, which |
|
| 102 |
+ // is a different seccomp path than the native socket(2) syscall. |
|
| 103 |
+ t.Run("AF_ALG_socketcall_i386", func(t *testing.T) {
|
|
| 104 |
+ skip.If(t, !isAmd64, "i386 cross-compilation only available on amd64") |
|
| 105 |
+ |
|
| 106 |
+ res := container.ExecT(ctx, t, apiClient, cID, []string{
|
|
| 107 |
+ "sh", "-c", "apt-get install -y --no-install-recommends gcc-i686-linux-gnu libc6-dev-i386-cross linux-libc-dev-i386-cross", |
|
| 108 |
+ }) |
|
| 109 |
+ res.AssertSuccess(t) |
|
| 110 |
+ |
|
| 111 |
+ compileAndExecSocketDenied(ctx, t, apiClient, cID, "AF_ALG_socketcall_i386", afALGSource, |
|
| 112 |
+ []string{"i686-linux-gnu-gcc", "-static"}, "not implemented",
|
|
| 113 |
+ ) |
|
| 85 | 114 |
}) |
| 86 | 115 |
} |
| 87 | 116 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,43 @@ |
| 0 |
+#include <stdio.h> |
|
| 1 |
+#include <errno.h> |
|
| 2 |
+#include <sys/mman.h> |
|
| 3 |
+ |
|
| 4 |
+#define SYS_SOCKETCALL_I386 102 |
|
| 5 |
+#define SYS_SOCKET 1 |
|
| 6 |
+#define AF_ALG 38 |
|
| 7 |
+#define SOCK_SEQPACKET 5 |
|
| 8 |
+ |
|
| 9 |
+int main() {
|
|
| 10 |
+ /* |
|
| 11 |
+ * The int $0x80 ia32 compat path truncates all registers to 32 bits. |
|
| 12 |
+ * The args pointer must live below 4 GB, so allocate it with MAP_32BIT. |
|
| 13 |
+ */ |
|
| 14 |
+ unsigned int *args = mmap(NULL, 4096, |
|
| 15 |
+ PROT_READ | PROT_WRITE, |
|
| 16 |
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, |
|
| 17 |
+ -1, 0); |
|
| 18 |
+ if (args == MAP_FAILED) {
|
|
| 19 |
+ perror("mmap");
|
|
| 20 |
+ return 2; |
|
| 21 |
+ } |
|
| 22 |
+ args[0] = AF_ALG; |
|
| 23 |
+ args[1] = SOCK_SEQPACKET; |
|
| 24 |
+ args[2] = 0; |
|
| 25 |
+ |
|
| 26 |
+ int ret; |
|
| 27 |
+ asm volatile ( |
|
| 28 |
+ "int $0x80" |
|
| 29 |
+ : "=a"(ret) |
|
| 30 |
+ : "a"(SYS_SOCKETCALL_I386), "b"(SYS_SOCKET), "c"(args) |
|
| 31 |
+ : "memory" |
|
| 32 |
+ ); |
|
| 33 |
+ |
|
| 34 |
+ if (ret < 0) {
|
|
| 35 |
+ errno = -ret; |
|
| 36 |
+ perror("socket");
|
|
| 37 |
+ return 1; |
|
| 38 |
+ } |
|
| 39 |
+ |
|
| 40 |
+ printf("AF_ALG socket created via socketcall\n");
|
|
| 41 |
+ return 0; |
|
| 42 |
+} |