From: Matthieu Buffet <matthieu@buffet.re>
To: "Mickaël Salaün" <mic@digikod.net>
Cc: "Günther Noack" <gnoack@google.com>,
linux-security-module@vger.kernel.org,
"Mikhail Ivanov" <ivanov.mikhail1@huawei-partners.com>,
konstantin.meskhidze@huawei.com, netdev@vger.kernel.org,
"Matthieu Buffet" <matthieu@buffet.re>
Subject: [RFC PATCH v3 5/8] landlock: Add UDP sendmsg access control
Date: Fri, 12 Dec 2025 17:37:01 +0100 [thread overview]
Message-ID: <20251212163704.142301-6-matthieu@buffet.re> (raw)
In-Reply-To: <20251212163704.142301-1-matthieu@buffet.re>
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
next prev parent reply other threads:[~2025-12-12 16:37 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
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 ` Matthieu Buffet [this message]
2025-12-12 16:37 ` [RFC PATCH v3 6/8] selftests/landlock: Add tests for UDP sendmsg Matthieu Buffet
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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20251212163704.142301-6-matthieu@buffet.re \
--to=matthieu@buffet.re \
--cc=gnoack@google.com \
--cc=ivanov.mikhail1@huawei-partners.com \
--cc=konstantin.meskhidze@huawei.com \
--cc=linux-security-module@vger.kernel.org \
--cc=mic@digikod.net \
--cc=netdev@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).