From f8d7350408f650ab0c693ce77087e808997840c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Fri, 21 Aug 2026 20:44:18 +0200 Subject: [PATCH] seccomp: Use an allow-list for socket domains MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the open-ended range checks with the Linux address families the default profile supports. AF_ALG and AF_VSOCK remain blocked, and unknown or future domains now receive the profile's default EPERM response. Signed-off-by: Paweł Gronowski --- seccomp/default.json | 528 ++++++++++++++++++++++++++++++++++++++- seccomp/default_linux.go | 149 +++++++---- 2 files changed, 620 insertions(+), 57 deletions(-) diff --git a/seccomp/default.json b/seccomp/default.json index ea5a494..5dc381a 100644 --- a/seccomp/default.json +++ b/seccomp/default.json @@ -444,8 +444,476 @@ "args": [ { "index": 0, - "value": 38, - "op": "SCMP_CMP_LT" + "value": 1, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 2, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 3, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 4, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 5, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 6, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 7, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 8, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 9, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 10, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 11, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 12, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 13, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 14, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 15, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 16, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 17, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 18, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 19, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 20, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 21, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 22, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 23, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 24, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 25, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 26, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 27, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 28, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 29, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 30, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 31, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 32, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 33, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 34, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 35, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 36, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 37, + "op": "SCMP_CMP_EQ" } ] }, @@ -470,8 +938,60 @@ "args": [ { "index": 0, - "value": 40, - "op": "SCMP_CMP_GT" + "value": 41, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 42, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 43, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 44, + "op": "SCMP_CMP_EQ" + } + ] + }, + { + "names": [ + "socket" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 45, + "op": "SCMP_CMP_EQ" } ] }, diff --git a/seccomp/default_linux.go b/seccomp/default_linux.go index f0f8df0..a03ebad 100644 --- a/seccomp/default_linux.go +++ b/seccomp/default_linux.go @@ -8,13 +8,93 @@ import ( "golang.org/x/sys/unix" ) -// The socket rules in DefaultProfile rely on AF_ALG and AF_VSOCK being -// exactly two apart (38 and 40), with a single family (39) between them. -var ( - _ [38]byte = [unix.AF_ALG]byte{} - _ [40]byte = [unix.AF_VSOCK]byte{} - _ [1]byte = [unix.AF_VSOCK - unix.AF_ALG - 1]byte{} -) +// These are the Linux socket domains currently defined by the UAPI: +// https://github.com/torvalds/linux/blob/master/include/linux/socket.h +// AF_UNSPEC is not a creatable domain, AF_MAX is a sentinel, and AF_LOCAL/AF_FILE +// and AF_ROUTE are aliases of AF_UNIX and AF_NETLINK respectively. +var allowedSocketDomains = []uint64{ + unix.AF_UNIX, + unix.AF_INET, + unix.AF_AX25, + unix.AF_IPX, + unix.AF_APPLETALK, + unix.AF_NETROM, + unix.AF_BRIDGE, + unix.AF_ATMPVC, + unix.AF_X25, + unix.AF_INET6, + unix.AF_ROSE, + unix.AF_DECnet, + unix.AF_NETBEUI, + unix.AF_SECURITY, + unix.AF_KEY, + unix.AF_NETLINK, + unix.AF_PACKET, + unix.AF_ASH, + unix.AF_ECONET, + unix.AF_ATMSVC, + unix.AF_RDS, + unix.AF_SNA, + unix.AF_IRDA, + unix.AF_PPPOX, + unix.AF_WANPIPE, + unix.AF_LLC, + unix.AF_IB, + unix.AF_MPLS, + unix.AF_CAN, + unix.AF_TIPC, + unix.AF_BLUETOOTH, + unix.AF_IUCV, + unix.AF_RXRPC, + unix.AF_ISDN, + unix.AF_PHONET, + unix.AF_IEEE802154, + unix.AF_CAIF, + + // AF_ALG gives userspace direct access to the kernel cryptography API. The + // vulnerabilities demonstrated by https://copy.fail/ have been fixed, but + // general-purpose containers have no practical need for this interface. + // Keep it blocked to avoid exposing an unnecessary kernel attack surface. + // + // unix.AF_ALG, + + unix.AF_NFC, + + // AF_VSOCK provides host/guest communication. Before Linux 7.0 it was global + // across network namespaces, allowing a container to reach any visible VM by + // CID. Linux 7.0 added opt-in namespace isolation for vhost-vsock and + // loopback, but global mode remains the default. Keep it blocked for older + // kernels and default-global configurations; intentional users can provide a + // custom seccomp profile. See https://docs.kernel.org/admin-guide/sysctl/net.html. + // + // unix.AF_VSOCK, + + unix.AF_KCM, + unix.AF_QIPCRTR, + unix.AF_SMC, + unix.AF_XDP, + unix.AF_MCTP, +} + +func socketSyscalls() []*Syscall { + syscalls := make([]*Syscall, 0, len(allowedSocketDomains)) + for _, domain := range allowedSocketDomains { + syscalls = append(syscalls, &Syscall{ + LinuxSyscall: specs.LinuxSyscall{ + Names: []string{"socket"}, + Action: specs.ActAllow, + Args: []specs.LinuxSeccompArg{ + { + Index: 0, + Value: domain, + Op: specs.OpEqualTo, + }, + }, + }, + }) + } + return syscalls +} func arches() []Architecture { return []Architecture{ @@ -442,51 +522,14 @@ func DefaultProfile() *Seccomp { MinKernel: &KernelVersion{4, 8}, }, }, - // Allow socket(2) for all address families except AF_VSOCK and AF_ALG. - // NOTE: on 32-bit x86, socket() goes through socketcall(2) which is - // allowed unconditionally above, so AF_VSOCK/AF_ALG is still reachable - // via the socketcall-based socket() path. These arg filters only apply - // to the direct socket syscall, and do not protect 32-bit x86 unless - // socketcall(2) is also addressed. - { - LinuxSyscall: specs.LinuxSyscall{ - Names: []string{"socket"}, - Action: specs.ActAllow, - Args: []specs.LinuxSeccompArg{ - { - Index: 0, - Value: unix.AF_ALG, - Op: specs.OpLessThan, - }, - }, - }, - }, - { - LinuxSyscall: specs.LinuxSyscall{ - Names: []string{"socket"}, - Action: specs.ActAllow, - Args: []specs.LinuxSeccompArg{ - { - Index: 0, - Value: unix.AF_ALG + 1, - Op: specs.OpEqualTo, - }, - }, - }, - }, - { - LinuxSyscall: specs.LinuxSyscall{ - Names: []string{"socket"}, - Action: specs.ActAllow, - Args: []specs.LinuxSeccompArg{ - { - Index: 0, - Value: unix.AF_VSOCK, - Op: specs.OpGreaterThan, - }, - }, - }, - }, + } + + // Allow socket(2) for the address families listed in allowedSocketDomains. + // On ABIs that use socketcall(2), the socket arguments are behind a pointer + // and cannot be filtered by seccomp. Because socketcall(2) is allowed above, + // this domain allow-list applies only to the direct socket syscall. + syscalls = append(syscalls, socketSyscalls()...) + syscalls = append(syscalls, []*Syscall{ { LinuxSyscall: specs.LinuxSyscall{ Names: []string{"personality"}, @@ -874,7 +917,7 @@ func DefaultProfile() *Seccomp { Caps: []string{"CAP_PERFMON"}, }, }, - } + }...) errnoRet := uint(unix.EPERM) return &Seccomp{