linux-security-module.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
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


  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).