[RFC PATCH v1 3/7] landlock: Add UDP bind+connect access control
Matthieu Buffet
matthieu at buffet.re
Mon Sep 16 12:22:26 UTC 2024
Add support for two more access rights:
- LANDLOCK_ACCESS_NET_CONNECT_UDP, to gate the possibility to connect()
an inet SOCK_DGRAM socket. This will be used by some client applications
(those who want to avoid specifying a destination for each datagram in
sendmsg), and for a few servers (those creating a socket per-client, who
want to only receive traffic from each client on these sockets)
- LANDLOCK_ACCESS_NET_BIND_UDP, to gate the possibility to bind() an
inet SOCK_DGRAM socket. This will be required for most server
applications (to start listening for datagrams on a non-ephemeral
port) and can be useful for some client applications (to set the
source port of future datagrams)
Also bump the ABI version from 5 to 6 so that userland can detect
whether these rights are supported and actually use them.
Signed-off-by: Matthieu Buffet <matthieu at buffet.re>
---
include/uapi/linux/landlock.h | 48 +++++++++++++++++++++++--------
security/landlock/limits.h | 2 +-
security/landlock/net.c | 54 ++++++++++++++++++++++++++---------
security/landlock/syscalls.c | 2 +-
4 files changed, 79 insertions(+), 27 deletions(-)
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index 2c8dbc74b955..7f9aa1cd2912 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -113,12 +113,15 @@ struct landlock_net_port_attr {
*
* It should be noted that port 0 passed to :manpage:`bind(2)` will bind
* to an available port from the ephemeral port range. This can be
- * configured with the ``/proc/sys/net/ipv4/ip_local_port_range`` sysctl
- * (also used for IPv6).
+ * configured globally with the
+ * ``/proc/sys/net/ipv4/ip_local_port_range`` sysctl (also used for
+ * IPv6), and on a per-socket basis using
+ * ``setsockopt(IP_LOCAL_PORT_RANGE)``.
*
* A Landlock rule with port 0 and the ``LANDLOCK_ACCESS_NET_BIND_TCP``
- * right means that requesting to bind on port 0 is allowed and it will
- * automatically translate to binding on the related port range.
+ * or ``LANDLOCK_ACCESS_NET_BIND_UDP`` right means that requesting to
+ * bind on port 0 is allowed and it will automatically translate to
+ * binding on the related port range.
*/
__u64 port;
};
@@ -261,17 +264,38 @@ struct landlock_net_port_attr {
* Network flags
* ~~~~~~~~~~~~~~~~
*
- * These flags enable to restrict a sandboxed process to a set of network
- * actions. This is supported since the Landlock ABI version 4.
- *
- * The following access rights apply to TCP port numbers:
- *
- * - %LANDLOCK_ACCESS_NET_BIND_TCP: Bind a TCP socket to a local port.
- * - %LANDLOCK_ACCESS_NET_CONNECT_TCP: Connect an active TCP socket to
- * a remote port.
+ * These flags enable to restrict which network-related actions a sandboxed
+ * process can take. TCP support was added in Landlock ABI version 4, and UDP
+ * support in version 6.
+ *
+ * TCP access rights:
+ * - %LANDLOCK_ACCESS_NET_BIND_TCP: bind sockets to the given local port,
+ * for servers that will listen on that port
+ * - %LANDLOCK_ACCESS_NET_CONNECT_TCP: connect sockets to the given remote port,
+ * to establish client connections to servers listening on that port
+ *
+ * UDP access rights:
+ * - %LANDLOCK_ACCESS_NET_BIND_UDP: bind sockets to the given local port,
+ * either for servers that will listen on that port, or for clients wishing
+ * to set the source port of datagrams they will send instead of using a
+ * kernel-assigned random ephemeral port (some protocols require a specific
+ * source port, e.g. mDNS with UDP/5353)
+ * - %LANDLOCK_ACCESS_NET_CONNECT_UDP: connect sockets to the given remote port,
+ * either for clients that will send datagrams to that destination (and want
+ * to send them faster, without specifying an explicit address every time),
+ * or for servers that want to filter which client address they want to
+ * receive datagrams from (if you create a client-specific socket for a
+ * client-specific process, e.g. using the established-over-unconnected
+ * method)
+ *
+ * Note that ``bind(0)`` means binding to an ephemeral kernel-assigned port,
+ * in the range configured in ``/proc/sys/net/ipv4/ip_local_port_range``
+ * globally (or on a per-socket basis with ``setsockopt(IP_LOCAL_PORT_RANGE)``).
*/
/* 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)
/* clang-format on */
#endif /* _UAPI_LINUX_LANDLOCK_H */
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index 4eb643077a2a..182b6a8d2976 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -22,7 +22,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_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 c8bcd29bde09..becc62c02cc9 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -79,8 +79,8 @@ static int current_check_access_socket(struct socket *const sock,
if (WARN_ON_ONCE(dom->num_layers < 1))
return -EACCES;
- /* Checks if it's a (potential) TCP socket. */
- if (sock->type != SOCK_STREAM)
+ /* Checks if it's a (potential) UDP or TCP socket. */
+ if (sock->type != SOCK_STREAM && sock->type != SOCK_DGRAM)
return 0;
/* Checks for minimal header length to safely read sa_family. */
@@ -110,17 +110,18 @@ static int current_check_access_socket(struct socket *const sock,
/* Specific AF_UNSPEC handling. */
if (address->sa_family == AF_UNSPEC) {
/*
- * 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.
+ * Connecting to an address with AF_UNSPEC dissolves the socket
+ * association. For SOCK_STREAM, it has the same effect as closing
+ * the connection while retaining the socket object (i.e., the
+ * file descriptor). For SOCK_DGRAM, it removes any configured
+ * destination address. Both cases remove accessible resources, so
+ * they are always legitimate and allowed, like dropping any
+ * privilege.
* Let the network stack handle potential inconsistencies and
* return -EINVAL if needed.
*/
- if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP)
+ if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP ||
+ access_request == LANDLOCK_ACCESS_NET_CONNECT_UDP)
return 0;
/*
@@ -134,7 +135,8 @@ static int current_check_access_socket(struct socket *const sock,
* checks, but it is safer to return a proper error and test
* consistency thanks to kselftest.
*/
- if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
+ if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP ||
+ access_request == LANDLOCK_ACCESS_NET_BIND_UDP) {
/* addrlen has already been checked for AF_UNSPEC. */
const struct sockaddr_in *const sockaddr =
(struct sockaddr_in *)address;
@@ -175,16 +177,42 @@ static int current_check_access_socket(struct socket *const sock,
static int hook_socket_bind(struct socket *const sock,
struct sockaddr *const address, const int addrlen)
{
+ access_mask_t access_request;
+
+ /*
+ * Check if it's a (potential) TCP or UDP socket. These checks could
+ * match e.g. Unix, netlink, or udplite sockets.
+ */
+ if (sock->type == SOCK_STREAM)
+ access_request = LANDLOCK_ACCESS_NET_BIND_TCP;
+ else if (sock->type == SOCK_DGRAM)
+ access_request = LANDLOCK_ACCESS_NET_BIND_UDP;
+ else
+ return 0;
+
return current_check_access_socket(sock, address, addrlen,
- LANDLOCK_ACCESS_NET_BIND_TCP);
+ access_request);
}
static int hook_socket_connect(struct socket *const sock,
struct sockaddr *const address,
const int addrlen)
{
+ access_mask_t access_request;
+
+ /*
+ * Check if it's a (potential) TCP or UDP socket. These checks could
+ * match e.g. Unix, netlink, or udplite sockets.
+ */
+ if (sock->type == SOCK_STREAM)
+ access_request = LANDLOCK_ACCESS_NET_CONNECT_TCP;
+ else if (sock->type == SOCK_DGRAM)
+ access_request = LANDLOCK_ACCESS_NET_CONNECT_UDP;
+ else
+ return 0;
+
return current_check_access_socket(sock, address, addrlen,
- LANDLOCK_ACCESS_NET_CONNECT_TCP);
+ access_request);
}
static struct security_hook_list landlock_hooks[] __ro_after_init = {
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index ccc8bc6c1584..328198e8a9f5 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -149,7 +149,7 @@ static const struct file_operations ruleset_fops = {
.write = fop_dummy_write,
};
-#define LANDLOCK_ABI_VERSION 5
+#define LANDLOCK_ABI_VERSION 6
/**
* sys_landlock_create_ruleset - Create a new ruleset
--
2.39.5
More information about the Linux-security-module-archive
mailing list