diff options
| author | Mark Brown <broonie@kernel.org> | 2026-05-30 00:25:53 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-05-30 00:25:53 +0100 |
| commit | cd9f27bc50e764e91f3014a851d97872a7fd6544 (patch) | |
| tree | b04afc6e4aa509a08c8918cac9017ce383dd6b95 /security | |
| parent | faa2be6c7cb758bb167e23978bbf3f93a1740b8c (diff) | |
| parent | fe7832557561ed6312563368854d5f8df1fa55e3 (diff) | |
| download | linux-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.c | 3 | ||||
| -rw-r--r-- | security/landlock/domain.c | 9 | ||||
| -rw-r--r-- | security/landlock/domain.h | 5 | ||||
| -rw-r--r-- | security/landlock/limits.h | 2 | ||||
| -rw-r--r-- | security/landlock/net.c | 162 | ||||
| -rw-r--r-- | security/landlock/syscalls.c | 2 |
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 |
