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