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 1/2] 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{ From 65adc7e022c97f55e45c054ff012988027733b87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Mon, 24 Aug 2026 18:50:35 +0200 Subject: [PATCH 2/2] seccomp: Compact socket domain allow rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit runc splits multiple comparisons on the same syscall argument into separate rules, so bounded ranges become unions and can allow blocked domains. Use one less-than rule for the consecutive domains below AF_ALG. The only additional value it matches is AF_UNSPEC, which is not a creatable domain. Keep higher domains as equality rules so AF_ALG, AF_VSOCK, and unknown or future domains remain blocked. This reduces the generated socket entries from 43 to seven. Signed-off-by: Paweł Gronowski --- seccomp/default.json | 472 +--------------------------------- seccomp/default_linux.go | 65 ++++- seccomp/default_linux_test.go | 103 ++++++++ 3 files changed, 156 insertions(+), 484 deletions(-) create mode 100644 seccomp/default_linux_test.go diff --git a/seccomp/default.json b/seccomp/default.json index 5dc381a..77df9d1 100644 --- a/seccomp/default.json +++ b/seccomp/default.json @@ -444,476 +444,8 @@ "args": [ { "index": 0, - "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" + "value": 38, + "op": "SCMP_CMP_LT" } ] }, diff --git a/seccomp/default_linux.go b/seccomp/default_linux.go index a03ebad..81e3942 100644 --- a/seccomp/default_linux.go +++ b/seccomp/default_linux.go @@ -4,6 +4,8 @@ package seccomp import ( + "slices" + "github.com/opencontainers/runtime-spec/specs-go" "golang.org/x/sys/unix" ) @@ -77,23 +79,58 @@ var allowedSocketDomains = []uint64{ } 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, - }, + // Keep range detection independent of the declaration order above. + s := slices.Clone(allowedSocketDomains) + slices.Sort(s) + return socketSyscallsForDomains(s) +} + +func socketSyscallsForDomains(domains []uint64) []*Syscall { + syscalls := make([]*Syscall, 0, len(domains)) + // runc treats repeated comparisons for one argument as separate OR rules, + // so bounded ranges cannot use both a lower and an upper comparison. + // See https://github.com/opencontainers/runc/issues/2735. + // + // A one-sided range starting at AF_UNIX is safe because AF_UNSPEC is not a + // creatable domain. Collapse it only when doing so removes equality rules. + rangeEnd := 0 + if len(domains) > 1 && domains[0] == unix.AF_UNIX && domains[1] == domains[0]+1 { + // The first domains are represented by the range instead of equalities. + rangeEnd = 2 + lastDomain := domains[1] + for _, domain := range domains[2:] { + // Stop at the first gap so the range does not include a blocked domain. + if domain != lastDomain+1 { + break + } + lastDomain = domain + rangeEnd++ + } + // OpLessThan is exclusive, so compare with the value after the range. + syscalls = append(syscalls, socketSyscall(lastDomain+1, specs.OpLessThan)) + } + + // Every later run is bounded, so preserve it with one equality per domain. + for _, domain := range domains[rangeEnd:] { + syscalls = append(syscalls, socketSyscall(domain, specs.OpEqualTo)) + } + return syscalls +} + +func socketSyscall(value uint64, op specs.LinuxSeccompOperator) *Syscall { + return &Syscall{ + LinuxSyscall: specs.LinuxSyscall{ + Names: []string{"socket"}, + Action: specs.ActAllow, + Args: []specs.LinuxSeccompArg{ + { + Index: 0, + Value: value, + Op: op, }, }, - }) + }, } - return syscalls } func arches() []Architecture { diff --git a/seccomp/default_linux_test.go b/seccomp/default_linux_test.go new file mode 100644 index 0000000..915395c --- /dev/null +++ b/seccomp/default_linux_test.go @@ -0,0 +1,103 @@ +// SPDX-FileCopyrightText: Copyright The Moby Authors +// SPDX-License-Identifier: Apache-2.0 + +package seccomp + +import ( + "testing" + + "github.com/opencontainers/runtime-spec/specs-go" + "golang.org/x/sys/unix" +) + +func socketTestSyscall(args ...specs.LinuxSeccompArg) *Syscall { + return &Syscall{ + LinuxSyscall: specs.LinuxSyscall{ + Names: []string{"socket"}, + Action: specs.ActAllow, + Args: args, + }, + } +} + +func TestSocketSyscallsForDomains(t *testing.T) { + fixtures := []struct { + name string + domains []uint64 + want []*Syscall + }{ + { + name: "singleton", + domains: []uint64{39}, + want: []*Syscall{ + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: 39, Op: specs.OpEqualTo}), + }, + }, + { + name: "later consecutive run", + domains: []uint64{1, 2, 4, 5}, + want: []*Syscall{ + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: 3, Op: specs.OpLessThan}), + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: 4, Op: specs.OpEqualTo}), + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: 5, Op: specs.OpEqualTo}), + }, + }, + { + name: "multiple gaps", + domains: []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 16, 20}, + want: []*Syscall{ + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: 11, Op: specs.OpLessThan}), + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: 12, Op: specs.OpEqualTo}), + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: 13, Op: specs.OpEqualTo}), + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: 14, Op: specs.OpEqualTo}), + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: 16, Op: specs.OpEqualTo}), + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: 20, Op: specs.OpEqualTo}), + }, + }, + { + name: "two-domain initial range", + domains: []uint64{1, 2}, + want: []*Syscall{ + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: 3, Op: specs.OpLessThan}), + }, + }, + { + name: "gap after initial domain", + domains: []uint64{1, 3, 4}, + want: []*Syscall{ + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: 1, Op: specs.OpEqualTo}), + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: 3, Op: specs.OpEqualTo}), + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: 4, Op: specs.OpEqualTo}), + }, + }, + { + name: "range not at initial domain", + domains: []uint64{10, 11, 12}, + want: []*Syscall{ + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: 10, Op: specs.OpEqualTo}), + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: 11, Op: specs.OpEqualTo}), + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: 12, Op: specs.OpEqualTo}), + }, + }, + } + + for _, test := range fixtures { + t.Run(test.name, func(t *testing.T) { + assertDeepEqual(t, test.want, socketSyscallsForDomains(test.domains)) + }) + } +} + +func TestDefaultSyscalls(t *testing.T) { + want := []*Syscall{ + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: unix.AF_ALG, Op: specs.OpLessThan}), + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: unix.AF_NFC, Op: specs.OpEqualTo}), + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: unix.AF_KCM, Op: specs.OpEqualTo}), + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: unix.AF_QIPCRTR, Op: specs.OpEqualTo}), + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: unix.AF_SMC, Op: specs.OpEqualTo}), + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: unix.AF_XDP, Op: specs.OpEqualTo}), + socketTestSyscall(specs.LinuxSeccompArg{Index: 0, Value: unix.AF_MCTP, Op: specs.OpEqualTo}), + } + + assertDeepEqual(t, want, socketSyscalls()) +}