From: Bryam Vargas <hexlabsecurity@proton.me>
To: "Mickaël Salaün" <mic@digikod.net>
Cc: "Günther Noack" <gnoack@google.com>,
"Matthieu Buffet" <matthieu@buffet.re>,
"Paul Moore" <paul@paul-moore.com>,
"Eric Dumazet" <edumazet@google.com>,
"Neal Cardwell" <ncardwell@google.com>,
linux-security-module@vger.kernel.org, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: Landlock: LANDLOCK_ACCESS_NET_CONNECT_TCP bypass via TCP Fast Open
Date: Tue, 16 Jun 2026 20:16:22 +0000 [thread overview]
Message-ID: <20260616201615.275032-1-hexlabsecurity@proton.me> (raw)
Hello Mickaël, and Landlock folks,
A task confined by a Landlock ruleset that handles
LANDLOCK_ACCESS_NET_CONNECT_TCP and is denied connecting to a given port can
still establish a TCP connection to that port by using TCP Fast Open, i.e.
sendto(fd, ..., MSG_FASTOPEN, &dst, dstlen) on a fresh stream socket. The
network-egress confinement for TCP connect is silently bypassed.
Affected
--------
Any kernel with CONFIG_SECURITY_LANDLOCK=y and Landlock enabled that supports
the TCP network access rights (Landlock ABI >= 4, since Linux 6.7). Confirmed by
source inspection on mainline (v7.1-rc7) and reproduced on Linux 7.0.11
(Landlock ABI 8). No CONFIG beyond Landlock + IPv4/IPv6 TCP; TCP Fast Open client
is enabled by the per-netns default (net.ipv4.tcp_fastopen has TFO_CLIENT_ENABLE
set), so no sysctl change and no setsockopt are required.
Root cause
----------
LANDLOCK_ACCESS_NET_CONNECT_TCP is enforced only by the socket_connect LSM hook
(hook_socket_connect -> current_check_access_socket). security_socket_connect()
has exactly one call site in the tree, net/socket.c (the connect(2) syscall).
TCP Fast Open performs an implicit connect inside sendmsg:
tcp_sendmsg_locked() net/ipv4/tcp.c (MSG_FASTOPEN branch)
-> tcp_sendmsg_fastopen() net/ipv4/tcp.c
-> __inet_stream_connect(..., is_sendmsg=1) net/ipv4/af_inet.c
-> sk->sk_prot->connect() net/ipv4/af_inet.c -> tcp_v4_connect()
This path establishes the connection to the address taken from msg_name but
never calls security_socket_connect(). The only LSM hook fired on the sendmsg
path is security_socket_sendmsg(), and Landlock registers no socket_sendmsg
hook, so LANDLOCK_ACCESS_NET_CONNECT_TCP is never re-checked. __inet_stream_connect()
itself carries no LSM hook (only the cgroup-BPF pre_connect, a different
mechanism).
Notably the kernel already mediates the analogous AF_UNIX implicit-connect on the
send path via the unix_may_send hook, which Landlock does register
(hook_unix_may_send) -- so the sendmsg-implies-connect pattern is recognized, but
the TCP Fast Open case has no equivalent coverage. The MPTCP fast-open path
(mptcp_sendmsg_fastopen -> __inet_stream_connect) is a second producer of the
same unmediated connect (by source inspection; not separately reproduced).
Reproducer
----------
A self-contained, fully unprivileged PoC is available on request. It forks an
unconfined TFO-capable loopback listener, then in a child applies a Landlock
ruleset handling LANDLOCK_ACCESS_NET_CONNECT_TCP with no allow rule
(landlock_create_ruleset() with handled_access_net =
LANDLOCK_ACCESS_NET_CONNECT_TCP, no landlock_add_rule(), then
landlock_restrict_self(); every TCP connect is denied) and tries the forbidden
port two ways:
(1) connect(fd, &dst) -> -EACCES (Landlock enforces CONNECT_TCP)
(2) sendto(fd2, buf, len, MSG_FASTOPEN, &dst, dstlen)
-> succeeds; the listener accepts the
connection and reads the payload.
Observed on Linux 7.0.11 (Landlock ABI 8):
[1] connect(2) -> ret=-1 errno=13 (Permission denied)
[2] sendto(MSG_FASTOPEN) -> ret=14 errno=0 (OK/queued)
[+] listener ACCEPTED the confined child's connection; payload="..."
connect(2) to the port is denied while sendto(MSG_FASTOPEN) reaches the identical
port and delivers data.
Impact
------
A sandbox that uses LANDLOCK_ACCESS_NET_CONNECT_TCP to restrict outbound TCP
(e.g. to keep a confined component from reaching an internal service or a
metadata endpoint) can be escaped by an unprivileged, self-confined task with no
CAP and no namespace transition -- for any destination port, since the
implicit-connect path never consults the connect hook regardless of address (the
run above shows one port). It is an integrity
bypass of the network-confinement property; no memory safety is involved.
I score it CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N (6.5 Medium) -- the
confined task escapes the policy authority that defined its sandbox, a scope
change; 5.5 if you treat the Landlock boundary as the same authority (S:U).
Note on the in-flight UDP series
--------------------------------
The "landlock: Add UDP access control support" series (v5, Matthieu Buffet,
https://lore.kernel.org/r/20260611162107.49278-3-matthieu@buffet.re) adds a
socket_sendmsg hook, hook_socket_sendmsg(), but it returns 0 for non-UDP
sockets:
if (sk_is_udp(sock->sk))
access_request = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP;
else
return 0;
so a TCP socket using MSG_FASTOPEN still bypasses LANDLOCK_ACCESS_NET_CONNECT_TCP
even after that series lands. It may be most convenient to fix this there.
Suggested direction
-------------------
Re-check LANDLOCK_ACCESS_NET_CONNECT_TCP on the implicit-connect path: either have
the socket_sendmsg hook evaluate CONNECT_TCP for stream sockets when the call
performs an implicit connect (mirroring the AF_UNIX unix_may_send handling), or
place the check inside __inet_stream_connect() so a single chokepoint covers
connect(2), TCP Fast Open, and the MPTCP fast-open sibling.
I am happy to send a patch for this if you would like me to.
Best regards,
Bryam Vargas
Independent security researcher, HEXLAB S.A.S., Cali, Colombia
hexlabsecurity@proton.me
next reply other threads:[~2026-06-16 20:16 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-16 20:16 Bryam Vargas [this message]
2026-06-17 14:22 ` Landlock: LANDLOCK_ACCESS_NET_CONNECT_TCP bypass via TCP Fast Open Mickaël Salaün
2026-06-17 18:05 ` Matthieu Buffet
2026-06-17 18:05 ` [RFC PATCH 1/2] landlock: fix TCP Fast Open connection bypass Matthieu Buffet
2026-06-17 18:05 ` [RFC PATCH 2/2] selftests/landlock: Add test for TCP fast open Matthieu Buffet
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260616201615.275032-1-hexlabsecurity@proton.me \
--to=hexlabsecurity@proton.me \
--cc=edumazet@google.com \
--cc=gnoack@google.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-security-module@vger.kernel.org \
--cc=matthieu@buffet.re \
--cc=mic@digikod.net \
--cc=ncardwell@google.com \
--cc=netdev@vger.kernel.org \
--cc=paul@paul-moore.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.