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 <rbranco@suse.de>
(cherry picked from commit 62eb6cd8eab6a35a408b2c6547526645bda55ffa)
This commit is contained in:
Ricardo Branco
2026-10-16 17:24:51 +01:00
committed by Luca Boccassi
parent 559c4bfdc0
commit f190b1a631
2 changed files with 18 additions and 2 deletions
+4 -2
View File
@@ -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;
}
+14
View File
@@ -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);