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 --- include/uapi/linux/landlock.h | 12 +++++++++--- security/landlock/audit.c | 1 + security/landlock/limits.h | 2 +- security/landlock/net.c | 18 ++++++++++++------ security/landlock/syscalls.c | 2 +- tools/testing/selftests/landlock/base_test.c | 4 ++-- tools/testing/selftests/landlock/net_test.c | 5 +++-- 7 files changed, 29 insertions(+), 15 deletions(-) diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h index 10a346e55e95..045b251ff1b4 100644 --- a/include/uapi/linux/landlock.h +++ b/include/uapi/linux/landlock.h @@ -201,9 +201,9 @@ struct landlock_net_port_attr { * 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. + * 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 */ /** diff --git a/security/landlock/audit.c b/security/landlock/audit.c index 8d0edf94037d..e676ebffeebe 100644 --- a/security/landlock/audit.c +++ b/security/landlock/audit.c @@ -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); diff --git a/security/landlock/limits.h b/security/landlock/limits.h index b454ad73b15e..c0f30a4591b8 100644 --- a/security/landlock/limits.h +++ b/security/landlock/limits.h @@ -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) diff --git a/security/landlock/net.c b/security/landlock/net.c index 9eafc1dbf8ff..8da40614c452 100644 --- a/security/landlock/net.c +++ b/security/landlock/net.c @@ -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; diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index accfd2e5a0cd..d45469d5d464 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -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 diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c index 30d37234086c..6c8113c2ded1 100644 --- a/tools/testing/selftests/landlock/base_test.c +++ b/tools/testing/selftests/landlock/base_test.c @@ -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)); diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index 4c528154ea92..ec392d971ea3 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -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 */ -- 2.47.3