TestExecSocketDenied verifies that AF_ALG and AF_VSOCK sockets cannot be created inside a container. AF_ALG is blocked by the default seccomp profile (via socket arg filtering) and by the default AppArmor profile (via "deny network alg"). AF_VSOCK is blocked by seccomp only.
(t *testing.T)
| 64 | // (via socket arg filtering) and by the default AppArmor profile (via |
| 65 | // "deny network alg"). AF_VSOCK is blocked by seccomp only. |
| 66 | func TestExecSocketDenied(t *testing.T) { |
| 67 | skip.If(t, testEnv.DaemonInfo.OSType != "linux") |
| 68 | |
| 69 | ctx := setupTest(t) |
| 70 | apiClient := testEnv.APIClient() |
| 71 | |
| 72 | cID := container.Run(ctx, t, apiClient, container.WithImage("debian:trixie-slim"), container.WithCmd("sleep", "infinity")) |
| 73 | |
| 74 | // Install build dependencies as root. |
| 75 | res := container.ExecT(ctx, t, apiClient, cID, []string{ |
| 76 | "sh", "-c", "apt-get update && apt-get install -y --no-install-recommends gcc libc-dev linux-libc-dev", |
| 77 | }) |
| 78 | res.AssertSuccess(t) |
| 79 | |
| 80 | gcc := []string{"gcc"} |
| 81 | |
| 82 | arch := testEnv.DaemonInfo.Architecture |
| 83 | isAmd64 := arch == "amd64" || arch == "x86_64" |
| 84 | |
| 85 | t.Run("AF_ALG", func(t *testing.T) { |
| 86 | compileAndExecSocketDenied(ctx, t, apiClient, cID, "AF_ALG", afALGSource, gcc) |
| 87 | }) |
| 88 | t.Run("AF_VSOCK", func(t *testing.T) { |
| 89 | compileAndExecSocketDenied(ctx, t, apiClient, cID, "AF_VSOCK", afVSOCKSource, gcc) |
| 90 | }) |
| 91 | |
| 92 | // Test socketcall(2) via int $0x80 to invoke the ia32 compat syscall |
| 93 | // path from a native 64-bit binary. MAP_32BIT is used to place the |
| 94 | // args array below 4 GB since the ia32 compat path truncates all |
| 95 | // registers to 32 bits. |
| 96 | // |
| 97 | // The socketcall binary is compiled with -DSOCK_FAMILY and -DSOCK_TYPE |
| 98 | // to set the address family and socket type at compile time. |
| 99 | t.Run("socketcall_int80", func(t *testing.T) { |
| 100 | skip.If(t, !isAmd64, "int $0x80 ia32 compat only available on amd64") |
| 101 | // Seccomp cannot filter socketcall arguments (the address family |
| 102 | // is behind a userspace pointer). Only an LSM (AppArmor or |
| 103 | // SELinux) can deny AF_ALG via the security_socket_create hook. |
| 104 | hasLSM := slices.Contains(testEnv.DaemonInfo.SecurityOptions, "name=apparmor") || |
| 105 | slices.Contains(testEnv.DaemonInfo.SecurityOptions, "name=selinux") |
| 106 | skip.If(t, !hasLSM, "socketcall filtering requires AppArmor or SELinux") |
| 107 | |
| 108 | srcPath := "/tmp/socketcall.c" |
| 109 | res := container.ExecT(ctx, t, apiClient, cID, []string{ |
| 110 | "sh", "-c", "cat > " + srcPath + " << 'CEOF'\n" + socketcallSource + "\nCEOF", |
| 111 | }) |
| 112 | res.AssertSuccess(t) |
| 113 | |
| 114 | // AF_ALG (38) via socketcall must be denied by the LSM |
| 115 | // (AppArmor's "deny network alg" or SELinux's alg_socket deny), |
| 116 | // which catches it at the security_socket_create hook even |
| 117 | // though seccomp cannot filter socketcall args. |
| 118 | t.Run("AF_ALG", func(t *testing.T) { |
| 119 | binPath := "/tmp/socketcall_af_alg" |
| 120 | res := container.ExecT(ctx, t, apiClient, cID, append(gcc, |
| 121 | "-DSOCK_FAMILY=AF_ALG", "-DSOCK_TYPE=SOCK_SEQPACKET", |
| 122 | "-include", "linux/if_alg.h", |
| 123 | srcPath, "-o", binPath, |
nothing calls this directly
no test coverage detected
searching dependent graphs…