[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