[PATCH 3/6] landlock: Add MPTCP bind and connect access rights
Geliang Tang
geliang at kernel.org
Wed Sep 23 05:04:40 UTC 2026
Hi Günther,
On Mon, 2026-08-31 at 12:09 +0800, Geliang Tang wrote:
> Hi Günther,
>
> On Sun, 2026-08-30 at 22:16 +0200, Günther Noack wrote:
> > 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 <gnoack3000 at gmail.com>
> > ---
> > 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_AC
> > CESS_FS)
> >
> > -#define
> > LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_CONNECT_SEND_U
> > DP
> > +#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_AC
> > CESS_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 <linux/net.h>
> > #include <linux/socket.h>
> > #include <net/ipv6.h>
> > +#include <net/mptcp.h>
> >
> > #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;
> > +}
>
> This helper should be placed in include/net/mptcp.h. I had already
> implemented one in [1], called sk_is_msk(), to differentiate it from
> sk_is_mptcp(). If you have no concerns with my implementation, please
> feel free to pick it up and use it in your series.
Sorry, let me correct myself. I thought about this more carefully, and
the ideal place to define sk_is_msk() is in include/net/sock.h, between
sk_is_tcp() and sk_is_udp():
static inline bool sk_is_tcp(const struct sock *sk)
{
return sk_is_inet(sk) &&
sk->sk_type == SOCK_STREAM &&
sk->sk_protocol == IPPROTO_TCP;
}
static inline bool sk_is_msk(const struct sock *sk)
{
return sk_is_inet(sk) &&
sk->sk_type == SOCK_STREAM &&
sk->sk_protocol == IPPROTO_MPTCP;
}
static inline bool sk_is_udp(const struct sock *sk)
{
return sk_is_inet(sk) &&
sk->sk_type == SOCK_DGRAM &&
sk->sk_protocol == IPPROTO_UDP;
}
If possible, please fold this code into this patch.
Thanks,
-Geliang
>
> Thanks,
> -Geliang
>
> [1]
> https://patchwork.kernel.org/project/mptcp/patch/e2727ba40084f261545f1cbb140a3fb9d295ba25.1765505775.git.tanggeliang@kylinos.cn/
>
> > +
> > +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,
More information about the Linux-security-module-archive
mailing list