[RFC PATCH v3 5/8] landlock: Add UDP sendmsg access control
Matthieu Buffet
matthieu at buffet.re
Fri Dec 12 16:37:01 UTC 2025
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 at 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
More information about the Linux-security-module-archive
mailing list