landlock: Add UDP bind() access control

Add support for a first fine-grained UDP access right.
LANDLOCK_ACCESS_NET_BIND_UDP controls the ability to set the local port
of a UDP socket (via bind()). It will be useful for servers (to start
receiving datagrams), and for some clients that need to use a specific
source port (e.g. mDNS requires to use port 5353)

For obvious performance concerns, access control is only enforced when
configuring sockets, not when using them for common send/recv
operations.

Bump ABI to allow userspace to detect and use this new right.

Signed-off-by: Matthieu Buffet <matthieu@buffet.re>
Link: https://patch.msgid.link/20260611162107.49278-2-matthieu@buffet.re
[mic: Fix comment formatting]
Signed-off-by: Mickaël Salaün <mic@digikod.net>
This commit is contained in:
Matthieu Buffet 2026-06-11 18:21:01 +02:00 committed by Mickaël Salaün
parent 0ce4243509
commit 9a8ed15ce2
No known key found for this signature in database
GPG Key ID: E5E3D0E88C82F6D2
7 changed files with 30 additions and 16 deletions

View File

@ -200,10 +200,10 @@ struct landlock_net_port_attr {
* (also used for IPv6), and within that range, on a per-socket basis
* with ``setsockopt(IP_LOCAL_PORT_RANGE)``.
*
* A Landlock rule with port 0 and the %LANDLOCK_ACCESS_NET_BIND_TCP
* right means that requesting to bind on port 0 is allowed and it will
* automatically translate to binding on a kernel-assigned ephemeral
* port.
* A Landlock rule with port 0 and the %LANDLOCK_ACCESS_NET_BIND_TCP or
* %LANDLOCK_ACCESS_NET_BIND_UDP right means that requesting to bind on
* port 0 is allowed and it will automatically translate to binding on a
* kernel-assigned ephemeral port.
*/
__u64 port;
};
@ -373,10 +373,16 @@ struct landlock_net_port_attr {
* port. Support added in Landlock ABI version 4.
* - %LANDLOCK_ACCESS_NET_CONNECT_TCP: Connect TCP sockets to the given
* remote port. Support added in Landlock ABI version 4.
*
* And similarly for UDP port numbers:
*
* - %LANDLOCK_ACCESS_NET_BIND_UDP: Bind UDP sockets to the given local
* port. Support added in Landlock ABI version 10.
*/
/* clang-format off */
#define LANDLOCK_ACCESS_NET_BIND_TCP (1ULL << 0)
#define LANDLOCK_ACCESS_NET_CONNECT_TCP (1ULL << 1)
#define LANDLOCK_ACCESS_NET_BIND_UDP (1ULL << 2)
/* clang-format on */
/**

View File

@ -45,6 +45,7 @@ static_assert(ARRAY_SIZE(fs_access_strings) == LANDLOCK_NUM_ACCESS_FS);
static const char *const net_access_strings[] = {
[BIT_INDEX(LANDLOCK_ACCESS_NET_BIND_TCP)] = "net.bind_tcp",
[BIT_INDEX(LANDLOCK_ACCESS_NET_CONNECT_TCP)] = "net.connect_tcp",
[BIT_INDEX(LANDLOCK_ACCESS_NET_BIND_UDP)] = "net.bind_udp",
};
static_assert(ARRAY_SIZE(net_access_strings) == LANDLOCK_NUM_ACCESS_NET);

View File

@ -23,7 +23,7 @@
#define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
#define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS)
#define LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_CONNECT_TCP
#define LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_BIND_UDP
#define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
#define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET)

View File

@ -88,15 +88,17 @@ static int current_check_access_socket(struct socket *const sock,
* inconsistencies and return -EINVAL if needed.
*/
return 0;
} else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
} else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP ||
access_request == LANDLOCK_ACCESS_NET_BIND_UDP) {
/*
* Binding to an AF_UNSPEC address is treated
* differently by IPv4 and IPv6 sockets. The socket's
* family may change under our feet due to
* setsockopt(IPV6_ADDRFORM), but that's ok: we either
* reject entirely or require
* %LANDLOCK_ACCESS_NET_BIND_TCP for the given port, so
* it cannot be used to bypass the policy.
* reject entirely for IPv6 or require
* %LANDLOCK_ACCESS_NET_BIND_TCP or
* %LANDLOCK_ACCESS_NET_BIND_UDP for IPv4, so it cannot
* be used to bypass the policy.
*
* IPv4 sockets map AF_UNSPEC to AF_INET for
* retrocompatibility for bind accesses, only if the
@ -142,7 +144,8 @@ static int current_check_access_socket(struct socket *const sock,
if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) {
audit_net.dport = port;
audit_net.v4info.daddr = addr4->sin_addr.s_addr;
} else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
} else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP ||
access_request == LANDLOCK_ACCESS_NET_BIND_UDP) {
audit_net.sport = port;
audit_net.v4info.saddr = addr4->sin_addr.s_addr;
} else {
@ -164,7 +167,8 @@ static int current_check_access_socket(struct socket *const sock,
if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) {
audit_net.dport = port;
audit_net.v6info.daddr = addr6->sin6_addr;
} else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
} else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP ||
access_request == LANDLOCK_ACCESS_NET_BIND_UDP) {
audit_net.sport = port;
audit_net.v6info.saddr = addr6->sin6_addr;
} else {
@ -224,6 +228,8 @@ static int hook_socket_bind(struct socket *const sock,
if (sk_is_tcp(sock->sk))
access_request = LANDLOCK_ACCESS_NET_BIND_TCP;
else if (sk_is_udp(sock->sk))
access_request = LANDLOCK_ACCESS_NET_BIND_UDP;
else
return 0;

View File

@ -166,7 +166,7 @@ static const struct file_operations ruleset_fops = {
* If the change involves a fix that requires userspace awareness, also update
* the errata documentation in Documentation/userspace-api/landlock.rst .
*/
const int landlock_abi_version = 9;
const int landlock_abi_version = 10;
/**
* sys_landlock_create_ruleset - Create a new ruleset

View File

@ -76,8 +76,8 @@ TEST(abi_version)
const struct landlock_ruleset_attr ruleset_attr = {
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
};
ASSERT_EQ(9, landlock_create_ruleset(NULL, 0,
LANDLOCK_CREATE_RULESET_VERSION));
ASSERT_EQ(10, landlock_create_ruleset(NULL, 0,
LANDLOCK_CREATE_RULESET_VERSION));
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
LANDLOCK_CREATE_RULESET_VERSION));

View File

@ -1326,11 +1326,12 @@ FIXTURE_TEARDOWN(mini)
/* clang-format off */
#define ACCESS_LAST LANDLOCK_ACCESS_NET_CONNECT_TCP
#define ACCESS_LAST LANDLOCK_ACCESS_NET_BIND_UDP
#define ACCESS_ALL ( \
LANDLOCK_ACCESS_NET_BIND_TCP | \
LANDLOCK_ACCESS_NET_CONNECT_TCP)
LANDLOCK_ACCESS_NET_CONNECT_TCP | \
LANDLOCK_ACCESS_NET_BIND_UDP)
/* clang-format on */