[PATCH 6.12.y] tcp: fix potential race in tcp_v6_syn_recv_sock()
From: Michael Pfeifroth <hidden>
Date: 2026-09-08 08:20:20
Also in:
stable
[ Upstream commit 858d2a4f67ff69e645a43487ef7ea7f28f06deae ]
Code in tcp_v6_syn_recv_sock() after the call to tcp_v4_syn_recv_sock()
is done too late.
After tcp_v4_syn_recv_sock(), the child socket is already visible
from TCP ehash table and other cpus might use it.
Since newinet->pinet6 is still pointing to the listener ipv6_pinfo
bad things can happen as syzbot found.
Move the problematic code in tcp_v6_mapped_child_init()
and call this new helper from tcp_v4_syn_recv_sock() before
the ehash insertion.
This allows the removal of one tcp_sync_mss(), since
tcp_v4_syn_recv_sock() will call it with the correct
context.
Backport notes (6.12.y):
The mainline commit does not apply cleanly to 6.12 because
ipv6_fl_list was later moved from struct ipv6_pinfo into
struct inet_sock. On 6.12 ipv6_fl_list still lives inside
ipv6_pinfo, so the memcpy of the parent ipv6_pinfo into the
child covers it, and the new helper must therefore keep the
ipv6_fl_list initialization in the post-memcpy block (as done
here). No other logic changes.
CVE-2026-43198
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Reported-by: syzbot+937b5bbb6a815b3e5d0b@syzkaller.appspotmail.com
Signed-off-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
[mpf: backport to 6.12.y; keep ipv6_fl_list initialization in the
post-memcpy block since ipv6_fl_list still lives in struct
ipv6_pinfo on 6.12 (mainline moved it into struct inet_sock
after 6.12)]
Signed-off-by: Michael Pfeifroth <redacted>
---
Index: b/include/net/inet_connection_sock.h
===================================================================--- a/include/net/inet_connection_sock.h 2026-07-04 13:42:28.000000000 +0200
+++ b/include/net/inet_connection_sock.h 2026-08-03 11:15:50.785059353 +0200@@ -42,7 +42,9 @@ struct inet_connection_sock_af_ops { struct request_sock *req, struct dst_entry *dst, struct request_sock *req_unhash, - bool *own_req); + bool *own_req, + void (*opt_child_init)(struct sock *newsk, + const struct sock *sk)); u16 net_header_len; u16 net_frag_header_len; u16 sockaddr_len;
Index: b/include/net/tcp.h ===================================================================
--- a/include/net/tcp.h 2026-08-03 11:15:41.286991513 +0200
+++ b/include/net/tcp.h 2026-08-03 11:15:50.789059382 +0200@@ -460,7 +460,9 @@ struct sock *tcp_v4_syn_recv_sock(const struct request_sock *req, struct dst_entry *dst, struct request_sock *req_unhash, - bool *own_req); + bool *own_req, + void (*opt_child_init)(struct sock *newsk, + const struct sock *sk)); int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb); int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len); int tcp_connect(struct sock *sk);
Index: b/net/ipv4/syncookies.c ===================================================================
--- a/net/ipv4/syncookies.c 2026-07-04 13:42:28.000000000 +0200
+++ b/net/ipv4/syncookies.c 2026-08-03 11:15:50.792059404 +0200@@ -199,7 +199,7 @@ struct sock *tcp_get_cookie_sock(struct bool own_req; child = icsk->icsk_af_ops->syn_recv_sock(sk, skb, req, dst, - NULL, &own_req); + NULL, &own_req, NULL); if (child) { refcount_set(&req->rsk_refcnt, 1); tcp_sk(child)->tsoffset = tsoff;
Index: b/net/ipv4/tcp_fastopen.c ===================================================================
--- a/net/ipv4/tcp_fastopen.c 2026-07-04 13:42:28.000000000 +0200
+++ b/net/ipv4/tcp_fastopen.c 2026-08-03 11:15:50.795059426 +0200@@ -247,7 +247,7 @@ static struct sock *tcp_fastopen_create_ bool own_req; child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL, - NULL, &own_req); + NULL, &own_req, NULL); if (!child) return NULL;
Index: b/net/ipv4/tcp_ipv4.c ===================================================================
--- a/net/ipv4/tcp_ipv4.c 2026-07-04 13:42:28.000000000 +0200
+++ b/net/ipv4/tcp_ipv4.c 2026-08-03 11:15:50.799059455 +0200@@ -1564,7 +1564,9 @@ struct sock *tcp_v4_syn_recv_sock(const struct request_sock *req, struct dst_entry *dst, struct request_sock *req_unhash, - bool *own_req) + bool *own_req, + void (*opt_child_init)(struct sock *newsk, + const struct sock *sk)) { struct inet_request_sock *ireq; bool found_dup_sk = false;
@@ -1620,6 +1622,10 @@ struct sock *tcp_v4_syn_recv_sock(const } sk_setup_caps(newsk, dst); +#if IS_ENABLED(CONFIG_IPV6) + if (opt_child_init) + opt_child_init(newsk, sk); +#endif tcp_ca_openreq_child(newsk, dst); tcp_sync_mss(newsk, dst_mtu(dst));
Index: b/net/ipv4/tcp_minisocks.c ===================================================================
--- a/net/ipv4/tcp_minisocks.c 2026-07-04 13:42:28.000000000 +0200
+++ b/net/ipv4/tcp_minisocks.c 2026-08-03 11:15:50.802059477 +0200@@ -798,7 +798,7 @@ struct sock *tcp_check_req(struct sock * * socket is created, wait for troubles. */ child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL, - req, &own_req); + req, &own_req, NULL); if (!child) goto listen_overflow;
Index: b/net/ipv6/tcp_ipv6.c ===================================================================
--- a/net/ipv6/tcp_ipv6.c 2026-07-04 13:42:28.000000000 +0200
+++ b/net/ipv6/tcp_ipv6.c 2026-08-03 11:16:23.691310616 +0200@@ -1186,11 +1186,48 @@ static void tcp_v6_restore_cb(struct sk_ sizeof(struct inet6_skb_parm)); } +/* Called from tcp_v4_syn_recv_sock() for v6_mapped children. */ +static void tcp_v6_mapped_child_init(struct sock *newsk, const struct sock *sk) +{ + struct inet_sock *newinet = inet_sk(newsk); + struct ipv6_pinfo *newnp; + + newinet->pinet6 = newnp = tcp_inet6_sk(newsk); + + memcpy(newnp, tcp_inet6_sk(sk), sizeof(struct ipv6_pinfo)); + + newnp->saddr = newsk->sk_v6_rcv_saddr; + + inet_csk(newsk)->icsk_af_ops = &ipv6_mapped; + if (sk_is_mptcp(newsk)) + mptcpv6_handle_mapped(newsk, true); + newsk->sk_backlog_rcv = tcp_v4_do_rcv; +#if defined(CONFIG_TCP_MD5SIG) || defined(CONFIG_TCP_AO) + tcp_sk(newsk)->af_specific = &tcp_sock_ipv6_mapped_specific; +#endif + + newnp->ipv6_mc_list = NULL; + newnp->ipv6_ac_list = NULL; + newnp->ipv6_fl_list = NULL; + newnp->pktoptions = NULL; + newnp->opt = NULL; + + /* tcp_v4_syn_recv_sock() has initialized newinet->mc_{index,ttl} */ + newnp->mcast_oif = newinet->mc_index; + newnp->mcast_hops = newinet->mc_ttl; + + newnp->rcv_flowinfo = 0; + if (inet6_test_bit(REPFLOW, sk)) + newnp->flow_label = 0; +} + static struct sock *tcp_v6_syn_recv_sock(const struct sock *sk, struct sk_buff *skb, struct request_sock *req, struct dst_entry *dst, struct request_sock *req_unhash, - bool *own_req) + bool *own_req, + void (*opt_child_init)(struct sock *newsk, + const struct sock *sk)) { struct inet_request_sock *ireq; struct ipv6_pinfo *newnp;
@@ -1206,59 +1243,10 @@ static struct sock *tcp_v6_syn_recv_sock #endif struct flowi6 fl6; - if (skb->protocol == htons(ETH_P_IP)) { - /* - * v6 mapped - */ - - newsk = tcp_v4_syn_recv_sock(sk, skb, req, dst, - req_unhash, own_req); - - if (!newsk) - return NULL; - - inet_sk(newsk)->pinet6 = tcp_inet6_sk(newsk); - - newnp = tcp_inet6_sk(newsk); - newtp = tcp_sk(newsk); - - memcpy(newnp, np, sizeof(struct ipv6_pinfo)); - - newnp->saddr = newsk->sk_v6_rcv_saddr; - - inet_csk(newsk)->icsk_af_ops = &ipv6_mapped; - if (sk_is_mptcp(newsk)) - mptcpv6_handle_mapped(newsk, true); - newsk->sk_backlog_rcv = tcp_v4_do_rcv; -#if defined(CONFIG_TCP_MD5SIG) || defined(CONFIG_TCP_AO) - newtp->af_specific = &tcp_sock_ipv6_mapped_specific; -#endif - - newnp->ipv6_mc_list = NULL; - newnp->ipv6_ac_list = NULL; - newnp->ipv6_fl_list = NULL; - newnp->pktoptions = NULL; - newnp->opt = NULL; - newnp->mcast_oif = inet_iif(skb); - newnp->mcast_hops = ip_hdr(skb)->ttl; - newnp->rcv_flowinfo = 0; - if (inet6_test_bit(REPFLOW, sk)) - newnp->flow_label = 0; - - /* - * No need to charge this sock to the relevant IPv6 refcnt debug socks count - * here, tcp_create_openreq_child now does this for us, see the comment in - * that function for the gory details. -acme - */ - - /* It is tricky place. Until this moment IPv4 tcp - worked with IPv6 icsk.icsk_af_ops. - Sync it now. - */ - tcp_sync_mss(newsk, inet_csk(newsk)->icsk_pmtu_cookie); - - return newsk; - } + if (skb->protocol == htons(ETH_P_IP)) + return tcp_v4_syn_recv_sock(sk, skb, req, dst, + req_unhash, own_req, + tcp_v6_mapped_child_init); ireq = inet_rsk(req);
Index: b/net/mptcp/subflow.c ===================================================================
--- a/net/mptcp/subflow.c 2026-07-04 13:42:28.000000000 +0200
+++ b/net/mptcp/subflow.c 2026-08-03 11:15:50.805059498 +0200@@ -788,7 +788,9 @@ static struct sock *subflow_syn_recv_soc struct request_sock *req, struct dst_entry *dst, struct request_sock *req_unhash, - bool *own_req) + bool *own_req, + void (*opt_child_init)(struct sock *newsk, + const struct sock *sk)) { struct mptcp_subflow_context *listener = mptcp_subflow_ctx(sk); struct mptcp_subflow_request_sock *subflow_req;
@@ -834,7 +836,7 @@ static struct sock *subflow_syn_recv_soc create_child: child = listener->icsk_af_ops->syn_recv_sock(sk, skb, req, dst, - req_unhash, own_req); + req_unhash, own_req, opt_child_init); if (child && *own_req) { struct mptcp_subflow_context *ctx = mptcp_subflow_ctx(child);
Index: b/net/smc/af_smc.c ===================================================================
--- a/net/smc/af_smc.c 2026-07-04 13:42:28.000000000 +0200
+++ b/net/smc/af_smc.c 2026-08-03 11:15:50.809059527 +0200@@ -119,7 +119,9 @@ static struct sock *smc_tcp_syn_recv_soc struct request_sock *req, struct dst_entry *dst, struct request_sock *req_unhash, - bool *own_req) + bool *own_req, + void (*opt_child_init)(struct sock *newsk, + const struct sock *sk)) { struct smc_sock *smc; struct sock *child;
@@ -144,7 +146,7 @@ static struct sock *smc_tcp_syn_recv_soc /* passthrough to original syn recv sock fct */ child = smc->ori_af_ops->syn_recv_sock(sk, skb, req, dst, req_unhash, - own_req); + own_req, opt_child_init); /* child must not inherit smc or its ops */ if (child) { rcu_assign_sk_user_data(child, NULL);