aboutsummaryrefslogtreecommitdiffstats
path: root/security
diff options
authorMark Brown <broonie@kernel.org>2026-05-30 00:25:53 +0100
committerMark Brown <broonie@kernel.org>2026-05-30 00:25:53 +0100
commitcd9f27bc50e764e91f3014a851d97872a7fd6544 (patch)
treeb04afc6e4aa509a08c8918cac9017ce383dd6b95 /security
parentfaa2be6c7cb758bb167e23978bbf3f93a1740b8c (diff)
parentfe7832557561ed6312563368854d5f8df1fa55e3 (diff)
downloadlinux-next-history-cd9f27bc50e764e91f3014a851d97872a7fd6544.tar.gz
Merge branch 'next' of https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git
Diffstat (limited to 'security')
-rw-r--r--security/landlock/audit.c3
-rw-r--r--security/landlock/domain.c9
-rw-r--r--security/landlock/domain.h5
-rw-r--r--security/landlock/limits.h2
-rw-r--r--security/landlock/net.c162
-rw-r--r--security/landlock/syscalls.c2
6 files changed, 152 insertions, 31 deletions
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index 8d0edf94037d7..851647197a010 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -45,6 +45,9 @@ 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_SEND_UDP)] =
+ "net.connect_send_udp",
};
static_assert(ARRAY_SIZE(net_access_strings) == LANDLOCK_NUM_ACCESS_NET);
diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index 06b6bd845060f..5dd06f7c2312f 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -90,11 +90,12 @@ static struct landlock_details *get_current_details(void)
return ERR_CAST(buffer);
/*
- * Create the new details according to the path's length. Do not
- * allocate with GFP_KERNEL_ACCOUNT because it is independent from the
- * caller.
+ * Create the new details according to the path's length. Account
+ * to the calling task's memcg, like the other Landlock per-domain
+ * allocations, even if it may not control the related size.
*/
- details = kzalloc_flex(*details, exe_path, path_size);
+ details =
+ kzalloc_flex(*details, exe_path, path_size, GFP_KERNEL_ACCOUNT);
if (!details)
return ERR_PTR(-ENOMEM);
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index a9d57db0120dc..35cac8f6daee7 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -33,10 +33,7 @@ enum landlock_log_status {
* Rarely accessed, mainly when logging the first domain's denial.
*
* The contained pointers are initialized at the domain creation time and never
- * changed again. Contrary to most other Landlock object types, this one is
- * not allocated with GFP_KERNEL_ACCOUNT because its size may not be under the
- * caller's control (e.g. unknown exe_path) and the data is not explicitly
- * requested nor used by tasks.
+ * changed again.
*/
struct landlock_details {
/**
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index b454ad73b15e8..a4d908b240a23 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_SEND_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 c368649985c53..db2046a89a9a8 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -44,7 +44,8 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
static int current_check_access_socket(struct socket *const sock,
struct sockaddr *const address,
const int addrlen,
- access_mask_t access_request)
+ access_mask_t access_request,
+ bool connecting)
{
__be16 port;
struct layer_access_masks layer_masks = {};
@@ -68,28 +69,61 @@ 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_SEND_UDP &&
+ connecting)) {
/*
* 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_CONNECT_SEND_UDP) {
+ if (sock->sk->__sk_common.skc_family == AF_INET6) {
+ /*
+ * We cannot allow sending UDP datagrams to an
+ * explicit AF_UNSPEC address on IPv6 sockets,
+ * even if AF_UNSPEC is treated as "no address"
+ * on such sockets (so it should always be allowed).
+ * That's because the socket's family can change under
+ * our feet (if another thread calls setsockopt(IPV6_ADDRFORM))
+ * to IPv4, which would then treat AF_UNSPEC as
+ * AF_INET.
+ */
+ 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,
+ });
+ return -EACCES;
+ }
+ } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP ||
+ access_request == LANDLOCK_ACCESS_NET_BIND_UDP) {
/*
* Binding to an AF_UNSPEC address is treated
* differently by IPv4 and IPv6 sockets. The socket's
* family may change under our feet due to
* setsockopt(IPV6_ADDRFORM), but that's ok: we either
- * reject entirely or require
- * %LANDLOCK_ACCESS_NET_BIND_TCP for the given port, so
- * it cannot be used to bypass the policy.
+ * reject entirely for IPv6 or require
+ * %LANDLOCK_ACCESS_NET_BIND_TCP or
+ * %LANDLOCK_ACCESS_NET_BIND_UDP for IPv4,
+ * so it cannot be used to bypass the policy.
*
* IPv4 sockets map AF_UNSPEC to AF_INET for
* retrocompatibility for bind accesses, only if the
@@ -121,7 +155,10 @@ static int current_check_access_socket(struct socket *const sock,
} else {
WARN_ON_ONCE(1);
}
- /* Only for bind(AF_UNSPEC+INADDR_ANY) on IPv4 socket. */
+ /*
+ * For bind(AF_UNSPEC+INADDR_ANY) on IPv4 socket and
+ * for sending to AF_UNSPEC addresses on IPv4 socket.
+ */
fallthrough;
case AF_INET: {
const struct sockaddr_in *addr4;
@@ -132,10 +169,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_SEND_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 +193,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_SEND_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 {
@@ -198,6 +239,7 @@ static int current_check_access_socket(struct socket *const sock,
return 0;
audit_net.family = address->sa_family;
+ audit_net.sk = sock->sk;
landlock_log_denial(subject,
&(struct landlock_request){
.type = LANDLOCK_REQUEST_NET_ACCESS,
@@ -209,6 +251,50 @@ static int current_check_access_socket(struct socket *const sock,
return -EACCES;
}
+static int current_check_autobind_udp_socket(struct socket *const sock)
+{
+ struct sockaddr_storage port0 = { 0 };
+
+ /*
+ * On UDP sockets, if a local port has not already been bound,
+ * calling connect() or sending a first datagram has the side
+ * effect of autobinding an ephemeral port: we also have to check
+ * that the process would have had the right to bind(0) explicitly.
+ * Note: socket is not locked, so another thread could do an
+ * explicit bind(!=0) on this socket, changing inet_num to non-zero
+ * after we read it, but this would only have us enforce an
+ * additional bind(0) access check and would not bypass policy.
+ */
+ if (inet_sk(sock->sk)->inet_num != 0)
+ return 0;
+
+ /*
+ * Construct a struct sockaddr* with port 0 to pretend the
+ * process tried to bind() on that address.
+ */
+ port0.ss_family = sock->sk->__sk_common.skc_family;
+ switch (port0.ss_family) {
+ case AF_INET: {
+ ((struct sockaddr_in *)&port0)->sin_port = 0;
+ break;
+ }
+
+#if IS_ENABLED(CONFIG_IPV6)
+ case AF_INET6: {
+ ((struct sockaddr_in6 *)&port0)->sin6_port = 0;
+ break;
+ }
+#endif /* IS_ENABLED(CONFIG_IPV6) */
+
+ default:
+ return 0;
+ }
+
+ return current_check_access_socket(sock, (struct sockaddr *)&port0,
+ sizeof(port0),
+ LANDLOCK_ACCESS_NET_BIND_UDP, false);
+}
+
static int hook_socket_bind(struct socket *const sock,
struct sockaddr *const address, const int addrlen)
{
@@ -216,11 +302,13 @@ 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;
return current_check_access_socket(sock, address, addrlen,
- access_request);
+ access_request, false);
}
static int hook_socket_connect(struct socket *const sock,
@@ -228,19 +316,51 @@ static int hook_socket_connect(struct socket *const sock,
const int addrlen)
{
access_mask_t access_request;
+ int ret = 0;
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_SEND_UDP;
else
return 0;
- return current_check_access_socket(sock, address, addrlen,
- access_request);
+ ret = current_check_access_socket(sock, address, addrlen,
+ access_request, true);
+
+ if (ret == 0 && sk_is_udp(sock->sk))
+ ret = current_check_autobind_udp_socket(sock);
+
+ return ret;
+}
+
+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;
+ int ret = 0;
+
+ if (sk_is_udp(sock->sk))
+ access_request = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP;
+ else
+ return 0;
+
+ if (address != NULL)
+ ret = current_check_access_socket(sock, address, addrlen,
+ access_request, false);
+
+ if (ret == 0)
+ ret = current_check_autobind_udp_socket(sock);
+
+ return ret;
}
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)
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index accfd2e5a0cd4..d45469d5d4644 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -166,7 +166,7 @@ static const struct file_operations ruleset_fops = {
* If the change involves a fix that requires userspace awareness, also update
* the errata documentation in Documentation/userspace-api/landlock.rst .
*/
-const int landlock_abi_version = 9;
+const int landlock_abi_version = 10;
/**
* sys_landlock_create_ruleset - Create a new ruleset