From f190b1a631a76ef1ce819a0efa8e83fc361410f9 Mon Sep 17 00:00:00 2001 From: Ricardo Branco Date: Sun, 11 Oct 2026 23:43:29 +0200 Subject: [PATCH] seccomp: mask socket() domain to 32 bits in RestrictAddressFamilies= The domain argument of socket() is an int, but it is passed to the kernel in a 64-bit register, and the kernel truncates it to the low 32 bits. The deny-list rules we install compared the full 64-bit register with SCMP_CMP_EQ, so setting any bit above bit 31 (e.g. AF_VSOCK | 1UL << 32) let a denied family through unfiltered. Use SCMP_CMP_MASKED_EQ with a 32-bit mask so we match on the same value the kernel does. The allow-list path is not affected: it relies on the LT/GT range checks, which compare as unsigned 64-bit, so values with upper bits set are always rejected. Add a test that issues socket() via syscall() with an upper bit set. Fixes: #44134 Signed-off-by: Ricardo Branco (cherry picked from commit 62eb6cd8eab6a35a408b2c6547526645bda55ffa) --- src/shared/seccomp-util.c | 6 ++++-- src/test/test-seccomp.c | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/shared/seccomp-util.c b/src/shared/seccomp-util.c index c7663417f7..d66cc97418 100644 --- a/src/shared/seccomp-util.c +++ b/src/shared/seccomp-util.c @@ -1751,7 +1751,9 @@ int seccomp_restrict_address_families(Set *address_families, bool allow_list) { void *af; /* If this is a deny list, then generate one rule for each address family that are - * then combined in OR checks. */ + * then combined in OR checks. The kernel takes the domain as an int, i.e. it + * truncates the 64-bit register to its low 32 bits, hence only compare those. A plain + * equality check could be bypassed by setting any of the upper bits. */ SET_FOREACH(af, address_families) { r = sym_seccomp_rule_add_exact( @@ -1759,7 +1761,7 @@ int seccomp_restrict_address_families(Set *address_families, bool allow_list) { SCMP_ACT_ERRNO(EAFNOSUPPORT), SCMP_SYS(socket), 1, - SCMP_A0(SCMP_CMP_EQ, PTR_TO_INT(af))); + SCMP_A0(SCMP_CMP_MASKED_EQ, UINT32_MAX, (uint32_t) PTR_TO_INT(af))); if (r < 0) break; } diff --git a/src/test/test-seccomp.c b/src/test/test-seccomp.c index e422edc955..1446e71853 100644 --- a/src/test/test-seccomp.c +++ b/src/test/test-seccomp.c @@ -447,6 +447,13 @@ TEST(restrict_address_families) { #else assert_se(fd < 0); assert_se(errno == EAFNOSUPPORT); + + /* The kernel truncates the domain to 32 bits, make sure setting upper bits doesn't bypass the filter */ + if (sizeof(long) == 8) { + fd = syscall(__NR_socket, UINT64_C(1) << 32 | AF_UNIX, SOCK_DGRAM, 0); + assert_se(fd < 0); + assert_se(errno == EAFNOSUPPORT); + } #endif fd = socket(AF_NETLINK, SOCK_DGRAM, 0); @@ -470,6 +477,13 @@ TEST(restrict_address_families) { #else assert_se(fd < 0); assert_se(errno == EAFNOSUPPORT); + + /* Same as above, the allow list must not be bypassed by setting upper bits either */ + if (sizeof(long) == 8) { + fd = syscall(__NR_socket, UINT64_C(1) << 32 | AF_UNIX, SOCK_DGRAM, 0); + assert_se(fd < 0); + assert_se(errno == EAFNOSUPPORT); + } #endif fd = socket(AF_NETLINK, SOCK_DGRAM, 0);