From: Hyunwoo Kim <imv4bel@gmail.com>
To: davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
pabeni@redhat.com, ncardwell@google.com, dsahern@kernel.org,
idosch@nvidia.com, kuniyu@google.com, horms@kernel.org,
willemb@google.com, andrew+netdev@lunn.ch, kees@kernel.org,
jiayuan.chen@linux.dev
Cc: kerneljasonxing@gmail.com, ij@kernel.org, martin.lau@kernel.org,
shakeel.butt@linux.dev, matttbe@kernel.org, martineau@kernel.org,
netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
imv4bel@gmail.com, stable@vger.kernel.org
Subject: [PATCH net v2 1/8] tcp: fix use-after-free of the listener's ipv6_pinfo after IPV6_ADDRFORM
Date: Mon, 24 Aug 2026 12:32:45 +0900 [thread overview]
Message-ID: <20260824033331.1084971-2-imv4bel@gmail.com> (raw)
In-Reply-To: <20260824033331.1084971-1-imv4bel@gmail.com>
IPV6_ADDRFORM switches an established AF_INET6 TCP socket to tcp_prot and
ipv4_specific. The socket is still a tcp6_sock, so ->pinet6 keeps pointing
at the ipv6_pinfo inside it, and sk_destruct is left alone because
commit d38afeec26ed ("tcp/udp: Call inet6_destroy_sock() in IPv6
sk->sk_destruct().") uses it to clean up the IPv6 resources.
After connect(AF_UNSPEC) the socket can listen() again. Its children are
then created by tcp_v4_syn_recv_sock() with opt_child_init NULL, and
sk_clone() allocates them from tcp_prot, so each one is a plain tcp_sock
that inherits ->pinet6 and sk_destruct from the listener.
tcp_v6_mapped_child_init(), added by commit 858d2a4f67ff ("tcp: fix
potential race in tcp_v6_syn_recv_sock()"), would overwrite ->pinet6, but
tcp_v6_syn_recv_sock() is the only caller that passes it and it is not
involved here.
A child can outlive the listener. INET_ECN_xmit() and INET_ECN_dontxmit()
test inet6_sk(sk) and not sk_family, so tcp_ecn_send() updates np->tclass
through the stale pointer, and the child's destructor runs
inet6_cleanup_sock() on the freed listener.
In short:
socket(AF_INET6) -> bind -> listen
// a client connects over IPv4
accept() // the child is v4-mapped
setsockopt(IPV6_ADDRFORM, PF_INET) // it becomes an AF_INET socket
connect(AF_UNSPEC) -> bind -> listen // reuse it as an IPv4 server
// a client connects again
accept()
close(the listener)
close(the accepted socket) // use-after-free
KASAN log:
BUG: KASAN: slab-use-after-free in __tcp_transmit_skb+0x1070/0x2020
Read of size 1 at addr ffff888016671af3 by task poc/111
...
Call Trace:
__tcp_transmit_skb+0x1070/0x2020
tcp_write_xmit+0xace/0x3380
__tcp_push_pending_frames+0x58/0x180
__tcp_close+0x4b8/0x7d0
tcp_close+0x23/0x90
inet_release+0x93/0x100
__sock_release+0x66/0x130
sock_close+0x18/0x20
__fput+0x1f0/0x4c0
__x64_sys_close+0x55/0x90
...
BUG: KASAN: slab-use-after-free in inet6_cleanup_sock+0x61/0x140
Write of size 8 at addr ffff888016671b20 by task poc/111
...
Call Trace:
inet6_cleanup_sock+0x61/0x140
inet6_sock_destruct+0x12/0x20
__sk_destruct+0x4f/0x420
inet_release+0x93/0x100
__sock_release+0x66/0x130
sock_close+0x18/0x20
__fput+0x1f0/0x4c0
__x64_sys_close+0x55/0x90
...
Allocated by task 111:
sk_prot_alloc+0x45/0x170
sk_clone+0x49/0x970
inet_csk_clone_lock+0x29/0x2c0
tcp_create_openreq_child+0x2a/0x10a0
tcp_v4_syn_recv_sock+0xd3/0x850
tcp_v6_syn_recv_sock+0xc12/0xd80
tcp_check_req+0x390/0x1080
tcp_v4_rcv+0xc15/0x21c0
...
Freed by task 14:
slab_free_after_rcu_debug+0xd5/0x220
rcu_core+0x4fe/0xe20
...
The buggy address belongs to the object at ffff888016670e40
which belongs to the cache TCPv6 of size 3328
Fix this by clearing ->pinet6 and ->ipv6_fl_list on the child, and by
returning early from inet6_cleanup_sock() when there is no ipv6_pinfo.
tcp_v6_mapped_child_init() sets both fields, so the v4-mapped path is not
affected. The converted listener keeps its own ipv6_pinfo, so there is
nothing to clear on the IPV6_ADDRFORM side.
Clearing ->pinet6 in tcp_disconnect() instead would keep this out of the
fast path, but it leaves the pointer NULL on a socket that still has a
file descriptor, and of the 120 inet6_sk() call sites only the two in
inet_ecn.h check it for NULL, so _all_ the others have to be found and
guarded first. At the point patched here the child is not in the ehash yet
and has no sk_socket. Once the two fields are cleared it is no different
from any other AF_INET child.
Fixes: d38afeec26ed ("tcp/udp: Call inet6_destroy_sock() in IPv6 sk->sk_destruct().")
Cc: stable@vger.kernel.org
Signed-off-by: Hyunwoo Kim <imv4bel@gmail.com>
---
Changes in v2:
- Clear ->pinet6 and ->ipv6_fl_list right after the inet fields are set
instead of in an else arm of the opt_child_init test, so the child is
already consistent on the put_and_exit path and the existing test is left
untouched.
- Explain why this is not done in tcp_disconnect().
- Add the reproducer and the KASAN reports.
- v1: https://lore.kernel.org/all/antr7RCJAO578ZFW@v4bel/
---
net/ipv4/tcp_ipv4.c | 8 ++++++++
net/ipv6/af_inet6.c | 4 ++++
2 files changed, 12 insertions(+)
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index 190c7af4cf923a..302afe8ebcbcc3 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -1714,6 +1714,14 @@ struct sock *tcp_v4_syn_recv_sock(const struct sock *sk, struct sk_buff *skb,
inet_csk(newsk)->icsk_ext_hdr_len = inet_opt->opt.optlen;
atomic_set(&newinet->inet_id, get_random_u16());
+#if IS_ENABLED(CONFIG_IPV6)
+ /* Never inherit the listener's ipv6_pinfo; IPV6_ADDRFORM leaves it set
+ * on an AF_INET socket. tcp_v6_mapped_child_init() installs our own.
+ */
+ newinet->pinet6 = NULL;
+ newinet->ipv6_fl_list = NULL;
+#endif
+
/* Set ToS of the new socket based upon the value of incoming SYN.
* ECT bits are set later in tcp_init_transfer().
*/
diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
index 282912a1199992..68b330f6941d04 100644
--- a/net/ipv6/af_inet6.c
+++ b/net/ipv6/af_inet6.c
@@ -479,6 +479,10 @@ void inet6_cleanup_sock(struct sock *sk)
struct sk_buff *skb;
struct ipv6_txoptions *opt;
+ /* AF_INET child of an IPV6_ADDRFORM'ed listener: nothing of its own. */
+ if (!np)
+ return;
+
/* Release rx options */
skb = xchg(&np->pktoptions, NULL);
--
2.43.0
next prev parent reply other threads:[~2026-08-24 3:34 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-24 3:32 [PATCH net v2 0/8] net: fixes for requests completing on a socket that no longer listens Hyunwoo Kim
2026-08-24 3:32 ` Hyunwoo Kim [this message]
2026-08-24 3:32 ` [PATCH net v2 2/8] tcp: fix imbalanced icsk_accept_queue count in tcp_check_req() Hyunwoo Kim
2026-08-24 3:32 ` [PATCH net v2 3/8] ipv6: fix request socket use-after-free after IPV6_ADDRFORM Hyunwoo Kim
2026-08-24 3:32 ` [PATCH net v2 4/8] net: fix out-of-bounds write in sk_clone() racing with IPV6_ADDRFORM Hyunwoo Kim
2026-08-24 3:32 ` [PATCH net v2 5/8] tcp: do not inherit out_of_order_queue from parent Hyunwoo Kim
2026-08-24 3:32 ` [PATCH net v2 6/8] tcp: fix use-after-free in the lockless listener path Hyunwoo Kim
2026-09-01 7:37 ` Hyunwoo Kim
2026-09-01 8:03 ` Paolo Abeni
2026-09-01 9:06 ` Hyunwoo Kim
2026-09-01 15:51 ` Jakub Kicinski
2026-09-01 15:57 ` Eric Dumazet
2026-09-03 19:16 ` Hyunwoo Kim
2026-09-03 19:28 ` Kuniyuki Iwashima
2026-09-03 19:31 ` Hyunwoo Kim
2026-09-01 9:04 ` Hyunwoo Kim
2026-08-24 3:32 ` [PATCH net v2 7/8] net: clear sk_tsq_flags in sk_clone() Hyunwoo Kim
2026-08-24 3:32 ` [PATCH net v2 8/8] tcp: do not inherit retransmit state from parent Hyunwoo Kim
2026-08-24 8:30 ` [PATCH net v2 0/8] net: fixes for requests completing on a socket that no longer listens David Laight
2026-08-24 12:56 ` Hyunwoo Kim
2026-08-31 23:35 ` Jakub Kicinski
2026-09-01 7:40 ` Hyunwoo Kim
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=20260824033331.1084971-2-imv4bel@gmail.com \
--to=imv4bel@gmail.com \
--cc=andrew+netdev@lunn.ch \
--cc=davem@davemloft.net \
--cc=dsahern@kernel.org \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=idosch@nvidia.com \
--cc=ij@kernel.org \
--cc=jiayuan.chen@linux.dev \
--cc=kees@kernel.org \
--cc=kerneljasonxing@gmail.com \
--cc=kuba@kernel.org \
--cc=kuniyu@google.com \
--cc=linux-kernel@vger.kernel.org \
--cc=martin.lau@kernel.org \
--cc=martineau@kernel.org \
--cc=matttbe@kernel.org \
--cc=ncardwell@google.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=shakeel.butt@linux.dev \
--cc=stable@vger.kernel.org \
--cc=willemb@google.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.