DORMANTno replies REVIEWED: 3 (3M)

1 review trailer.

[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);
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help