* [RFC PATCH v3 0/8] landlock: Add UDP access control support
@ 2025-12-12 16:36 Matthieu Buffet
2025-12-12 16:36 ` [RFC PATCH v3 1/8] landlock: Minor reword of docs for TCP access rights Matthieu Buffet
` (9 more replies)
0 siblings, 10 replies; 13+ messages in thread
From: Matthieu Buffet @ 2025-12-12 16:36 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Günther Noack, linux-security-module, Mikhail Ivanov,
konstantin.meskhidze, netdev, Matthieu Buffet
Hi Mickaël, Günther, Mikhail, Konstantin,
Here is v3 of UDP support for Landlock. My apologies for the delay, I've
had to deal with unrelated problems. All feedback from v1/v2 should be
merged, thanks again for taking the time to review them.
I based these patches on linux-mic/next commit 1a3cedbdc156 ("landlock:
Fix wrong type usage") plus my previous patch "landlock: Fix TCP
handling of short AF_UNSPEC addresses" to avoid adding UDP with already
known bugs, duplicated from TCP. I waited a bit to get feedback on that
patch and no one yelled, so I hope it's acceptable, tell me if it's not.
Link: https://lore.kernel.org/linux-security-module/20251027190726.626244-4-matthieu@buffet.re/
Changes since v2
================
Link: https://lore.kernel.org/all/20241214184540.3835222-1-matthieu@buffet.re/
- removed support for sending datagrams with explicit destination
address of family AF_UNSPEC, which allowed to bypass restrictions with
a race condition
- rebased on linux-mic/next => add support for auditing
- fixed mistake in selftests when using unspec_srv variables, which were
implicitly of type SOCK_STREAM and did not actually test UDP code
- add tests for IPPROTO_IP
- improved docs, split off TCP-related refactoring into another commit
Changes since v1
================
Link: https://lore.kernel.org/all/20240916122230.114800-1-matthieu@buffet.re/
- recvmsg hook is gone and sendmsg hook doesn't apply to connected
sockets anymore, to improve performance
- don't add a get_addr_port() helper function, which required a weird
"am I in IPv4 or IPv6 context" to avoid a addrlen > sizeof(struct
sockaddr_in) check in connect(AF_UNSPEC) IPv6 context. A helper was
useful when ports also needed to be read in a recvmsg() hook, now it
is just a simple switch case in the sendmsg() hook, more readable
- rename sendmsg access right to LANDLOCK_ACCESS_NET_UDP_SENDTO
- reorder hook prologue for consistency: check domain, then type and
family
- add additional selftests cases around minimal address length
- update documentation
All important cases should have a selftest now. lcov gives me net.c
going from 91.9% lines/82.5% branches to 93.4% lines/87% branches.
Thank you for taking the time to read this!
Closes: https://github.com/landlock-lsm/linux/issues/10
Matthieu Buffet (8):
landlock: Minor reword of docs for TCP access rights
landlock: Refactor TCP socket type check
landlock: Add UDP bind+connect access control
selftests/landlock: Add UDP bind/connect tests
landlock: Add UDP sendmsg access control
selftests/landlock: Add tests for UDP sendmsg
samples/landlock: Add sandboxer UDP access control
landlock: Add documentation for UDP support
Documentation/userspace-api/landlock.rst | 94 ++-
include/uapi/linux/landlock.h | 46 +-
samples/landlock/sandboxer.c | 58 +-
security/landlock/audit.c | 3 +
security/landlock/limits.h | 2 +-
security/landlock/net.c | 119 +++-
security/landlock/syscalls.c | 2 +-
tools/testing/selftests/landlock/base_test.c | 2 +-
tools/testing/selftests/landlock/net_test.c | 691 ++++++++++++++++---
9 files changed, 869 insertions(+), 148 deletions(-)
base-commit: 1a3cedbdc156e100eb1a5208a8562a3265c35d87
prerequisite-patch-id: 22051d5d4076a87481b22798c127ce84e219ca97
prerequisite-patch-id: 37a1b44596a2d861ba91989edb1d7aac005931d6
prerequisite-patch-id: c7be1c906699a2590ab7112cdf2ab6892178ec07
--
2.47.3
^ permalink raw reply [flat|nested] 13+ messages in thread
* [RFC PATCH v3 1/8] landlock: Minor reword of docs for TCP access rights
2025-12-12 16:36 [RFC PATCH v3 0/8] landlock: Add UDP access control support Matthieu Buffet
@ 2025-12-12 16:36 ` Matthieu Buffet
2025-12-12 16:36 ` [RFC PATCH v3 2/8] landlock: Refactor TCP socket type check Matthieu Buffet
` (8 subsequent siblings)
9 siblings, 0 replies; 13+ messages in thread
From: Matthieu Buffet @ 2025-12-12 16:36 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Günther Noack, linux-security-module, Mikhail Ivanov,
konstantin.meskhidze, netdev, Matthieu Buffet
- Move ABI requirement next to each access right to prepare adding more
access rights;
- Mention the possibility to remove the random component of a socket's
ephemeral port choice within the netns-wide ephemeral port range,
since it allows choosing the "random" ephemeral port.
Signed-off-by: Matthieu Buffet <matthieu@buffet.re>
---
include/uapi/linux/landlock.h | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index f030adc462ee..efb383af40b2 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -182,11 +182,13 @@ struct landlock_net_port_attr {
* It should be noted that port 0 passed to :manpage:`bind(2)` will bind
* to an available port from the ephemeral port range. This can be
* configured with the ``/proc/sys/net/ipv4/ip_local_port_range`` sysctl
- * (also used for IPv6).
+ * (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``
+ * 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 the related port range.
+ * automatically translate to binding on a kernel-assigned ephemeral
+ * port.
*/
__u64 port;
};
@@ -332,13 +334,12 @@ struct landlock_net_port_attr {
* These flags enable to restrict a sandboxed process to a set of network
* actions.
*
- * This is supported since Landlock ABI version 4.
- *
* The following access rights apply to TCP port numbers:
*
- * - %LANDLOCK_ACCESS_NET_BIND_TCP: Bind a TCP socket to a local port.
- * - %LANDLOCK_ACCESS_NET_CONNECT_TCP: Connect an active TCP socket to
- * a remote port.
+ * - %LANDLOCK_ACCESS_NET_BIND_TCP: Bind TCP sockets to the given local
+ * 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.
*/
/* clang-format off */
#define LANDLOCK_ACCESS_NET_BIND_TCP (1ULL << 0)
--
2.47.3
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [RFC PATCH v3 2/8] landlock: Refactor TCP socket type check
2025-12-12 16:36 [RFC PATCH v3 0/8] landlock: Add UDP access control support Matthieu Buffet
2025-12-12 16:36 ` [RFC PATCH v3 1/8] landlock: Minor reword of docs for TCP access rights Matthieu Buffet
@ 2025-12-12 16:36 ` Matthieu Buffet
2025-12-12 16:36 ` [RFC PATCH v3 3/8] landlock: Add UDP bind+connect access control Matthieu Buffet
` (7 subsequent siblings)
9 siblings, 0 replies; 13+ messages in thread
From: Matthieu Buffet @ 2025-12-12 16:36 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Günther Noack, linux-security-module, Mikhail Ivanov,
konstantin.meskhidze, netdev, Matthieu Buffet
Move the socket type check earlier, so that we will later be able to add
elseifs for other types. Ordering of checks (socket is of a type we
enforce restrictions on) / (current creds have landlock restrictions)
should not change anything.
Signed-off-by: Matthieu Buffet <matthieu@buffet.re>
---
security/landlock/net.c | 21 ++++++++++++++++-----
1 file changed, 16 insertions(+), 5 deletions(-)
diff --git a/security/landlock/net.c b/security/landlock/net.c
index e6367e30e5b0..59438285e73b 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -62,9 +62,6 @@ static int current_check_access_socket(struct socket *const sock,
if (!subject)
return 0;
- if (!sk_is_tcp(sock->sk))
- return 0;
-
/* Checks for minimal header length to safely read sa_family. */
if (addrlen < offsetofend(typeof(*address), sa_family))
return -EINVAL;
@@ -214,16 +211,30 @@ static int current_check_access_socket(struct socket *const sock,
static int hook_socket_bind(struct socket *const sock,
struct sockaddr *const address, const int addrlen)
{
+ access_mask_t access_request;
+
+ if (sk_is_tcp(sock->sk))
+ access_request = LANDLOCK_ACCESS_NET_BIND_TCP;
+ else
+ return 0;
+
return current_check_access_socket(sock, address, addrlen,
- LANDLOCK_ACCESS_NET_BIND_TCP);
+ access_request);
}
static int hook_socket_connect(struct socket *const sock,
struct sockaddr *const address,
const int addrlen)
{
+ access_mask_t access_request;
+
+ if (sk_is_tcp(sock->sk))
+ access_request = LANDLOCK_ACCESS_NET_CONNECT_TCP;
+ else
+ return 0;
+
return current_check_access_socket(sock, address, addrlen,
- LANDLOCK_ACCESS_NET_CONNECT_TCP);
+ access_request);
}
static struct security_hook_list landlock_hooks[] __ro_after_init = {
--
2.47.3
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [RFC PATCH v3 3/8] landlock: Add UDP bind+connect access control
2025-12-12 16:36 [RFC PATCH v3 0/8] landlock: Add UDP access control support Matthieu Buffet
2025-12-12 16:36 ` [RFC PATCH v3 1/8] landlock: Minor reword of docs for TCP access rights Matthieu Buffet
2025-12-12 16:36 ` [RFC PATCH v3 2/8] landlock: Refactor TCP socket type check Matthieu Buffet
@ 2025-12-12 16:36 ` Matthieu Buffet
2025-12-12 16:37 ` [RFC PATCH v3 4/8] selftests/landlock: Add UDP bind/connect tests Matthieu Buffet
` (6 subsequent siblings)
9 siblings, 0 replies; 13+ messages in thread
From: Matthieu Buffet @ 2025-12-12 16:36 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Günther Noack, linux-security-module, Mikhail Ivanov,
konstantin.meskhidze, netdev, Matthieu Buffet
If a process doesn't need to be able to open UDP sockets, it should be
denied the right to create UDP sockets altogether. For processes using
UDP, add support for two more fine-grained access rights:
- LANDLOCK_ACCESS_NET_CONNECT_UDP, to gate the possibility to connect()
a UDP socket. For client apps (those which want to avoid specifying a
destination for each datagram they send), and for a few servers (those
creating per-client sockets, which want to receive traffic only from a
specific address)
- LANDLOCK_ACCESS_NET_BIND_UDP, to gate the possibility to bind() a UDP
socket. For most servers (to start receiving datagrams), and some
clients that need to use a specific source port (e.g. mDNS requires to
use port 5353)
Access control is only enforced when trying to set the local or remote
end of a UDP socket. So, nothing prevents a client with the right to
send datagrams to use it to get an automatically bound socket and
receive datagrams on an ephemeral port of their choice, without having
LANDLOCK_ACCESS_NET_BIND_UDP. Restricting send/recv semantics is left
for a future patch, as it would require tagging sockets or a netfilter
hook.
Bump ABI to v8 to allow userland to detect and use these new access
rights.
Signed-off-by: Matthieu Buffet <matthieu@buffet.re>
---
include/uapi/linux/landlock.h | 20 ++++++++++++++---
security/landlock/audit.c | 2 ++
security/landlock/limits.h | 2 +-
security/landlock/net.c | 41 ++++++++++++++++++++++-------------
security/landlock/syscalls.c | 2 +-
5 files changed, 47 insertions(+), 20 deletions(-)
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index efb383af40b2..8f748fcf79dd 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -186,9 +186,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;
};
@@ -340,10 +340,24 @@ 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 8.
+ * Note: this access right is not required if your program sends
+ * datagrams and just uses the implicitly bound port assigned by the
+ * kernel to reply. Conversely, denying this right only blocks a
+ * program from binding to non-ephemeral ports if it can send datagrams.
+ * - %LANDLOCK_ACCESS_NET_CONNECT_UDP: Connect UDP sockets to remote
+ * addresses with the given remote port. Support added in Landlock ABI
+ * version 8.
*/
/* 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)
+#define LANDLOCK_ACCESS_NET_CONNECT_UDP (1ULL << 3)
/* clang-format on */
/**
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index e899995f1fd5..23d8dee320ef 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -44,6 +44,8 @@ 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",
+ [BIT_INDEX(LANDLOCK_ACCESS_NET_CONNECT_UDP)] = "net.connect_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 65b5ff051674..13dd5503e471 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_CONNECT_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 59438285e73b..9bddcf466ce9 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -68,28 +68,31 @@ static int current_check_access_socket(struct socket *const sock,
switch (address->sa_family) {
case AF_UNSPEC:
- if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) {
+ if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP ||
+ access_request == LANDLOCK_ACCESS_NET_CONNECT_UDP) {
/*
* Connecting to an address with AF_UNSPEC dissolves
- * the TCP association, which have the same effect as
- * closing the connection while retaining the socket
- * object (i.e., the file descriptor). As for dropping
- * privileges, closing connections is always allowed.
- *
- * For a TCP access control system, this request is
- * legitimate. Let the network stack handle potential
+ * the remote association while retaining the socket
+ * object (i.e., the file descriptor). For TCP, it has
+ * the same effect as closing the connection. For UDP,
+ * it removes any preset remote address. As for
+ * dropping privileges, these actions are always
+ * allowed.
+ * Let the network stack handle potential
* 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.
+ * %LANDLOCK_ACCESS_NET_BIND_TCP or
+ * %LANDLOCK_ACCESS_NET_BIND_UDP for the given port,
+ * 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
@@ -132,10 +135,12 @@ static int current_check_access_socket(struct socket *const sock,
addr4 = (struct sockaddr_in *)address;
port = addr4->sin_port;
- if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) {
+ if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP ||
+ access_request == LANDLOCK_ACCESS_NET_CONNECT_UDP) {
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 {
@@ -154,10 +159,12 @@ static int current_check_access_socket(struct socket *const sock,
addr6 = (struct sockaddr_in6 *)address;
port = addr6->sin6_port;
- if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) {
+ if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP ||
+ access_request == LANDLOCK_ACCESS_NET_CONNECT_UDP) {
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 {
@@ -215,6 +222,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;
@@ -230,6 +239,8 @@ static int hook_socket_connect(struct socket *const sock,
if (sk_is_tcp(sock->sk))
access_request = LANDLOCK_ACCESS_NET_CONNECT_TCP;
+ else if (sk_is_udp(sock->sk))
+ access_request = LANDLOCK_ACCESS_NET_CONNECT_UDP;
else
return 0;
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 0116e9f93ffe..66fd196be85a 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -161,7 +161,7 @@ static const struct file_operations ruleset_fops = {
* Documentation/userspace-api/landlock.rst should be updated to reflect the
* UAPI change.
*/
-const int landlock_abi_version = 7;
+const int landlock_abi_version = 8;
/**
* sys_landlock_create_ruleset - Create a new ruleset
--
2.47.3
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [RFC PATCH v3 4/8] selftests/landlock: Add UDP bind/connect tests
2025-12-12 16:36 [RFC PATCH v3 0/8] landlock: Add UDP access control support Matthieu Buffet
` (2 preceding siblings ...)
2025-12-12 16:36 ` [RFC PATCH v3 3/8] landlock: Add UDP bind+connect access control Matthieu Buffet
@ 2025-12-12 16:37 ` Matthieu Buffet
2025-12-12 16:37 ` [RFC PATCH v3 5/8] landlock: Add UDP sendmsg access control Matthieu Buffet
` (5 subsequent siblings)
9 siblings, 0 replies; 13+ messages in thread
From: Matthieu Buffet @ 2025-12-12 16:37 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Günther Noack, linux-security-module, Mikhail Ivanov,
konstantin.meskhidze, netdev, Matthieu Buffet
Make basic changes to the existing bind() and connect() test suite to
also encompass testing UDP access control.
Signed-off-by: Matthieu Buffet <matthieu@buffet.re>
---
tools/testing/selftests/landlock/base_test.c | 2 +-
tools/testing/selftests/landlock/net_test.c | 389 ++++++++++++++-----
2 files changed, 303 insertions(+), 88 deletions(-)
diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index 7b69002239d7..f4b1a275d8d9 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -76,7 +76,7 @@ TEST(abi_version)
const struct landlock_ruleset_attr ruleset_attr = {
.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
};
- ASSERT_EQ(7, landlock_create_ruleset(NULL, 0,
+ ASSERT_EQ(8, landlock_create_ruleset(NULL, 0,
LANDLOCK_CREATE_RULESET_VERSION));
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index b34b139b3f89..977d82eb9934 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -35,6 +35,7 @@ enum sandbox_type {
NO_SANDBOX,
/* This may be used to test rules that allow *and* deny accesses. */
TCP_SANDBOX,
+ UDP_SANDBOX,
};
static int set_service(struct service_fixture *const srv,
@@ -93,11 +94,20 @@ static bool prot_is_tcp(const struct protocol_variant *const prot)
(prot->protocol == IPPROTO_TCP || prot->protocol == IPPROTO_IP);
}
+static bool prot_is_udp(const struct protocol_variant *const prot)
+{
+ return (prot->domain == AF_INET || prot->domain == AF_INET6) &&
+ prot->type == SOCK_DGRAM &&
+ (prot->protocol == IPPROTO_UDP || prot->protocol == IPPROTO_IP);
+}
+
static bool is_restricted(const struct protocol_variant *const prot,
const enum sandbox_type sandbox)
{
if (sandbox == TCP_SANDBOX)
return prot_is_tcp(prot);
+ else if (sandbox == UDP_SANDBOX)
+ return prot_is_udp(prot);
return false;
}
@@ -271,10 +281,9 @@ FIXTURE_VARIANT(protocol)
FIXTURE_SETUP(protocol)
{
- const struct protocol_variant prot_unspec = {
- .domain = AF_UNSPEC,
- .type = SOCK_STREAM,
- };
+ struct protocol_variant prot_unspec = variant->prot;
+
+ prot_unspec.domain = AF_UNSPEC;
disable_caps(_metadata);
@@ -510,6 +519,92 @@ FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_unix_datagram) {
},
};
+/* clang-format off */
+FIXTURE_VARIANT_ADD(protocol, udp_sandbox_with_ipv4_udp1) {
+ /* clang-format on */
+ .sandbox = UDP_SANDBOX,
+ .prot = {
+ .domain = AF_INET,
+ .type = SOCK_DGRAM,
+ .protocol = IPPROTO_UDP,
+ },
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(protocol, udp_sandbox_with_ipv4_udp2) {
+ /* clang-format on */
+ .sandbox = UDP_SANDBOX,
+ .prot = {
+ .domain = AF_INET,
+ .type = SOCK_DGRAM,
+ /* IPPROTO_IP == 0 */
+ .protocol = IPPROTO_IP,
+ },
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(protocol, udp_sandbox_with_ipv6_udp1) {
+ /* clang-format on */
+ .sandbox = UDP_SANDBOX,
+ .prot = {
+ .domain = AF_INET6,
+ .type = SOCK_DGRAM,
+ .protocol = IPPROTO_UDP,
+ },
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(protocol, udp_sandbox_with_ipv6_udp2) {
+ /* clang-format on */
+ .sandbox = UDP_SANDBOX,
+ .prot = {
+ .domain = AF_INET6,
+ .type = SOCK_DGRAM,
+ /* IPPROTO_IP == 0 */
+ .protocol = IPPROTO_IP,
+ },
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(protocol, udp_sandbox_with_ipv4_tcp) {
+ /* clang-format on */
+ .sandbox = UDP_SANDBOX,
+ .prot = {
+ .domain = AF_INET,
+ .type = SOCK_STREAM,
+ },
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(protocol, udp_sandbox_with_ipv6_tcp) {
+ /* clang-format on */
+ .sandbox = UDP_SANDBOX,
+ .prot = {
+ .domain = AF_INET6,
+ .type = SOCK_STREAM,
+ },
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(protocol, udp_sandbox_with_unix_stream) {
+ /* clang-format on */
+ .sandbox = UDP_SANDBOX,
+ .prot = {
+ .domain = AF_UNIX,
+ .type = SOCK_STREAM,
+ },
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(protocol, udp_sandbox_with_unix_datagram) {
+ /* clang-format on */
+ .sandbox = UDP_SANDBOX,
+ .prot = {
+ .domain = AF_UNIX,
+ .type = SOCK_DGRAM,
+ },
+};
+
static void test_bind_and_connect(struct __test_metadata *const _metadata,
const struct service_fixture *const srv,
const bool deny_bind, const bool deny_connect)
@@ -602,7 +697,7 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
ret = connect_variant(connect_fd, srv);
if (deny_connect) {
EXPECT_EQ(-EACCES, ret);
- } else if (deny_bind) {
+ } else if (deny_bind && srv->protocol.type == SOCK_STREAM) {
/* No listening server. */
EXPECT_EQ(-ECONNREFUSED, ret);
} else {
@@ -641,18 +736,25 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
TEST_F(protocol, bind)
{
- if (variant->sandbox == TCP_SANDBOX) {
+ if (variant->sandbox == TCP_SANDBOX ||
+ variant->sandbox == UDP_SANDBOX) {
+ const __u64 bind_access =
+ (variant->sandbox == TCP_SANDBOX ?
+ LANDLOCK_ACCESS_NET_BIND_TCP :
+ LANDLOCK_ACCESS_NET_BIND_UDP);
+ const __u64 connect_access =
+ (variant->sandbox == TCP_SANDBOX ?
+ LANDLOCK_ACCESS_NET_CONNECT_TCP :
+ LANDLOCK_ACCESS_NET_CONNECT_UDP);
const struct landlock_ruleset_attr ruleset_attr = {
- .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ .handled_access_net = bind_access | connect_access,
};
- const struct landlock_net_port_attr tcp_bind_connect_p0 = {
- .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ const struct landlock_net_port_attr bind_connect_p0 = {
+ .allowed_access = bind_access | connect_access,
.port = self->srv0.port,
};
- const struct landlock_net_port_attr tcp_connect_p1 = {
- .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ const struct landlock_net_port_attr connect_p1 = {
+ .allowed_access = connect_access,
.port = self->srv1.port,
};
int ruleset_fd;
@@ -664,12 +766,12 @@ TEST_F(protocol, bind)
/* Allows connect and bind for the first port. */
ASSERT_EQ(0,
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
- &tcp_bind_connect_p0, 0));
+ &bind_connect_p0, 0));
/* Allows connect and denies bind for the second port. */
ASSERT_EQ(0,
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
- &tcp_connect_p1, 0));
+ &connect_p1, 0));
enforce_ruleset(_metadata, ruleset_fd);
EXPECT_EQ(0, close(ruleset_fd));
@@ -691,18 +793,25 @@ TEST_F(protocol, bind)
TEST_F(protocol, connect)
{
- if (variant->sandbox == TCP_SANDBOX) {
+ if (variant->sandbox == TCP_SANDBOX ||
+ variant->sandbox == UDP_SANDBOX) {
+ const __u64 bind_access =
+ (variant->sandbox == TCP_SANDBOX ?
+ LANDLOCK_ACCESS_NET_BIND_TCP :
+ LANDLOCK_ACCESS_NET_BIND_UDP);
+ const __u64 connect_access =
+ (variant->sandbox == TCP_SANDBOX ?
+ LANDLOCK_ACCESS_NET_CONNECT_TCP :
+ LANDLOCK_ACCESS_NET_CONNECT_UDP);
const struct landlock_ruleset_attr ruleset_attr = {
- .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ .handled_access_net = bind_access | connect_access,
};
- const struct landlock_net_port_attr tcp_bind_connect_p0 = {
- .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ const struct landlock_net_port_attr bind_connect_p0 = {
+ .allowed_access = bind_access | connect_access,
.port = self->srv0.port,
};
- const struct landlock_net_port_attr tcp_bind_p1 = {
- .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+ const struct landlock_net_port_attr bind_p1 = {
+ .allowed_access = bind_access,
.port = self->srv1.port,
};
int ruleset_fd;
@@ -714,12 +823,12 @@ TEST_F(protocol, connect)
/* Allows connect and bind for the first port. */
ASSERT_EQ(0,
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
- &tcp_bind_connect_p0, 0));
+ &bind_connect_p0, 0));
/* Allows bind and denies connect for the second port. */
ASSERT_EQ(0,
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
- &tcp_bind_p1, 0));
+ &bind_p1, 0));
enforce_ruleset(_metadata, ruleset_fd);
EXPECT_EQ(0, close(ruleset_fd));
@@ -737,16 +846,24 @@ TEST_F(protocol, connect)
TEST_F(protocol, bind_unspec)
{
- const struct landlock_ruleset_attr ruleset_attr = {
- .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP,
- };
- const struct landlock_net_port_attr tcp_bind = {
- .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
- .port = self->srv0.port,
- };
+ const int bind_access_right = (variant->sandbox == TCP_SANDBOX ?
+ LANDLOCK_ACCESS_NET_BIND_TCP :
+ LANDLOCK_ACCESS_NET_BIND_UDP);
int bind_fd, ret;
- if (variant->sandbox == TCP_SANDBOX) {
+ if (variant->sandbox == TCP_SANDBOX ||
+ variant->sandbox == UDP_SANDBOX) {
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_net = bind_access_right,
+ };
+ const struct landlock_net_port_attr bind = {
+ .allowed_access =
+ (variant->sandbox == TCP_SANDBOX ?
+ LANDLOCK_ACCESS_NET_BIND_TCP :
+ LANDLOCK_ACCESS_NET_BIND_UDP),
+ .port = self->srv0.port,
+ };
+
const int ruleset_fd = landlock_create_ruleset(
&ruleset_attr, sizeof(ruleset_attr), 0);
ASSERT_LE(0, ruleset_fd);
@@ -754,7 +871,7 @@ TEST_F(protocol, bind_unspec)
/* Allows bind. */
ASSERT_EQ(0,
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
- &tcp_bind, 0));
+ &bind, 0));
enforce_ruleset(_metadata, ruleset_fd);
EXPECT_EQ(0, close(ruleset_fd));
}
@@ -782,7 +899,11 @@ TEST_F(protocol, bind_unspec)
}
EXPECT_EQ(0, close(bind_fd));
- if (variant->sandbox == TCP_SANDBOX) {
+ if (variant->sandbox == TCP_SANDBOX ||
+ variant->sandbox == UDP_SANDBOX) {
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_net = bind_access_right,
+ };
const int ruleset_fd = landlock_create_ruleset(
&ruleset_attr, sizeof(ruleset_attr), 0);
ASSERT_LE(0, ruleset_fd);
@@ -829,10 +950,12 @@ TEST_F(protocol, bind_unspec)
TEST_F(protocol, connect_unspec)
{
const struct landlock_ruleset_attr ruleset_attr = {
- .handled_access_net = LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ .handled_access_net = LANDLOCK_ACCESS_NET_CONNECT_TCP |
+ LANDLOCK_ACCESS_NET_CONNECT_UDP,
};
- const struct landlock_net_port_attr tcp_connect = {
- .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ const struct landlock_net_port_attr connect = {
+ .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP |
+ LANDLOCK_ACCESS_NET_CONNECT_UDP,
.port = self->srv0.port,
};
int bind_fd, client_fd, status;
@@ -865,7 +988,8 @@ TEST_F(protocol, connect_unspec)
EXPECT_EQ(0, ret);
}
- if (variant->sandbox == TCP_SANDBOX) {
+ if (variant->sandbox == TCP_SANDBOX ||
+ variant->sandbox == UDP_SANDBOX) {
const int ruleset_fd = landlock_create_ruleset(
&ruleset_attr, sizeof(ruleset_attr), 0);
ASSERT_LE(0, ruleset_fd);
@@ -873,7 +997,7 @@ TEST_F(protocol, connect_unspec)
/* Allows connect. */
ASSERT_EQ(0, landlock_add_rule(ruleset_fd,
LANDLOCK_RULE_NET_PORT,
- &tcp_connect, 0));
+ &connect, 0));
enforce_ruleset(_metadata, ruleset_fd);
EXPECT_EQ(0, close(ruleset_fd));
}
@@ -896,7 +1020,8 @@ TEST_F(protocol, connect_unspec)
EXPECT_EQ(0, ret);
}
- if (variant->sandbox == TCP_SANDBOX) {
+ if (variant->sandbox == TCP_SANDBOX ||
+ variant->sandbox == UDP_SANDBOX) {
const int ruleset_fd = landlock_create_ruleset(
&ruleset_attr, sizeof(ruleset_attr), 0);
ASSERT_LE(0, ruleset_fd);
@@ -975,6 +1100,13 @@ FIXTURE_VARIANT_ADD(ipv4, tcp_sandbox_with_tcp) {
.type = SOCK_STREAM,
};
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ipv4, udp_sandbox_with_tcp) {
+ /* clang-format on */
+ .sandbox = UDP_SANDBOX,
+ .type = SOCK_STREAM,
+};
+
/* clang-format off */
FIXTURE_VARIANT_ADD(ipv4, no_sandbox_with_udp) {
/* clang-format on */
@@ -989,6 +1121,13 @@ FIXTURE_VARIANT_ADD(ipv4, tcp_sandbox_with_udp) {
.type = SOCK_DGRAM,
};
+/* clang-format off */
+FIXTURE_VARIANT_ADD(ipv4, udp_sandbox_with_udp) {
+ /* clang-format on */
+ .sandbox = UDP_SANDBOX,
+ .type = SOCK_DGRAM,
+};
+
FIXTURE_SETUP(ipv4)
{
const struct protocol_variant prot = {
@@ -1010,16 +1149,21 @@ FIXTURE_TEARDOWN(ipv4)
TEST_F(ipv4, from_unix_to_inet)
{
+ const int access_rights =
+ (variant->sandbox == TCP_SANDBOX ?
+ LANDLOCK_ACCESS_NET_BIND_TCP |
+ LANDLOCK_ACCESS_NET_CONNECT_TCP :
+ LANDLOCK_ACCESS_NET_BIND_UDP |
+ LANDLOCK_ACCESS_NET_CONNECT_UDP);
int unix_stream_fd, unix_dgram_fd;
- if (variant->sandbox == TCP_SANDBOX) {
+ if (variant->sandbox == TCP_SANDBOX ||
+ variant->sandbox == UDP_SANDBOX) {
const struct landlock_ruleset_attr ruleset_attr = {
- .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ .handled_access_net = access_rights,
};
- const struct landlock_net_port_attr tcp_bind_connect_p0 = {
- .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ const struct landlock_net_port_attr bind_connect_p0 = {
+ .allowed_access = access_rights,
.port = self->srv0.port,
};
int ruleset_fd;
@@ -1032,7 +1176,7 @@ TEST_F(ipv4, from_unix_to_inet)
/* Allows connect and bind for srv0. */
ASSERT_EQ(0,
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
- &tcp_bind_connect_p0, 0));
+ &bind_connect_p0, 0));
enforce_ruleset(_metadata, ruleset_fd);
EXPECT_EQ(0, close(ruleset_fd));
@@ -1326,11 +1470,13 @@ FIXTURE_TEARDOWN(mini)
/* clang-format off */
-#define ACCESS_LAST LANDLOCK_ACCESS_NET_CONNECT_TCP
+#define ACCESS_LAST LANDLOCK_ACCESS_NET_CONNECT_UDP
#define ACCESS_ALL ( \
LANDLOCK_ACCESS_NET_BIND_TCP | \
- LANDLOCK_ACCESS_NET_CONNECT_TCP)
+ LANDLOCK_ACCESS_NET_CONNECT_TCP | \
+ LANDLOCK_ACCESS_NET_BIND_UDP | \
+ LANDLOCK_ACCESS_NET_CONNECT_UDP)
/* clang-format on */
@@ -1697,7 +1843,7 @@ FIXTURE_VARIANT_ADD(port_specific, no_sandbox_with_ipv4) {
};
/* clang-format off */
-FIXTURE_VARIANT_ADD(port_specific, sandbox_with_ipv4) {
+FIXTURE_VARIANT_ADD(port_specific, tcp_sandbox_with_ipv4) {
/* clang-format on */
.sandbox = TCP_SANDBOX,
.prot = {
@@ -1706,6 +1852,16 @@ FIXTURE_VARIANT_ADD(port_specific, sandbox_with_ipv4) {
},
};
+/* clang-format off */
+FIXTURE_VARIANT_ADD(port_specific, udp_sandbox_with_ipv4) {
+ /* clang-format on */
+ .sandbox = UDP_SANDBOX,
+ .prot = {
+ .domain = AF_INET,
+ .type = SOCK_DGRAM,
+ },
+};
+
/* clang-format off */
FIXTURE_VARIANT_ADD(port_specific, no_sandbox_with_ipv6) {
/* clang-format on */
@@ -1717,7 +1873,7 @@ FIXTURE_VARIANT_ADD(port_specific, no_sandbox_with_ipv6) {
};
/* clang-format off */
-FIXTURE_VARIANT_ADD(port_specific, sandbox_with_ipv6) {
+FIXTURE_VARIANT_ADD(port_specific, tcp_sandbox_with_ipv6) {
/* clang-format on */
.sandbox = TCP_SANDBOX,
.prot = {
@@ -1726,6 +1882,16 @@ FIXTURE_VARIANT_ADD(port_specific, sandbox_with_ipv6) {
},
};
+/* clang-format off */
+FIXTURE_VARIANT_ADD(port_specific, udp_sandbox_with_ipv6) {
+ /* clang-format on */
+ .sandbox = UDP_SANDBOX,
+ .prot = {
+ .domain = AF_INET6,
+ .type = SOCK_DGRAM,
+ },
+};
+
FIXTURE_SETUP(port_specific)
{
disable_caps(_metadata);
@@ -1745,14 +1911,19 @@ TEST_F(port_specific, bind_connect_zero)
uint16_t port;
/* Adds a rule layer with bind and connect actions. */
- if (variant->sandbox == TCP_SANDBOX) {
+ if (variant->sandbox == TCP_SANDBOX ||
+ variant->sandbox == UDP_SANDBOX) {
+ const int access_rights =
+ (variant->sandbox == TCP_SANDBOX ?
+ LANDLOCK_ACCESS_NET_BIND_TCP |
+ LANDLOCK_ACCESS_NET_CONNECT_TCP :
+ LANDLOCK_ACCESS_NET_BIND_UDP |
+ LANDLOCK_ACCESS_NET_CONNECT_UDP);
const struct landlock_ruleset_attr ruleset_attr = {
- .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP
+ .handled_access_net = access_rights,
};
const struct landlock_net_port_attr tcp_bind_connect_zero = {
- .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ .allowed_access = access_rights,
.port = 0,
};
int ruleset_fd;
@@ -1785,11 +1956,13 @@ TEST_F(port_specific, bind_connect_zero)
ret = bind_variant(bind_fd, &self->srv0);
EXPECT_EQ(0, ret);
- EXPECT_EQ(0, listen(bind_fd, backlog));
+ if (variant->prot.type == SOCK_STREAM) {
+ EXPECT_EQ(0, listen(bind_fd, backlog));
- /* Connects on port 0. */
- ret = connect_variant(connect_fd, &self->srv0);
- EXPECT_EQ(-ECONNREFUSED, ret);
+ /* Connects on port 0. */
+ ret = connect_variant(connect_fd, &self->srv0);
+ EXPECT_EQ(-ECONNREFUSED, ret);
+ }
/* Sets binded port for both protocol families. */
port = get_binded_port(bind_fd, &variant->prot);
@@ -1813,21 +1986,25 @@ TEST_F(port_specific, bind_connect_1023)
int bind_fd, connect_fd, ret;
/* Adds a rule layer with bind and connect actions. */
- if (variant->sandbox == TCP_SANDBOX) {
+ if (variant->sandbox == TCP_SANDBOX ||
+ variant->sandbox == UDP_SANDBOX) {
+ const int access_rights =
+ (variant->sandbox == TCP_SANDBOX ?
+ LANDLOCK_ACCESS_NET_BIND_TCP |
+ LANDLOCK_ACCESS_NET_CONNECT_TCP :
+ LANDLOCK_ACCESS_NET_BIND_UDP |
+ LANDLOCK_ACCESS_NET_CONNECT_UDP);
const struct landlock_ruleset_attr ruleset_attr = {
- .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP
+ .handled_access_net = access_rights,
};
/* A rule with port value less than 1024. */
- const struct landlock_net_port_attr tcp_bind_connect_low_range = {
- .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ const struct landlock_net_port_attr bind_connect_low_range = {
+ .allowed_access = access_rights,
.port = 1023,
};
/* A rule with 1024 port. */
- const struct landlock_net_port_attr tcp_bind_connect = {
- .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ const struct landlock_net_port_attr bind_connect = {
+ .allowed_access = access_rights,
.port = 1024,
};
int ruleset_fd;
@@ -1838,10 +2015,10 @@ TEST_F(port_specific, bind_connect_1023)
ASSERT_EQ(0,
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
- &tcp_bind_connect_low_range, 0));
+ &bind_connect_low_range, 0));
ASSERT_EQ(0,
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
- &tcp_bind_connect, 0));
+ &bind_connect, 0));
enforce_ruleset(_metadata, ruleset_fd);
EXPECT_EQ(0, close(ruleset_fd));
@@ -1865,7 +2042,8 @@ TEST_F(port_specific, bind_connect_1023)
ret = bind_variant(bind_fd, &self->srv0);
clear_cap(_metadata, CAP_NET_BIND_SERVICE);
EXPECT_EQ(0, ret);
- EXPECT_EQ(0, listen(bind_fd, backlog));
+ if (variant->prot.type == SOCK_STREAM)
+ EXPECT_EQ(0, listen(bind_fd, backlog));
/* Connects on the binded port 1023. */
ret = connect_variant(connect_fd, &self->srv0);
@@ -1885,7 +2063,8 @@ TEST_F(port_specific, bind_connect_1023)
/* Binds on port 1024. */
ret = bind_variant(bind_fd, &self->srv0);
EXPECT_EQ(0, ret);
- EXPECT_EQ(0, listen(bind_fd, backlog));
+ if (variant->prot.type == SOCK_STREAM)
+ EXPECT_EQ(0, listen(bind_fd, backlog));
/* Connects on the binded port 1024. */
ret = connect_variant(connect_fd, &self->srv0);
@@ -1895,9 +2074,9 @@ TEST_F(port_specific, bind_connect_1023)
EXPECT_EQ(0, close(bind_fd));
}
-static int matches_log_tcp(const int audit_fd, const char *const blockers,
- const char *const dir_addr, const char *const addr,
- const char *const dir_port)
+static int matches_log_prot(const int audit_fd, const char *const blockers,
+ const char *const dir_addr, const char *const addr,
+ const char *const dir_port)
{
static const char log_template[] = REGEX_LANDLOCK_PREFIX
" blockers=%s %s=%s %s=1024$";
@@ -1933,7 +2112,7 @@ FIXTURE_VARIANT(audit)
};
/* clang-format off */
-FIXTURE_VARIANT_ADD(audit, ipv4) {
+FIXTURE_VARIANT_ADD(audit, ipv4_tcp) {
/* clang-format on */
.addr = "127\\.0\\.0\\.1",
.prot = {
@@ -1943,7 +2122,17 @@ FIXTURE_VARIANT_ADD(audit, ipv4) {
};
/* clang-format off */
-FIXTURE_VARIANT_ADD(audit, ipv6) {
+FIXTURE_VARIANT_ADD(audit, ipv4_udp) {
+ /* clang-format on */
+ .addr = "127\\.0\\.0\\.1",
+ .prot = {
+ .domain = AF_INET,
+ .type = SOCK_DGRAM,
+ },
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(audit, ipv6_tcp) {
/* clang-format on */
.addr = "::1",
.prot = {
@@ -1952,6 +2141,16 @@ FIXTURE_VARIANT_ADD(audit, ipv6) {
},
};
+/* clang-format off */
+FIXTURE_VARIANT_ADD(audit, ipv6_udp) {
+ /* clang-format on */
+ .addr = "::1",
+ .prot = {
+ .domain = AF_INET6,
+ .type = SOCK_DGRAM,
+ },
+};
+
FIXTURE_SETUP(audit)
{
ASSERT_EQ(0, set_service(&self->srv0, variant->prot, 0));
@@ -1972,9 +2171,17 @@ FIXTURE_TEARDOWN(audit)
TEST_F(audit, bind)
{
+ const char *audit_evt = (variant->prot.type == SOCK_STREAM ?
+ "net\\.bind_tcp" :
+ "net\\.bind_udp");
+ const int access_rights =
+ (variant->prot.type == SOCK_STREAM ?
+ LANDLOCK_ACCESS_NET_BIND_TCP |
+ LANDLOCK_ACCESS_NET_CONNECT_TCP :
+ LANDLOCK_ACCESS_NET_BIND_UDP |
+ LANDLOCK_ACCESS_NET_CONNECT_UDP);
const struct landlock_ruleset_attr ruleset_attr = {
- .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ .handled_access_net = access_rights,
};
struct audit_records records;
int ruleset_fd, sock_fd;
@@ -1988,8 +2195,8 @@ TEST_F(audit, bind)
sock_fd = socket_variant(&self->srv0);
ASSERT_LE(0, sock_fd);
EXPECT_EQ(-EACCES, bind_variant(sock_fd, &self->srv0));
- EXPECT_EQ(0, matches_log_tcp(self->audit_fd, "net\\.bind_tcp", "saddr",
- variant->addr, "src"));
+ EXPECT_EQ(0, matches_log_prot(self->audit_fd, audit_evt, "saddr",
+ variant->addr, "src"));
EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
EXPECT_EQ(0, records.access);
@@ -2000,9 +2207,17 @@ TEST_F(audit, bind)
TEST_F(audit, connect)
{
+ const char *audit_evt = (variant->prot.type == SOCK_STREAM ?
+ "net\\.connect_tcp" :
+ "net\\.connect_udp");
+ const int access_rights =
+ (variant->prot.type == SOCK_STREAM ?
+ LANDLOCK_ACCESS_NET_BIND_TCP |
+ LANDLOCK_ACCESS_NET_CONNECT_TCP :
+ LANDLOCK_ACCESS_NET_BIND_UDP |
+ LANDLOCK_ACCESS_NET_CONNECT_UDP);
const struct landlock_ruleset_attr ruleset_attr = {
- .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ .handled_access_net = access_rights,
};
struct audit_records records;
int ruleset_fd, sock_fd;
@@ -2016,8 +2231,8 @@ TEST_F(audit, connect)
sock_fd = socket_variant(&self->srv0);
ASSERT_LE(0, sock_fd);
EXPECT_EQ(-EACCES, connect_variant(sock_fd, &self->srv0));
- EXPECT_EQ(0, matches_log_tcp(self->audit_fd, "net\\.connect_tcp",
- "daddr", variant->addr, "dest"));
+ EXPECT_EQ(0, matches_log_prot(self->audit_fd, audit_evt, "daddr",
+ variant->addr, "dest"));
EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
EXPECT_EQ(0, records.access);
--
2.47.3
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [RFC PATCH v3 5/8] landlock: Add UDP sendmsg access control
2025-12-12 16:36 [RFC PATCH v3 0/8] landlock: Add UDP access control support Matthieu Buffet
` (3 preceding siblings ...)
2025-12-12 16:37 ` [RFC PATCH v3 4/8] selftests/landlock: Add UDP bind/connect tests Matthieu Buffet
@ 2025-12-12 16:37 ` Matthieu Buffet
2025-12-12 16:37 ` [RFC PATCH v3 6/8] selftests/landlock: Add tests for UDP sendmsg Matthieu Buffet
` (4 subsequent siblings)
9 siblings, 0 replies; 13+ messages in thread
From: Matthieu Buffet @ 2025-12-12 16:37 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Günther Noack, linux-security-module, Mikhail Ivanov,
konstantin.meskhidze, netdev, Matthieu Buffet
Add support for a LANDLOCK_ACCESS_NET_SENDTO_UDP access right, providing
control over specifying a UDP datagram's destination address explicitly
in sendto(), sendmsg(), and sendmmsg().
This complements the previous control of connect() via
LANDLOCK_ACCESS_NET_CONNECT_UDP.
Signed-off-by: Matthieu Buffet <matthieu@buffet.re>
---
include/uapi/linux/landlock.h | 13 ++++++++
security/landlock/audit.c | 1 +
security/landlock/limits.h | 2 +-
security/landlock/net.c | 61 +++++++++++++++++++++++++++++++++--
4 files changed, 74 insertions(+), 3 deletions(-)
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index 8f748fcf79dd..c43586e02216 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -352,12 +352,25 @@ struct landlock_net_port_attr {
* - %LANDLOCK_ACCESS_NET_CONNECT_UDP: Connect UDP sockets to remote
* addresses with the given remote port. Support added in Landlock ABI
* version 8.
+ * - %LANDLOCK_ACCESS_NET_SENDTO_UDP: Send datagrams on UDP sockets with
+ * an explicit destination address set to the given remote port.
+ * Support added in Landlock ABI version 8. Note: this access right
+ * does not control sending datagrams with no explicit destination
+ * (e.g. via :manpage:`send(2)` or ``sendto(..., NULL, 0)``, so this
+ * access right is not necessary when specifying a destination address
+ * once and for all in :manpage:`connect(2)`.
+ *
+ * Note: sending datagrams to an explicit ``AF_UNSPEC`` destination
+ * address family is not supported. For IPv4 sockets, you will need to
+ * use an ``AF_INET`` address instead, and for IPv6 sockets, you will
+ * need to use a ``NULL`` address.
*/
/* 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)
#define LANDLOCK_ACCESS_NET_CONNECT_UDP (1ULL << 3)
+#define LANDLOCK_ACCESS_NET_SENDTO_UDP (1ULL << 4)
/* clang-format on */
/**
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index 23d8dee320ef..e0c030727dab 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -46,6 +46,7 @@ static const char *const net_access_strings[] = {
[BIT_INDEX(LANDLOCK_ACCESS_NET_CONNECT_TCP)] = "net.connect_tcp",
[BIT_INDEX(LANDLOCK_ACCESS_NET_BIND_UDP)] = "net.bind_udp",
[BIT_INDEX(LANDLOCK_ACCESS_NET_CONNECT_UDP)] = "net.connect_udp",
+ [BIT_INDEX(LANDLOCK_ACCESS_NET_SENDTO_UDP)] = "net.sendto_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 13dd5503e471..b6d26bc5c49e 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_UDP
+#define LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_SENDTO_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 9bddcf466ce9..061a531339de 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -121,6 +121,34 @@ static int current_check_access_socket(struct socket *const sock,
else
return -EAFNOSUPPORT;
}
+ } else if (access_request == LANDLOCK_ACCESS_NET_SENDTO_UDP) {
+ /*
+ * We cannot allow LANDLOCK_ACCESS_NET_SENDTO_UDP on an
+ * explicit AF_UNSPEC address. That's because semantics
+ * of AF_UNSPEC change between socket families (e.g.
+ * IPv6 treat it as "no address" in the sendmsg()
+ * syscall family, so we should always allow, whilst
+ * IPv4 treat it as AF_INET, so we should filter based
+ * on port, and future address families might even do
+ * something else), and the socket's family can change
+ * under our feet due to setsockopt(IPV6_ADDRFORM).
+ */
+ audit_net.family = AF_UNSPEC;
+ landlock_init_layer_masks(subject->domain,
+ access_request, &layer_masks,
+ LANDLOCK_KEY_NET_PORT);
+ landlock_log_denial(
+ subject,
+ &(struct landlock_request){
+ .type = LANDLOCK_REQUEST_NET_ACCESS,
+ .audit.type = LSM_AUDIT_DATA_NET,
+ .audit.u.net = &audit_net,
+ .access = access_request,
+ .layer_masks = &layer_masks,
+ .layer_masks_size =
+ ARRAY_SIZE(layer_masks),
+ });
+ return -EACCES;
} else {
WARN_ON_ONCE(1);
}
@@ -136,7 +164,8 @@ static int current_check_access_socket(struct socket *const sock,
port = addr4->sin_port;
if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP ||
- access_request == LANDLOCK_ACCESS_NET_CONNECT_UDP) {
+ access_request == LANDLOCK_ACCESS_NET_CONNECT_UDP ||
+ access_request == LANDLOCK_ACCESS_NET_SENDTO_UDP) {
audit_net.dport = port;
audit_net.v4info.daddr = addr4->sin_addr.s_addr;
} else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP ||
@@ -160,7 +189,8 @@ static int current_check_access_socket(struct socket *const sock,
port = addr6->sin6_port;
if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP ||
- access_request == LANDLOCK_ACCESS_NET_CONNECT_UDP) {
+ access_request == LANDLOCK_ACCESS_NET_CONNECT_UDP ||
+ access_request == LANDLOCK_ACCESS_NET_SENDTO_UDP) {
audit_net.dport = port;
audit_net.v6info.daddr = addr6->sin6_addr;
} else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP ||
@@ -248,9 +278,36 @@ static int hook_socket_connect(struct socket *const sock,
access_request);
}
+static int hook_socket_sendmsg(struct socket *const sock,
+ struct msghdr *const msg, const int size)
+{
+ struct sockaddr *const address = msg->msg_name;
+ const int addrlen = msg->msg_namelen;
+ access_mask_t access_request;
+
+ /*
+ * If there is no explicit address in the message, we have no
+ * policy to enforce here because either:
+ * - the socket has a remote address assigned, so the appropriate
+ * access check has already been done back then at assignment time;
+ * - or, we can let the networking stack reply -EDESTADDRREQ.
+ */
+ if (!address)
+ return 0;
+
+ if (sk_is_udp(sock->sk))
+ access_request = LANDLOCK_ACCESS_NET_SENDTO_UDP;
+ else
+ return 0;
+
+ return current_check_access_socket(sock, address, addrlen,
+ access_request);
+}
+
static struct security_hook_list landlock_hooks[] __ro_after_init = {
LSM_HOOK_INIT(socket_bind, hook_socket_bind),
LSM_HOOK_INIT(socket_connect, hook_socket_connect),
+ LSM_HOOK_INIT(socket_sendmsg, hook_socket_sendmsg),
};
__init void landlock_add_net_hooks(void)
--
2.47.3
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [RFC PATCH v3 6/8] selftests/landlock: Add tests for UDP sendmsg
2025-12-12 16:36 [RFC PATCH v3 0/8] landlock: Add UDP access control support Matthieu Buffet
` (4 preceding siblings ...)
2025-12-12 16:37 ` [RFC PATCH v3 5/8] landlock: Add UDP sendmsg access control Matthieu Buffet
@ 2025-12-12 16:37 ` Matthieu Buffet
2026-02-01 16:19 ` Tingmao Wang
2025-12-12 16:37 ` [RFC PATCH v3 7/8] samples/landlock: Add sandboxer UDP access control Matthieu Buffet
` (3 subsequent siblings)
9 siblings, 1 reply; 13+ messages in thread
From: Matthieu Buffet @ 2025-12-12 16:37 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Günther Noack, linux-security-module, Mikhail Ivanov,
konstantin.meskhidze, netdev, Matthieu Buffet
Add tests specific to UDP sendmsg(), orthogonal to whether the process
is allowed to bind()/connect(). Make this part of the protocol_*
variants to ensure behaviour is consistent across AF_INET, AF_INET6 and
AF_UNIX.
Signed-off-by: Matthieu Buffet <matthieu@buffet.re>
---
tools/testing/selftests/landlock/net_test.c | 306 +++++++++++++++++++-
1 file changed, 299 insertions(+), 7 deletions(-)
diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index 977d82eb9934..79ce52907ef3 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -268,9 +268,68 @@ static int connect_variant(const int sock_fd,
return connect_variant_addrlen(sock_fd, srv, get_addrlen(srv, false));
}
+static int sendto_variant_addrlen(const int sock_fd,
+ const struct service_fixture *const srv,
+ const socklen_t addrlen, void *buf,
+ size_t len, size_t flags)
+{
+ const struct sockaddr *dst = NULL;
+ ssize_t ret;
+
+ /*
+ * We never want our processes to be killed by SIGPIPE: we check
+ * return codes and errno, so that we have actual error messages.
+ */
+ flags |= MSG_NOSIGNAL;
+
+ if (srv != NULL) {
+ switch (srv->protocol.domain) {
+ case AF_UNSPEC:
+ case AF_INET:
+ dst = (const struct sockaddr *)&srv->ipv4_addr;
+ break;
+
+ case AF_INET6:
+ dst = (const struct sockaddr *)&srv->ipv6_addr;
+ break;
+
+ case AF_UNIX:
+ dst = (const struct sockaddr *)&srv->unix_addr;
+ break;
+
+ default:
+ errno = -EAFNOSUPPORT;
+ return -errno;
+ }
+ }
+
+ ret = sendto(sock_fd, buf, len, flags, dst, addrlen);
+ if (ret < 0)
+ return -errno;
+
+ /* errno is not set in cases of partial writes. */
+ if (ret != len)
+ return -EINTR;
+
+ return 0;
+}
+
+static int sendto_variant(const int sock_fd,
+ const struct service_fixture *const srv, void *buf,
+ size_t len, size_t flags)
+{
+ socklen_t addrlen = 0;
+
+ if (srv != NULL)
+ addrlen = get_addrlen(srv, false);
+
+ return sendto_variant_addrlen(sock_fd, srv, addrlen, buf, len, flags);
+}
+
FIXTURE(protocol)
{
- struct service_fixture srv0, srv1, srv2, unspec_any0, unspec_srv0;
+ struct service_fixture srv0, srv1, srv2;
+ struct service_fixture unspec_any0, unspec_srv0, unspec_srv1;
};
FIXTURE_VARIANT(protocol)
@@ -292,6 +351,7 @@ FIXTURE_SETUP(protocol)
ASSERT_EQ(0, set_service(&self->srv2, variant->prot, 2));
ASSERT_EQ(0, set_service(&self->unspec_srv0, prot_unspec, 0));
+ ASSERT_EQ(0, set_service(&self->unspec_srv1, prot_unspec, 1));
ASSERT_EQ(0, set_service(&self->unspec_any0, prot_unspec, 0));
self->unspec_any0.ipv4_addr.sin_addr.s_addr = htonl(INADDR_ANY);
@@ -1075,6 +1135,185 @@ TEST_F(protocol, connect_unspec)
EXPECT_EQ(0, close(bind_fd));
}
+TEST_F(protocol, sendmsg)
+{
+ /* Arbitrary value just to not block other tests indefinitely. */
+ const struct timeval read_timeout = {
+ .tv_sec = 0,
+ .tv_usec = 100000,
+ };
+ const bool sandboxed = variant->sandbox == UDP_SANDBOX &&
+ (variant->prot.domain == AF_INET ||
+ variant->prot.domain == AF_INET6);
+ int res, srv1_fd, srv0_fd, client_fd;
+ char read_buf[1] = { 0 };
+
+ if (variant->prot.type != SOCK_DGRAM)
+ return;
+
+ disable_caps(_metadata);
+
+ /* Prepare server on port #0 to be denied */
+ ASSERT_EQ(0, set_service(&self->srv0, variant->prot, 0));
+ srv0_fd = socket_variant(&self->srv0);
+ ASSERT_LE(0, srv0_fd);
+ ASSERT_EQ(0, bind_variant(srv0_fd, &self->srv0));
+
+ /* And another server on port #1 to be allowed */
+ ASSERT_EQ(0, set_service(&self->srv1, variant->prot, 1));
+ srv1_fd = socket_variant(&self->srv1);
+ ASSERT_LE(0, srv1_fd);
+ ASSERT_EQ(0, bind_variant(srv1_fd, &self->srv1));
+
+ EXPECT_EQ(0, setsockopt(srv0_fd, SOL_SOCKET, SO_RCVTIMEO, &read_timeout,
+ sizeof(read_timeout)));
+ EXPECT_EQ(0, setsockopt(srv1_fd, SOL_SOCKET, SO_RCVTIMEO, &read_timeout,
+ sizeof(read_timeout)));
+
+ client_fd = socket_variant(&self->srv0);
+ ASSERT_LE(0, client_fd);
+
+ if (sandboxed) {
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_net = LANDLOCK_ACCESS_NET_SENDTO_UDP |
+ LANDLOCK_ACCESS_NET_BIND_UDP,
+ };
+ const struct landlock_net_port_attr allow_one_server = {
+ .allowed_access = LANDLOCK_ACCESS_NET_SENDTO_UDP,
+ .port = self->srv1.port,
+ };
+ const int ruleset_fd = landlock_create_ruleset(
+ &ruleset_attr, sizeof(ruleset_attr), 0);
+ ASSERT_LE(0, ruleset_fd);
+ ASSERT_EQ(0,
+ landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
+ &allow_one_server, 0));
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+ }
+
+ /* No connect(), NULL address */
+ EXPECT_EQ(-1, write(client_fd, "A", 1));
+ if (variant->prot.domain == AF_UNIX) {
+ EXPECT_EQ(ENOTCONN, errno);
+ } else {
+ EXPECT_EQ(EDESTADDRREQ, errno);
+ }
+
+ /* No connect(), non-NULL but too small explicit address */
+ EXPECT_EQ(-EINVAL,
+ sendto_variant_addrlen(client_fd, &self->srv0,
+ get_addrlen(&self->srv0, true) - 1,
+ "B", 1, 0));
+
+ /* No connect(), explicit denied port */
+ res = sendto_variant(client_fd, &self->srv0, "C", 1, 0);
+ if (sandboxed) {
+ EXPECT_EQ(-EACCES, res);
+ } else {
+ EXPECT_EQ(0, res);
+ EXPECT_EQ(1, read(srv0_fd, read_buf, 1));
+ EXPECT_EQ(read_buf[0], 'C');
+ }
+
+ /* No connect(), explicit allowed port */
+ EXPECT_EQ(0, sendto_variant(client_fd, &self->srv1, "D", 1, 0));
+ EXPECT_EQ(1, read(srv1_fd, read_buf, 1));
+ EXPECT_EQ(read_buf[0], 'D');
+
+ /* Explicit AF_UNSPEC address but truncated */
+ EXPECT_EQ(-EINVAL, sendto_variant_addrlen(
+ client_fd, &self->unspec_srv0,
+ get_addrlen(&self->unspec_srv0, true) - 1,
+ "E", 1, 0));
+
+ /* Explicit AF_UNSPEC address, should always be denied */
+ res = sendto_variant(client_fd, &self->unspec_srv1, "F", 1, 0);
+ if (sandboxed) {
+ EXPECT_EQ(-EACCES, res);
+ } else {
+ if (variant->prot.domain == AF_INET) {
+ /* IPv4 sockets treat AF_UNSPEC as AF_INET */
+ EXPECT_EQ(0, res);
+ EXPECT_EQ(1, read(srv1_fd, read_buf, 1))
+ {
+ TH_LOG("read() failed: %s", strerror(errno));
+ }
+ EXPECT_EQ(read_buf[0], 'F');
+ } else if (variant->prot.domain == AF_INET6) {
+ /* IPv6 sockets treat AF_UNSPEC as a NULL address */
+ EXPECT_EQ(-EDESTADDRREQ, res);
+ } else {
+ /* Unix sockets don't accept AF_UNSPEC */
+ EXPECT_EQ(-EINVAL, res);
+ }
+ }
+
+ /* With connect() on an allowed explicit port, no explicit address */
+ ASSERT_EQ(0, connect_variant(client_fd, &self->srv1));
+ EXPECT_EQ(0, sendto_variant(client_fd, NULL, "G", 1, 0));
+ EXPECT_EQ(1, read(srv1_fd, read_buf, 1));
+ EXPECT_EQ(read_buf[0], 'G');
+
+ /* With connect() on a denied explicit port, no explicit address */
+ ASSERT_EQ(0, connect_variant(client_fd, &self->srv0));
+ EXPECT_EQ(0, sendto_variant(client_fd, NULL, "H", 1, 0));
+ EXPECT_EQ(1, read(srv0_fd, read_buf, 1));
+ EXPECT_EQ(read_buf[0], 'H');
+
+ /* Explicit AF_UNSPEC minimal address (just the sa_family_t field) */
+ res = sendto_variant_addrlen(client_fd, &self->unspec_srv0,
+ get_addrlen(&self->unspec_srv0, true), "I",
+ 1, 0);
+ if (sandboxed) {
+ EXPECT_EQ(-EACCES, res);
+ } else if (variant->prot.domain == AF_INET6) {
+ /*
+ * IPv6 sockets treat AF_UNSPEC as a NULL address,
+ * falling back to the connected address
+ */
+ EXPECT_EQ(0, res);
+ EXPECT_EQ(1, read(srv0_fd, read_buf, 1));
+ EXPECT_EQ(read_buf[0], 'I');
+ } else {
+ /*
+ * IPv4 socket will expect a struct sockaddr_in, our address
+ * is considered truncated.
+ * And Unix sockets don't accept AF_UNSPEC at all.
+ */
+ EXPECT_EQ(-EINVAL, res);
+ }
+
+ /* Explicit AF_UNSPEC address, should always be denied */
+ res = sendto_variant(client_fd, &self->unspec_srv1, "J", 1, 0);
+ if (sandboxed) {
+ EXPECT_EQ(-EACCES, res);
+ } else if (variant->prot.domain == AF_INET ||
+ variant->prot.domain == AF_INET6) {
+ int read_fd;
+
+ EXPECT_EQ(0, res);
+ if (variant->prot.domain == AF_INET) {
+ /* IPv4 sockets treat AF_UNSPEC as AF_INET */
+ read_fd = srv1_fd;
+ } else {
+ /*
+ * IPv6 sockets treat AF_UNSPEC as a NULL address,
+ * falling back to the connected address
+ */
+ read_fd = srv0_fd;
+ }
+ EXPECT_EQ(1, read(read_fd, read_buf, 1))
+ {
+ TH_LOG("read failed: %s", strerror(errno));
+ }
+ EXPECT_EQ(read_buf[0], 'J');
+ } else {
+ /* Unix sockets don't accept AF_UNSPEC */
+ EXPECT_EQ(-EINVAL, res);
+ }
+}
+
FIXTURE(ipv4)
{
struct service_fixture srv0, srv1;
@@ -1470,13 +1709,14 @@ FIXTURE_TEARDOWN(mini)
/* clang-format off */
-#define ACCESS_LAST LANDLOCK_ACCESS_NET_CONNECT_UDP
+#define ACCESS_LAST LANDLOCK_ACCESS_NET_SENDTO_UDP
#define ACCESS_ALL ( \
LANDLOCK_ACCESS_NET_BIND_TCP | \
LANDLOCK_ACCESS_NET_CONNECT_TCP | \
LANDLOCK_ACCESS_NET_BIND_UDP | \
- LANDLOCK_ACCESS_NET_CONNECT_UDP)
+ LANDLOCK_ACCESS_NET_CONNECT_UDP | \
+ LANDLOCK_ACCESS_NET_SENDTO_UDP)
/* clang-format on */
@@ -2078,19 +2318,26 @@ static int matches_log_prot(const int audit_fd, const char *const blockers,
const char *const dir_addr, const char *const addr,
const char *const dir_port)
{
- static const char log_template[] = REGEX_LANDLOCK_PREFIX
+ static const char tmpl_with_addr_port[] = REGEX_LANDLOCK_PREFIX
" blockers=%s %s=%s %s=1024$";
+ static const char tmpl_no_addr_port[] = REGEX_LANDLOCK_PREFIX
+ " blockers=%s$";
/*
* Max strlen(blockers): 16
* Max strlen(dir_addr): 5
* Max strlen(addr): 12
* Max strlen(dir_port): 4
*/
- char log_match[sizeof(log_template) + 37];
+ char log_match[sizeof(tmpl_with_addr_port) + 37];
int log_match_len;
- log_match_len = snprintf(log_match, sizeof(log_match), log_template,
- blockers, dir_addr, addr, dir_port);
+ if (addr != NULL)
+ log_match_len = snprintf(log_match, sizeof(log_match), tmpl_with_addr_port,
+ blockers, dir_addr, addr, dir_port);
+ else
+ log_match_len = snprintf(log_match, sizeof(log_match), tmpl_no_addr_port,
+ blockers);
+
if (log_match_len > sizeof(log_match))
return -E2BIG;
@@ -2101,6 +2348,7 @@ static int matches_log_prot(const int audit_fd, const char *const blockers,
FIXTURE(audit)
{
struct service_fixture srv0;
+ struct service_fixture unspec_srv0;
struct audit_filter audit_filter;
int audit_fd;
};
@@ -2153,7 +2401,13 @@ FIXTURE_VARIANT_ADD(audit, ipv6_udp) {
FIXTURE_SETUP(audit)
{
+ struct protocol_variant prot_unspec = variant->prot;
+
+ prot_unspec.domain = AF_UNSPEC;
+
ASSERT_EQ(0, set_service(&self->srv0, variant->prot, 0));
+ ASSERT_EQ(0, set_service(&self->unspec_srv0, prot_unspec, 0));
+
setup_loopback(_metadata);
set_cap(_metadata, CAP_AUDIT_CONTROL);
@@ -2241,4 +2495,42 @@ TEST_F(audit, connect)
EXPECT_EQ(0, close(sock_fd));
}
+TEST_F(audit, sendmsg)
+{
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_net = LANDLOCK_ACCESS_NET_SENDTO_UDP,
+ };
+ struct audit_records records;
+ int ruleset_fd, sock_fd;
+
+ if (variant->prot.type != SOCK_DGRAM)
+ return;
+
+ ruleset_fd =
+ landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+ ASSERT_LE(0, ruleset_fd);
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+
+ sock_fd = socket_variant(&self->srv0);
+ ASSERT_LE(0, sock_fd);
+ EXPECT_EQ(-EACCES, sendto_variant(sock_fd, &self->srv0, "A", 1, 0));
+ EXPECT_EQ(0, matches_log_prot(self->audit_fd, "net.sendto_udp", "daddr",
+ variant->addr, "dest"));
+
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ EXPECT_EQ(0, records.access);
+ EXPECT_EQ(1, records.domain);
+
+ EXPECT_EQ(-EACCES, sendto_variant(sock_fd, &self->unspec_srv0, "B", 1, 0));
+ EXPECT_EQ(0, matches_log_prot(self->audit_fd, "net.sendto_udp", "daddr",
+ NULL, "dest"));
+
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ EXPECT_EQ(0, records.access);
+ EXPECT_EQ(0, records.domain);
+
+ EXPECT_EQ(0, close(sock_fd));
+}
+
TEST_HARNESS_MAIN
--
2.47.3
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [RFC PATCH v3 7/8] samples/landlock: Add sandboxer UDP access control
2025-12-12 16:36 [RFC PATCH v3 0/8] landlock: Add UDP access control support Matthieu Buffet
` (5 preceding siblings ...)
2025-12-12 16:37 ` [RFC PATCH v3 6/8] selftests/landlock: Add tests for UDP sendmsg Matthieu Buffet
@ 2025-12-12 16:37 ` Matthieu Buffet
2025-12-12 16:37 ` [RFC PATCH v3 8/8] landlock: Add documentation for UDP support Matthieu Buffet
` (2 subsequent siblings)
9 siblings, 0 replies; 13+ messages in thread
From: Matthieu Buffet @ 2025-12-12 16:37 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Günther Noack, linux-security-module, Mikhail Ivanov,
konstantin.meskhidze, netdev, Matthieu Buffet
Add environment variables to control associated access rights:
- LL_UDP_BIND
- LL_UDP_CONNECT
- LL_UDP_SENDTO
Each one takes a list of ports separated by colons, like other list
options.
Signed-off-by: Matthieu Buffet <matthieu@buffet.re>
---
samples/landlock/sandboxer.c | 58 ++++++++++++++++++++++++++++++++++--
1 file changed, 56 insertions(+), 2 deletions(-)
diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
index e7af02f98208..65accc095926 100644
--- a/samples/landlock/sandboxer.c
+++ b/samples/landlock/sandboxer.c
@@ -62,6 +62,9 @@ static inline int landlock_restrict_self(const int ruleset_fd,
#define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT"
#define ENV_SCOPED_NAME "LL_SCOPED"
#define ENV_FORCE_LOG_NAME "LL_FORCE_LOG"
+#define ENV_UDP_BIND_NAME "LL_UDP_BIND"
+#define ENV_UDP_CONNECT_NAME "LL_UDP_CONNECT"
+#define ENV_UDP_SENDTO_NAME "LL_UDP_SENDTO"
#define ENV_DELIMITER ":"
static int str2num(const char *numstr, __u64 *num_dst)
@@ -299,7 +302,7 @@ static bool check_ruleset_scope(const char *const env_var,
/* clang-format on */
-#define LANDLOCK_ABI_LAST 7
+#define LANDLOCK_ABI_LAST 8
#define XSTR(s) #s
#define STR(s) XSTR(s)
@@ -322,6 +325,12 @@ static const char help[] =
"means an empty list):\n"
"* " ENV_TCP_BIND_NAME ": ports allowed to bind (server)\n"
"* " ENV_TCP_CONNECT_NAME ": ports allowed to connect (client)\n"
+ "* " ENV_UDP_BIND_NAME ": local UDP ports allowed to bind (server: "
+ "prepare to receive on port / client: set as source port)\n"
+ "* " ENV_UDP_CONNECT_NAME ": remote UDP ports allowed to connect "
+ "(client: set as destination port / server: receive only from it)\n"
+ "* " ENV_UDP_SENDTO_NAME ": remote UDP ports allowed to send to "
+ "without prior connect()\n"
"* " ENV_SCOPED_NAME ": actions denied on the outside of the landlock domain\n"
" - \"a\" to restrict opening abstract unix sockets\n"
" - \"s\" to restrict sending signals\n"
@@ -334,6 +343,8 @@ static const char help[] =
ENV_FS_RW_NAME "=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
ENV_TCP_BIND_NAME "=\"9418\" "
ENV_TCP_CONNECT_NAME "=\"80:443\" "
+ ENV_UDP_CONNECT_NAME "=\"53\" "
+ ENV_UDP_SENDTO_NAME "=\"53\" "
ENV_SCOPED_NAME "=\"a:s\" "
"%1$s bash -i\n"
"\n"
@@ -354,7 +365,10 @@ int main(const int argc, char *const argv[], char *const *const envp)
struct landlock_ruleset_attr ruleset_attr = {
.handled_access_fs = access_fs_rw,
.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ LANDLOCK_ACCESS_NET_CONNECT_TCP |
+ LANDLOCK_ACCESS_NET_BIND_UDP |
+ LANDLOCK_ACCESS_NET_CONNECT_UDP |
+ LANDLOCK_ACCESS_NET_SENDTO_UDP,
.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
LANDLOCK_SCOPE_SIGNAL,
};
@@ -436,6 +450,13 @@ int main(const int argc, char *const argv[], char *const *const envp)
/* Removes LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON for ABI < 7 */
supported_restrict_flags &=
~LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON;
+ __attribute__((fallthrough));
+ case 7:
+ /* Removes UDP support for ABI < 8 */
+ ruleset_attr.handled_access_net &=
+ ~(LANDLOCK_ACCESS_NET_BIND_UDP |
+ LANDLOCK_ACCESS_NET_CONNECT_UDP |
+ LANDLOCK_ACCESS_NET_SENDTO_UDP);
/* Must be printed for any ABI < LANDLOCK_ABI_LAST. */
fprintf(stderr,
@@ -468,6 +489,27 @@ int main(const int argc, char *const argv[], char *const *const envp)
ruleset_attr.handled_access_net &=
~LANDLOCK_ACCESS_NET_CONNECT_TCP;
}
+ /* Removes UDP bind access control if not supported by a user. */
+ env_port_name = getenv(ENV_UDP_BIND_NAME);
+ if (!env_port_name) {
+ ruleset_attr.handled_access_net &=
+ ~LANDLOCK_ACCESS_NET_BIND_UDP;
+ }
+ /* Removes UDP connect access control if not supported by a user. */
+ env_port_name = getenv(ENV_UDP_CONNECT_NAME);
+ if (!env_port_name) {
+ ruleset_attr.handled_access_net &=
+ ~LANDLOCK_ACCESS_NET_CONNECT_UDP;
+ }
+ /*
+ * Removes UDP send with explicit address access control if not
+ * supported by a user.
+ */
+ env_port_name = getenv(ENV_UDP_SENDTO_NAME);
+ if (!env_port_name) {
+ ruleset_attr.handled_access_net &=
+ ~LANDLOCK_ACCESS_NET_SENDTO_UDP;
+ }
if (check_ruleset_scope(ENV_SCOPED_NAME, &ruleset_attr))
return 1;
@@ -512,6 +554,18 @@ int main(const int argc, char *const argv[], char *const *const envp)
LANDLOCK_ACCESS_NET_CONNECT_TCP)) {
goto err_close_ruleset;
}
+ if (populate_ruleset_net(ENV_UDP_BIND_NAME, ruleset_fd,
+ LANDLOCK_ACCESS_NET_BIND_UDP)) {
+ goto err_close_ruleset;
+ }
+ if (populate_ruleset_net(ENV_UDP_CONNECT_NAME, ruleset_fd,
+ LANDLOCK_ACCESS_NET_CONNECT_UDP)) {
+ goto err_close_ruleset;
+ }
+ if (populate_ruleset_net(ENV_UDP_SENDTO_NAME, ruleset_fd,
+ LANDLOCK_ACCESS_NET_SENDTO_UDP)) {
+ goto err_close_ruleset;
+ }
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
perror("Failed to restrict privileges");
--
2.47.3
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [RFC PATCH v3 8/8] landlock: Add documentation for UDP support
2025-12-12 16:36 [RFC PATCH v3 0/8] landlock: Add UDP access control support Matthieu Buffet
` (6 preceding siblings ...)
2025-12-12 16:37 ` [RFC PATCH v3 7/8] samples/landlock: Add sandboxer UDP access control Matthieu Buffet
@ 2025-12-12 16:37 ` Matthieu Buffet
2026-01-11 21:23 ` [RFC PATCH v3 0/8] landlock: Add UDP access control support Günther Noack
2026-02-14 10:34 ` Mickaël Salaün
9 siblings, 0 replies; 13+ messages in thread
From: Matthieu Buffet @ 2025-12-12 16:37 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Günther Noack, linux-security-module, Mikhail Ivanov,
konstantin.meskhidze, netdev, Matthieu Buffet
Add example of UDP usage, without detailing each access right, but with
an explicit note about the need to handle both SENDTO and CONNECT to
completely block sending (which could otherwise be overlooked).
Slightly change the example used in code blocks: build a ruleset for a
DNS client, so that it uses both TCP and UDP. Also consider an opaque
implementation so that we get to introduce both the right to connect()
and sendmsg(addr != NULL) within the same example.
Signed-off-by: Matthieu Buffet <matthieu@buffet.re>
---
Documentation/userspace-api/landlock.rst | 94 ++++++++++++++++++------
1 file changed, 72 insertions(+), 22 deletions(-)
diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
index 1d0c2c15c22e..758217c2b260 100644
--- a/Documentation/userspace-api/landlock.rst
+++ b/Documentation/userspace-api/landlock.rst
@@ -8,7 +8,7 @@ Landlock: unprivileged access control
=====================================
:Author: Mickaël Salaün
-:Date: March 2025
+:Date: October 2025
The goal of Landlock is to enable restriction of ambient rights (e.g. global
filesystem or network access) for a set of processes. Because Landlock
@@ -40,8 +40,8 @@ Filesystem rules
and the related filesystem actions are defined with
`filesystem access rights`.
-Network rules (since ABI v4)
- For these rules, the object is a TCP port,
+Network rules (since ABI v4 for TCP and v8 for UDP)
+ For these rules, the object is a TCP or UDP port,
and the related actions are defined with `network access rights`.
Defining and enforcing a security policy
@@ -49,11 +49,11 @@ Defining and enforcing a security policy
We first need to define the ruleset that will contain our rules.
-For this example, the ruleset will contain rules that only allow filesystem
-read actions and establish a specific TCP connection. Filesystem write
-actions and other TCP actions will be denied.
+For this example, the ruleset will contain rules that only allow some
+filesystem read actions and some specific UDP and TCP accesses. Filesystem
+write actions and other TCP/UDP actions will be denied.
-The ruleset then needs to handle both these kinds of actions. This is
+The ruleset then needs to handle all these kinds of actions. This is
required for backward and forward compatibility (i.e. the kernel and user
space may not know each other's supported restrictions), hence the need
to be explicit about the denied-by-default access rights.
@@ -80,7 +80,10 @@ to be explicit about the denied-by-default access rights.
LANDLOCK_ACCESS_FS_IOCTL_DEV,
.handled_access_net =
LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ LANDLOCK_ACCESS_NET_CONNECT_TCP |
+ LANDLOCK_ACCESS_NET_BIND_UDP |
+ LANDLOCK_ACCESS_NET_CONNECT_UDP |
+ LANDLOCK_ACCESS_NET_SENDTO_UDP,
.scoped =
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
LANDLOCK_SCOPE_SIGNAL,
@@ -127,6 +130,14 @@ version, and only use the available subset of access rights:
/* Removes LANDLOCK_SCOPE_* for ABI < 6 */
ruleset_attr.scoped &= ~(LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
LANDLOCK_SCOPE_SIGNAL);
+ __attribute__((fallthrough));
+ case 6:
+ case 7:
+ /* Removes LANDLOCK_ACCESS_*_UDP for ABI < 8 */
+ ruleset_attr.handled_access_net &=
+ ~(LANDLOCK_ACCESS_NET_BIND_UDP |
+ LANDLOCK_ACCESS_NET_CONNECT_UDP |
+ LANDLOCK_ACCESS_NET_SENDTO_UDP);
}
This enables the creation of an inclusive ruleset that will contain our rules.
@@ -175,26 +186,53 @@ descriptor.
It may also be required to create rules following the same logic as explained
for the ruleset creation, by filtering access rights according to the Landlock
-ABI version. In this example, this is not required because all of the requested
-``allowed_access`` rights are already available in ABI 1.
+ABI version. So far, this was not required because all of the requested
+``allowed_access`` rights have always been available, from ABI 1.
-For network access-control, we can add a set of rules that allow to use a port
-number for a specific action: HTTPS connections.
+For network access-control, we will add a set of rules to allow DNS
+queries, which requires both UDP and TCP. For TCP, we need to allow
+outbound connections to port 53, which can be handled and granted starting
+with ABI 4:
.. code-block:: c
- struct landlock_net_port_attr net_port = {
- .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
- .port = 443,
- };
+ if (ruleset_attr.handled_access_net & LANDLOCK_ACCESS_NET_CONNECT_TCP) {
+ struct landlock_net_port_attr net_port = {
+ .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ .port = 53,
+ };
- err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
- &net_port, 0);
+ err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
+ &net_port, 0);
+
+We also need to be able to send UDP datagrams to port 53: we don't know if
+the client will call e.g. :manpage:`sendto(2)` with an explicit destination
+address, or :manpage:`connect(2)` then e.g. :manpage:`send(2)`, so we
+allow both. Note that granting ``LANDLOCK_ACCESS_NET_BIND_UDP`` is not
+necessary here because the client's socket will be automatically bound to
+an ephemeral port by the kernel. Also note that we need to handle both
+``LANDLOCK_ACCESS_NET_CONNECT_UDP`` and ``LANDLOCK_ACCESS_NET_SENDTO_UDP``
+to effectively block sending UDP datagrams to arbitrary ports.
+
+.. code-block:: c
+
+ if ((ruleset_attr.handled_access_net & (LANDLOCK_ACCESS_NET_CONNECT_UDP |
+ LANDLOCK_ACCESS_NET_SENDTO_UDP)) ==
+ (LANDLOCK_ACCESS_NET_CONNECT_UDP |
+ LANDLOCK_ACCESS_NET_SENDTO_UDP)) {
+ struct landlock_net_port_attr net_port = {
+ .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_UDP |
+ LANDLOCK_ACCESS_NET_SENDTO_UDP,
+ .port = 53,
+ };
+
+ err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
+ &net_port, 0);
The next step is to restrict the current thread from gaining more privileges
(e.g. through a SUID binary). We now have a ruleset with the first rule
allowing read access to ``/usr`` while denying all other handled accesses for
-the filesystem, and a second rule allowing HTTPS connections.
+the filesystem, and two more rules allowing DNS queries.
.. code-block:: c
@@ -604,6 +642,18 @@ Landlock audit events with the ``LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF``,
sys_landlock_restrict_self(). See Documentation/admin-guide/LSM/landlock.rst
for more details on audit.
+UDP networking (ABI < 8)
+------------------------
+
+Starting with the Landlock ABI version 8, it is possible to restrict
+setting the local/remote ports of UDP sockets to specific values. Restrictions
+are now enforced at :manpage:`bind(2)` time with the new
+``LANDLOCK_ACCESS_NET_BIND_UDP`` access right, and at :manpage:`connect(2)`
+time with ``LANDLOCK_ACCESS_NET_CONNECT_UDP``. Finally,
+``LANDLOCK_ACCESS_NET_SENDTO_UDP`` also restricts sending datagrams with
+an explicit destination address (e.g. with :manpage:`sendmsg(2)`) to only some
+specific remote ports.
+
.. _kernel_support:
Kernel support
@@ -666,10 +716,10 @@ the boot loader.
Network support
---------------
-To be able to explicitly allow TCP operations (e.g., adding a network rule with
-``LANDLOCK_ACCESS_NET_BIND_TCP``), the kernel must support TCP
+To be able to explicitly allow TCP or UDP operations (e.g., adding a network rule with
+``LANDLOCK_ACCESS_NET_BIND_TCP``), the kernel must support the TCP/IP protocol suite
(``CONFIG_INET=y``). Otherwise, sys_landlock_add_rule() returns an
-``EAFNOSUPPORT`` error, which can safely be ignored because this kind of TCP
+``EAFNOSUPPORT`` error, which can safely be ignored because this kind of TCP or UDP
operation is already not possible.
Questions and answers
--
2.47.3
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v3 0/8] landlock: Add UDP access control support
2025-12-12 16:36 [RFC PATCH v3 0/8] landlock: Add UDP access control support Matthieu Buffet
` (7 preceding siblings ...)
2025-12-12 16:37 ` [RFC PATCH v3 8/8] landlock: Add documentation for UDP support Matthieu Buffet
@ 2026-01-11 21:23 ` Günther Noack
2026-01-12 16:03 ` Mickaël Salaün
2026-02-14 10:34 ` Mickaël Salaün
9 siblings, 1 reply; 13+ messages in thread
From: Günther Noack @ 2026-01-11 21:23 UTC (permalink / raw)
To: Matthieu Buffet
Cc: Mickaël Salaün, Günther Noack,
linux-security-module, Mikhail Ivanov, konstantin.meskhidze,
netdev
Hello Matthieu!
On Fri, Dec 12, 2025 at 05:36:56PM +0100, Matthieu Buffet wrote:
> Here is v3 of UDP support for Landlock. My apologies for the delay, I've
> had to deal with unrelated problems. All feedback from v1/v2 should be
> merged, thanks again for taking the time to review them.
Good to see the patch again. :)
Apologies for review delay as well. There are many Landlock reviews
in flight at the moment, it might take some time to catch up with all
of them.
FYI: In [1], I have been sending a patch for controlling UNIX socket
lookup, which is restricting connect() and sendmsg() operations for
UNIX domain sockets of types SOCK_STREAM, SOCK_DGRAM and
SOCK_SEQPACKET. I am bringing it up because it feels that the
semantics for the UDP and UNIX datagram access rights hook in similar
places and therefore should work similarly?
In the current UNIX socket patch set (v2), there is only one Landlock
access right which controls both connect() and sendmsg() when they are
done on a UNIX datagram socket. This feels natural to be, because you
can reach the same recipient address whether that is done with
connect() or with sendmsg()...?
(Was there a previous discussion where it was decided that these
should be two different access rights for UDP sockets and UNIX dgram
sockets?)
[1] https://lore.kernel.org/all/20260101134102.25938-1-gnoack3000@gmail.com/
Thanks,
–Günther
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v3 0/8] landlock: Add UDP access control support
2026-01-11 21:23 ` [RFC PATCH v3 0/8] landlock: Add UDP access control support Günther Noack
@ 2026-01-12 16:03 ` Mickaël Salaün
0 siblings, 0 replies; 13+ messages in thread
From: Mickaël Salaün @ 2026-01-12 16:03 UTC (permalink / raw)
To: Günther Noack
Cc: Matthieu Buffet, Günther Noack, linux-security-module,
Mikhail Ivanov, konstantin.meskhidze, netdev
On Sun, Jan 11, 2026 at 10:23:16PM +0100, Günther Noack wrote:
> Hello Matthieu!
>
> On Fri, Dec 12, 2025 at 05:36:56PM +0100, Matthieu Buffet wrote:
> > Here is v3 of UDP support for Landlock. My apologies for the delay, I've
> > had to deal with unrelated problems. All feedback from v1/v2 should be
> > merged, thanks again for taking the time to review them.
>
> Good to see the patch again. :)
>
> Apologies for review delay as well. There are many Landlock reviews
> in flight at the moment, it might take some time to catch up with all
> of them.
>
> FYI: In [1], I have been sending a patch for controlling UNIX socket
> lookup, which is restricting connect() and sendmsg() operations for
> UNIX domain sockets of types SOCK_STREAM, SOCK_DGRAM and
> SOCK_SEQPACKET. I am bringing it up because it feels that the
> semantics for the UDP and UNIX datagram access rights hook in similar
> places and therefore should work similarly?
Thanks for bringing this up.
>
> In the current UNIX socket patch set (v2), there is only one Landlock
> access right which controls both connect() and sendmsg() when they are
> done on a UNIX datagram socket. This feels natural to be, because you
> can reach the same recipient address whether that is done with
> connect() or with sendmsg()...?
>
> (Was there a previous discussion where it was decided that these
> should be two different access rights for UDP sockets and UNIX dgram
> sockets?)
The rationale for these three access rights (connect, bind, and sendto)
is in the related commit message and it was discussed here:
https://lore.kernel.org/all/3631edfd-7f41-4ff1-9f30-20dcaa17b726@buffet.re/
Access rights for UNIX sockets can be simpler because we always know the
peer process, which is not the case for IP requests. For the later,
being able to filter on the socket type can help.
>
> [1] https://lore.kernel.org/all/20260101134102.25938-1-gnoack3000@gmail.com/
>
> Thanks,
> –Günther
>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v3 6/8] selftests/landlock: Add tests for UDP sendmsg
2025-12-12 16:37 ` [RFC PATCH v3 6/8] selftests/landlock: Add tests for UDP sendmsg Matthieu Buffet
@ 2026-02-01 16:19 ` Tingmao Wang
0 siblings, 0 replies; 13+ messages in thread
From: Tingmao Wang @ 2026-02-01 16:19 UTC (permalink / raw)
To: Matthieu Buffet
Cc: Mickaël Salaün, Günther Noack,
linux-security-module, Mikhail Ivanov, konstantin.meskhidze,
netdev
Hi Matthieu,
I noticed in passing that
On 12/12/25 16:37, Matthieu Buffet wrote:
> [...]
> + EXPECT_EQ(-EACCES, sendto_variant(sock_fd, &self->srv0, "A", 1, 0));
> + EXPECT_EQ(0, matches_log_prot(self->audit_fd, "net.sendto_udp", "daddr",
> + variant->addr, "dest"));
net.sendto_udp should probably be net\.sendto_udp
> +
> + EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
> + EXPECT_EQ(0, records.access);
> + EXPECT_EQ(1, records.domain);
> +
> + EXPECT_EQ(-EACCES, sendto_variant(sock_fd, &self->unspec_srv0, "B", 1, 0));
> + EXPECT_EQ(0, matches_log_prot(self->audit_fd, "net.sendto_udp", "daddr",
> + NULL, "dest"));
and same here
> +
> + EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
> + EXPECT_EQ(0, records.access);
> + EXPECT_EQ(0, records.domain);
> +
> + EXPECT_EQ(0, close(sock_fd));
> +}
> +
> TEST_HARNESS_MAIN
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v3 0/8] landlock: Add UDP access control support
2025-12-12 16:36 [RFC PATCH v3 0/8] landlock: Add UDP access control support Matthieu Buffet
` (8 preceding siblings ...)
2026-01-11 21:23 ` [RFC PATCH v3 0/8] landlock: Add UDP access control support Günther Noack
@ 2026-02-14 10:34 ` Mickaël Salaün
9 siblings, 0 replies; 13+ messages in thread
From: Mickaël Salaün @ 2026-02-14 10:34 UTC (permalink / raw)
To: Matthieu Buffet, Paul Moore
Cc: Günther Noack, linux-security-module, Mikhail Ivanov,
konstantin.meskhidze, netdev, Tingmao Wang, Justin Suess
I had a chat with Matthieu and here is a summary of our discussion
(Matthieu, please complete if necessary):
First, this patch series is hight quality. The remaining issues are
related to the semantic and the capabilities of the enforced
restrictions.
1/ Simplify access rights
It would make more sense to have only two access rights, one to set the
source port and another to set the destination port. The source port is
should be handled by LANDLOCK_ACCESS_NET_BIND_UDP, but the destination
port is should also be handle by a unique access right, something like
LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP because it can be set with
connect(2) or sendmsg(2). Any suggestion for a better name?
2/ Autobind handling
If an UDP socket is not explicitly binded to a port, inet_autobind() is
called and provides an implicit port. In this case, the socket_bind
hook is not called and we cannot enforce any kind of restriction
(without relying on Netfilter like SELinux does).
The idea would be to lazily detect previous autobinds when one of the
socket_bind, socket_connect, socket_sendmsg, or socket_recvmsg hook is
called. This would mean to attach a Landlock security blob to all
sockets and store their state there. A better alternative would be to
only rely on the struct socket to infer this information, but we're not
sure if this is possible.
3/ AF_UNSPEC trick
As explained in patch 5/8, the network stack behaves differntly when
AF_UNSPEC is used with sendmsg(2) against an IPv4 or an IPv6 socket.
In a nutshell, because the LSM hook is not called while the socket is
locked, there is a possible race condition that Landlock needs to
handle to avoid policy bypass.
It's not clear yet what can be implemented safely but one new solution
would be to implement a socket_setsockopt hook to detect (potentially
concurrent) change of a socket type and store in a new Landlock socket
security blob the state of the socket (e.g. if it was IPv6 and it is now
IPv4). This would enables us to detect a malicious race condition while
allowing legitimate use cases.
When such deny would be triggered, Landlock should log that, probably
with a new dedicated blocker. One reason is because this should help
users to understand the issue (which should be very rare), and another
reason is because audit_log_lsm_data() currently ignores
LSM_AUDIT_DATA_NET records if the socket family is not AF_INET* nor
AF_UNIX.
Paul, not sure we'll need it now but, do you think it would be OK to add
a new case for LSM_AUDIT_DATA_NET to log the socket family for
AF_INET/AF_INET6/AF_UNIX and also for currently unhandled families?
Something like " family=%x" .
On Fri, Dec 12, 2025 at 05:36:56PM +0100, Matthieu Buffet wrote:
> Hi Mickaël, Günther, Mikhail, Konstantin,
>
> Here is v3 of UDP support for Landlock. My apologies for the delay, I've
> had to deal with unrelated problems. All feedback from v1/v2 should be
> merged, thanks again for taking the time to review them.
>
> I based these patches on linux-mic/next commit 1a3cedbdc156 ("landlock:
> Fix wrong type usage") plus my previous patch "landlock: Fix TCP
> handling of short AF_UNSPEC addresses" to avoid adding UDP with already
> known bugs, duplicated from TCP. I waited a bit to get feedback on that
> patch and no one yelled, so I hope it's acceptable, tell me if it's not.
> Link: https://lore.kernel.org/linux-security-module/20251027190726.626244-4-matthieu@buffet.re/
>
> Changes since v2
> ================
> Link: https://lore.kernel.org/all/20241214184540.3835222-1-matthieu@buffet.re/
> - removed support for sending datagrams with explicit destination
> address of family AF_UNSPEC, which allowed to bypass restrictions with
> a race condition
> - rebased on linux-mic/next => add support for auditing
> - fixed mistake in selftests when using unspec_srv variables, which were
> implicitly of type SOCK_STREAM and did not actually test UDP code
> - add tests for IPPROTO_IP
> - improved docs, split off TCP-related refactoring into another commit
>
> Changes since v1
> ================
> Link: https://lore.kernel.org/all/20240916122230.114800-1-matthieu@buffet.re/
> - recvmsg hook is gone and sendmsg hook doesn't apply to connected
> sockets anymore, to improve performance
> - don't add a get_addr_port() helper function, which required a weird
> "am I in IPv4 or IPv6 context" to avoid a addrlen > sizeof(struct
> sockaddr_in) check in connect(AF_UNSPEC) IPv6 context. A helper was
> useful when ports also needed to be read in a recvmsg() hook, now it
> is just a simple switch case in the sendmsg() hook, more readable
> - rename sendmsg access right to LANDLOCK_ACCESS_NET_UDP_SENDTO
> - reorder hook prologue for consistency: check domain, then type and
> family
> - add additional selftests cases around minimal address length
> - update documentation
>
> All important cases should have a selftest now. lcov gives me net.c
> going from 91.9% lines/82.5% branches to 93.4% lines/87% branches.
> Thank you for taking the time to read this!
>
> Closes: https://github.com/landlock-lsm/linux/issues/10
>
> Matthieu Buffet (8):
> landlock: Minor reword of docs for TCP access rights
> landlock: Refactor TCP socket type check
> landlock: Add UDP bind+connect access control
> selftests/landlock: Add UDP bind/connect tests
> landlock: Add UDP sendmsg access control
> selftests/landlock: Add tests for UDP sendmsg
> samples/landlock: Add sandboxer UDP access control
> landlock: Add documentation for UDP support
>
> Documentation/userspace-api/landlock.rst | 94 ++-
> include/uapi/linux/landlock.h | 46 +-
> samples/landlock/sandboxer.c | 58 +-
> security/landlock/audit.c | 3 +
> security/landlock/limits.h | 2 +-
> security/landlock/net.c | 119 +++-
> security/landlock/syscalls.c | 2 +-
> tools/testing/selftests/landlock/base_test.c | 2 +-
> tools/testing/selftests/landlock/net_test.c | 691 ++++++++++++++++---
> 9 files changed, 869 insertions(+), 148 deletions(-)
>
>
> base-commit: 1a3cedbdc156e100eb1a5208a8562a3265c35d87
> prerequisite-patch-id: 22051d5d4076a87481b22798c127ce84e219ca97
> prerequisite-patch-id: 37a1b44596a2d861ba91989edb1d7aac005931d6
> prerequisite-patch-id: c7be1c906699a2590ab7112cdf2ab6892178ec07
> --
> 2.47.3
>
>
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-02-14 10:34 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-12-12 16:36 [RFC PATCH v3 0/8] landlock: Add UDP access control support Matthieu Buffet
2025-12-12 16:36 ` [RFC PATCH v3 1/8] landlock: Minor reword of docs for TCP access rights Matthieu Buffet
2025-12-12 16:36 ` [RFC PATCH v3 2/8] landlock: Refactor TCP socket type check Matthieu Buffet
2025-12-12 16:36 ` [RFC PATCH v3 3/8] landlock: Add UDP bind+connect access control Matthieu Buffet
2025-12-12 16:37 ` [RFC PATCH v3 4/8] selftests/landlock: Add UDP bind/connect tests Matthieu Buffet
2025-12-12 16:37 ` [RFC PATCH v3 5/8] landlock: Add UDP sendmsg access control Matthieu Buffet
2025-12-12 16:37 ` [RFC PATCH v3 6/8] selftests/landlock: Add tests for UDP sendmsg Matthieu Buffet
2026-02-01 16:19 ` Tingmao Wang
2025-12-12 16:37 ` [RFC PATCH v3 7/8] samples/landlock: Add sandboxer UDP access control Matthieu Buffet
2025-12-12 16:37 ` [RFC PATCH v3 8/8] landlock: Add documentation for UDP support Matthieu Buffet
2026-01-11 21:23 ` [RFC PATCH v3 0/8] landlock: Add UDP access control support Günther Noack
2026-01-12 16:03 ` Mickaël Salaün
2026-02-14 10:34 ` Mickaël Salaün
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox