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:
committed by
Luca Boccassi
parent
559c4bfdc0
commit
f190b1a631
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user