From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-wm1-f53.google.com (mail-wm1-f53.google.com [209.85.128.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0F28E31AF2D for ; Sun, 30 Aug 2026 20:17:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.53 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788121030; cv=none; b=Cr/WCOB0Wy/ZQB7jajEzPdI9HqgQTUXvR6F1966c/Z3joWBfv+r1PvoRWjkd0JQ81PtQneCHWqQiPf09ixtY64MynDwvgrDoNXPJd5pc2PnOGmGlepnBl9K5oScFfNt8qzw8h5n1hB8mq9kWHOEZCnSW1ilwKRvUz8mK1CqV58I= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788121030; c=relaxed/simple; bh=3HlGz5FZAf5YiW0SZUDdJqY51I4PRzoZ+1IAMBTh0S0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=RL/ppjsmZAX61dKgZXlhs9mKJkrKQomwwoAU/S6jdSeYyUlmAXP1rr9cdhgRDAMJprQENggWT47kQo+Y4QPHobo4pkOa2t6RY6NweR5VR723MucnTKXIj3znVbw8045Rve1cMTf5GSc13w7Sj3uXSHP1kGLGUc5kRlVb/2z1aak= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=W6GA5XdG; arc=none smtp.client-ip=209.85.128.53 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="W6GA5XdG" Received: by mail-wm1-f53.google.com with SMTP id 5b1f17b1804b1-499ae1c6471so20139795e9.3 for ; Sun, 30 Aug 2026 13:17:07 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788121026; x=1788725826; darn=vger.kernel.org; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=PCMfevEQD6EgJt39HDJsajq+HZlaKHKHiTDnNP3c2jg=; b=W6GA5XdG2bEXRf3VtQfDkXRAiUc9Kkbn1vROh3+s9ngGZnp2WEPWXIh5aqS8hCGKmV qt644SrDCrCSWGvubWTWathI1rxecYu4OIISHOzNEsEdj3c6HwsoY5KcozMNfGe3rv9U N/2DYN/9T5LqjLk1eINYr5KciJk4ViGnnUG4WciYYk1qqOV/t2nRw2ZcAd6IQToB4eUr SsuB7uCF7oOJDBXxclux1LXw+RRvk97ZqHrGDWqvWfwLRokOYJjpJTC0Na243CE4rAPU mGhUE8VxJgn/H+LfKQUDplTo/9SvRa/Htcj+JEEh4oTmRF0ekKQuTYbObBw4a3URYIpK C5TA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788121026; x=1788725826; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=PCMfevEQD6EgJt39HDJsajq+HZlaKHKHiTDnNP3c2jg=; b=rXTp1+RCCvZmojYDJBwZOT0rfw3tCaNSO36qAxpa7YDKav3HnrMha8m5hZB536rKwg j6xidagOtVyioyRpYOxwBvpqI7W1CZ8/vRxLK2Dp9fPACag3OJYMzOeDWAyH2VLToQ53 1l0SdpNouX5ldgIp5cFG7IvzknxoCu5dbeIgwA4yIzau3djLhj6HBRqSLm9IHLIJfAM2 +kr9UyG/1UQelqMu4BSuxER9F+gt/adHBpfMQm2rFYdHu2Bq5y0PGnPOiqEtX+EMyPmI AMZfo6LgDxlOjARhxYPp4vbS1p77PzID+N7Bu4Bj7lHG5dGDSe03WfDCCa+B6AX7uwTp NWdg== X-Forwarded-Encrypted: i=1; AHgh+RpbpchCbp7a5/5MyfQy8mCjKG8e5rMw45HsxCY4/icjw0hoU47UPGl+mMnZOUfKgm7F36p/L+g=@vger.kernel.org X-Gm-Message-State: AFuF++mDnlAvbUvoqUDhCDFYlVruK+p9KubvG3qAqgdBJCVCfxX78LyA sIevdwLKx/qRy4nuEZjdyg9HM0qGtjoSpzi9MG/EFrBZwHsCRfBUhSPD X-Gm-Gg: AR+sD12bHeVotBKsNZfI5u7joWw3i4kY4lBbsdNYowfrDsqw9nmn6he1aL/iTNpmngg fm/IIPTq1NOyKSkGwV6qWKop2EIC22pP7zG/a6KM7Bq6WCuvyDUn537uZDpoqxSsSJ339Nc5Rl+ 9dAv+JvC0tmAb6G1pupO+lGVlXgJMJPjMFSOBSoYJN807Gj5UHtp0dnJM5PpOuTL35jFoV9qDgX dY1bz4OGrD5UodRATbPfi40/KTZr9U8ackxx9fbDZorVVjjV8oPlAqIjNlBq7M5Mr6A55oOUK2U PNO/BBZkqL6sxfK+TO7+rKEtilScphD+9GSvpmAsk/bvfsYC5DZQt96I9YvqAcKN/6MEipnfx0N vYFDGlfUJZl1ovjMlA0gCM8K0pzMWXCbvcFTUOQSPm11RVlpJeMOlUE86CxqbUQsw8MMtmY38qN 2yICPIxeELVjjFS8bhEPWDwni55vW+bJkWjua/RTDMOiJMegPbAoW/WnyNWKoO3muK5YqjEnzoH mRSxXI6V9mXTQ== X-Received: by 2002:a05:600c:1f8f:b0:499:b65d:1250 with SMTP id 5b1f17b1804b1-49b91c1fc4bmr290143725e9.2.1788121025986; Sun, 30 Aug 2026 13:17:05 -0700 (PDT) Received: from localhost (ip87-106-108-193.pbiaas.com. [87.106.108.193]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49cd2e9d9fcsm129432455e9.1.2026.08.30.13.17.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 30 Aug 2026 13:17:05 -0700 (PDT) From: =?UTF-8?q?G=C3=BCnther=20Noack?= To: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Cc: Matthieu Baerts , Mat Martineau , Geliang Tang , Mikhail Ivanov , mptcp@lists.linux.dev, netdev@vger.kernel.org, linux-security-module@vger.kernel.org, =?UTF-8?q?G=C3=BCnther=20Noack?= Subject: [PATCH 3/6] landlock: Add MPTCP bind and connect access rights Date: Sun, 30 Aug 2026 22:16:47 +0200 Message-ID: <20260830201650.67050-4-gnoack3000@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260830201650.67050-1-gnoack3000@gmail.com> References: <20260830201650.67050-1-gnoack3000@gmail.com> Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MPTCP sockets have equivalent bind(2) and connect(2) operations as TCP sockets, but can not currently be restricted with Landlock without explicit MPTCP access rights. As MPTCP operates on the same TCP port number space as TCP, this is a gap in Landlock's policies. Add access rights for MPTCP bind(2) and connect(2) operations and document them in the header. Treat TCP Fast Open the same as done for plain TCP in commit 33cb713db016 ("landlock: Fix TCP Fast Open connection bypass") The port numbers used in MPTCP subflows are negotiated by the kernel and therefore not subject to these access rights. Bump the Landlock ABI version to 12. Closes: https://github.com/landlock-lsm/linux/issues/54 Signed-off-by: Günther Noack --- include/linux/landlock.h | 5 +- include/uapi/linux/landlock.h | 24 +++++++ security/landlock/limits.h | 2 +- security/landlock/net.c | 68 ++++++++++++++------ security/landlock/syscalls.c | 2 +- tools/testing/selftests/landlock/base_test.c | 2 +- 6 files changed, 79 insertions(+), 24 deletions(-) diff --git a/include/linux/landlock.h b/include/linux/landlock.h index 004cbd0b9298..b04ffc7caa21 100644 --- a/include/linux/landlock.h +++ b/include/linux/landlock.h @@ -46,7 +46,10 @@ _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_NET_CONNECT_TCP, "connect_tcp"), \ _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_NET_BIND_UDP, "bind_udp"), \ _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP, \ - "connect_send_udp") + "connect_send_udp"), \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_NET_BIND_MPTCP, "bind_mptcp"), \ + _LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_NET_CONNECT_MPTCP, \ + "connect_mptcp") #define _LANDLOCK_SCOPE_NAMES \ _LANDLOCK_NAME_ENTRY(LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET, \ diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h index cceda3b3b961..2a953ba7ce25 100644 --- a/include/uapi/linux/landlock.h +++ b/include/uapi/linux/landlock.h @@ -448,6 +448,9 @@ struct landlock_net_port_attr { * - %LANDLOCK_ACCESS_NET_CONNECT_TCP: Connect TCP sockets to the given * remote port. Support added in Landlock ABI version 4. * + * .. note:: These rights do not apply to MPTCP sockets, which have their own + * access rights (see below). + * * And similarly for UDP port numbers: * * - %LANDLOCK_ACCESS_NET_BIND_UDP: Bind UDP sockets to the given local @@ -474,12 +477,33 @@ struct landlock_net_port_attr { * .. note:: Sending datagrams to an ``AF_UNSPEC`` destination address * family is not supported for IPv6 UDP sockets: you will need to use a * ``NULL`` address instead. + * + * MPTCP sockets (created with ``IPPROTO_MPTCP``) use TCP port numbers, but + * they are controlled by their own access rights: + * + * - %LANDLOCK_ACCESS_NET_BIND_MPTCP: Bind MPTCP sockets to the given local + * port. Support added in Landlock ABI version 12. + * - %LANDLOCK_ACCESS_NET_CONNECT_MPTCP: Connect MPTCP sockets to the given + * remote port. Support added in Landlock ABI version 12. + * + * .. note:: The TCP and the MPTCP access rights are independent, even though + * they refer to the same port number space. Handling only + * %LANDLOCK_ACCESS_NET_BIND_TCP and %LANDLOCK_ACCESS_NET_CONNECT_TCP leaves + * MPTCP sockets unrestricted, and vice versa. A sandbox that wants to + * control all TCP-based traffic needs to handle both sets. + * + * .. note:: These MPTCP access rights restrict the ports passed to + * :manpage:`bind(2)` and :manpage:`connect(2)`. The ports used in MPTCP + * subflows are negotiated in the MPTCP protocol by the kernel and are not + * subject to these restrictions. */ /* 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_SEND_UDP (1ULL << 3) +#define LANDLOCK_ACCESS_NET_BIND_MPTCP (1ULL << 4) +#define LANDLOCK_ACCESS_NET_CONNECT_MPTCP (1ULL << 5) /* clang-format on */ /** diff --git a/security/landlock/limits.h b/security/landlock/limits.h index 1a7c5fb8f6fd..d25e056b7ca2 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_SEND_UDP +#define LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_CONNECT_MPTCP #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 8f2aaac54b33..8541b0c07d64 100644 --- a/security/landlock/net.c +++ b/security/landlock/net.c @@ -11,6 +11,7 @@ #include #include #include +#include #include "common.h" #include "cred.h" @@ -53,6 +54,26 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset, return err; } +static bool sk_is_mptcp_socket(const struct sock *sk) +{ + return sk_is_inet(sk) && sk->sk_type == SOCK_STREAM && + sk->sk_protocol == IPPROTO_MPTCP; +} + +static bool is_connect_access(const access_mask_t access_request) +{ + return access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP || + access_request == LANDLOCK_ACCESS_NET_CONNECT_MPTCP || + access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP; +} + +static bool is_bind_access(const access_mask_t access_request) +{ + return access_request == LANDLOCK_ACCESS_NET_BIND_TCP || + access_request == LANDLOCK_ACCESS_NET_BIND_MPTCP || + access_request == LANDLOCK_ACCESS_NET_BIND_UDP; +} + static bool unmask_layers_net(const struct landlock_domain *const domain, const struct landlock_id id, struct layer_masks *masks, @@ -104,6 +125,7 @@ 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 || + access_request == LANDLOCK_ACCESS_NET_CONNECT_MPTCP || (access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP && connecting)) { /* @@ -147,17 +169,15 @@ static int current_check_access_socket(struct socket *const sock, }); return -EACCES; } - } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP || - access_request == LANDLOCK_ACCESS_NET_BIND_UDP) { + } else if (is_bind_access(access_request)) { /* * 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 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. + * reject entirely for IPv6 or require the relevant bind + * access right 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 @@ -204,12 +224,10 @@ 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 || - access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP) { + if (is_connect_access(access_request)) { audit_net.dport = port; audit_net.v4info.daddr = addr4->sin_addr.s_addr; - } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP || - access_request == LANDLOCK_ACCESS_NET_BIND_UDP) { + } else if (is_bind_access(access_request)) { audit_net.sport = port; audit_net.v4info.saddr = addr4->sin_addr.s_addr; } else { @@ -228,12 +246,10 @@ 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 || - access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP) { + if (is_connect_access(access_request)) { audit_net.dport = port; audit_net.v6info.daddr = addr6->sin6_addr; - } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP || - access_request == LANDLOCK_ACCESS_NET_BIND_UDP) { + } else if (is_bind_access(access_request)) { audit_net.sport = port; audit_net.v6info.saddr = addr6->sin6_addr; } else { @@ -331,6 +347,8 @@ 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_mptcp_socket(sock->sk)) + access_request = LANDLOCK_ACCESS_NET_BIND_MPTCP; else if (sk_is_udp(sock->sk)) access_request = LANDLOCK_ACCESS_NET_BIND_UDP; else @@ -349,6 +367,8 @@ static int hook_socket_connect(struct socket *const sock, if (sk_is_tcp(sock->sk)) access_request = LANDLOCK_ACCESS_NET_CONNECT_TCP; + else if (sk_is_mptcp_socket(sock->sk)) + access_request = LANDLOCK_ACCESS_NET_CONNECT_MPTCP; else if (sk_is_udp(sock->sk)) access_request = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP; else @@ -377,12 +397,20 @@ static int hook_socket_sendmsg(struct socket *const sock, access_mask_t access_request; int ret = 0; - if ((msg->msg_flags & MSG_FASTOPEN) && address && sk_is_tcp(sock->sk)) { - ret = current_check_access_socket( - sock, address, addrlen, LANDLOCK_ACCESS_NET_CONNECT_TCP, - true); - if (ret != 0) - return ret; + if ((msg->msg_flags & MSG_FASTOPEN) && address) { + access_mask_t fastopen_access = 0; + + if (sk_is_tcp(sock->sk)) + fastopen_access = LANDLOCK_ACCESS_NET_CONNECT_TCP; + else if (sk_is_mptcp_socket(sock->sk)) + fastopen_access = LANDLOCK_ACCESS_NET_CONNECT_MPTCP; + + if (fastopen_access) { + ret = current_check_access_socket( + sock, address, addrlen, fastopen_access, true); + if (ret != 0) + return ret; + } } if (sk_is_udp(sock->sk)) diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 1d02d57f4c48..cc54d4f1d502 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -172,7 +172,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 = 11; +const int landlock_abi_version = 12; /** * sys_landlock_create_ruleset - Create a new ruleset diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c index d20ab8f0862c..58fe322d8637 100644 --- a/tools/testing/selftests/landlock/base_test.c +++ b/tools/testing/selftests/landlock/base_test.c @@ -76,7 +76,7 @@ TEST(abi_version) const struct landlock_ruleset_attr ruleset_attr = { .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, }; - ASSERT_EQ(11, landlock_create_ruleset(NULL, 0, + ASSERT_EQ(12, landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION)); ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0, -- 2.55.0