* [PATCH net] tcp: restore IPv6 af_ops on disconnect of a v4-mapped socket
@ 2026-10-30 3:23 Zhiyu Zhang
2026-10-30 3:29 ` netdev-bot+sinfo
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Zhiyu Zhang @ 2026-10-30 3:23 UTC (permalink / raw)
To: Eric Dumazet, Kuniyuki Iwashima
Cc: Neal Cardwell, David S. Miller, Jakub Kicinski, Paolo Abeni,
Simon Horman, Matthieu Baerts, Mat Martineau, Geliang Tang,
netdev, mptcp, linux-kernel, Tong Liu, Qi Tang
From: Eric Dumazet <edumazet@kernel.org>
A successful connect() of an IPv6 socket to a v4-mapped address switches
icsk_af_ops, sk_backlog_rcv and af_specific to their IPv4-mapped variants.
These are only restored if the connect() fails.
After connect(AF_UNSPEC), the socket can be reused for listen() or for a
native IPv6 connect while still using the mapped handlers. IPv6 packets
processed from the socket backlog are then handled by tcp_v4_do_rcv().
tcp_v4_send_reset() and tcp_v4_send_ack() interpret
TCP_SKB_CB(skb)->header.h6 as IPv4 options, and __ip_options_echo() can
overflow its 40-byte destination buffer.
Commit 8cc3aef0cb19 ("tcp: Do not allow buggy transitions between ehash
and lhash2.") prevents listen() after connect(AF_UNSPEC), but a native IPv6
connect is still allowed.
Add tcp_v6_disconnect() to restore the IPv6 handlers when a v4-mapped
socket is disconnected, and use the same helper in the connect error path.
MPTCP subflows override sk_prot, and two teardown paths call
tcp_disconnect() directly. Route those calls through the protocol-specific
disconnect operation so IPv6 MPTCP subflows receive the same restoration.
Tested with TCP and MPTCP by reconnecting an AF_INET6 socket from a
v4-mapped peer to a native IPv6 peer after connect(AF_UNSPEC).
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Reported-by: Tong Liu <lyutoon@gmail.com>
Reported-by: Qi Tang <tpluszz77@gmail.com>
Reported-by: Zhiyu Zhang <zhiyuzhang999@gmail.com>
Closes: https://lore.kernel.org/netdev/CALf2hKvp0NPVLwr=a3+jUHtPohxbra8gox+aYAfWathJnn-J+Q@mail.gmail.com/
Link: https://lore.kernel.org/netdev/CAL4WiiqFJpmcV4=TWRpukCq6q=-729658FwGvieNcPXe97kLvQ@mail.gmail.com/
Link: https://lore.kernel.org/lkml/20261029054136.1174216-1-zhiyuzhang999@gmail.com/
Signed-off-by: Eric Dumazet <edumazet@kernel.org>
Co-developed-by: Zhiyu Zhang <zhiyuzhang999@gmail.com>
Signed-off-by: Zhiyu Zhang <zhiyuzhang999@gmail.com>
---
net/ipv6/tcp_ipv6.c | 37 ++++++++++++++++++++++++++++---------
net/mptcp/protocol.c | 4 ++--
2 files changed, 30 insertions(+), 11 deletions(-)
diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c
index e006a30360ea..a93410aab66e 100644
--- a/net/ipv6/tcp_ipv6.c
+++ b/net/ipv6/tcp_ipv6.c
@@ -130,6 +130,19 @@ static int tcp_v6_pre_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
return BPF_CGROUP_RUN_PROG_INET6_CONNECT(sk, uaddr, &addr_len);
}
+/* Undo the v4-mapped setup done by tcp_v6_connect(). */
+static void tcp_v6_restore_af_ops(struct sock *sk)
+{
+ /* Paired with READ_ONCE() in tcp_(get|set)sockopt() */
+ WRITE_ONCE(inet_csk(sk)->icsk_af_ops, &ipv6_specific);
+ if (sk_is_mptcp(sk))
+ mptcpv6_handle_mapped(sk, false);
+ sk->sk_backlog_rcv = tcp_v6_do_rcv;
+#if defined(CONFIG_TCP_MD5SIG) || defined(CONFIG_TCP_AO)
+ tcp_sk(sk)->af_specific = &tcp_sock_ipv6_specific;
+#endif
+}
+
static int tcp_v6_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
int addr_len)
{
@@ -243,14 +256,7 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
if (err) {
icsk->icsk_ext_hdr_len = exthdrlen;
- /* Paired with READ_ONCE() in tcp_(get|set)sockopt() */
- WRITE_ONCE(icsk->icsk_af_ops, &ipv6_specific);
- if (sk_is_mptcp(sk))
- mptcpv6_handle_mapped(sk, false);
- sk->sk_backlog_rcv = tcp_v6_do_rcv;
-#if defined(CONFIG_TCP_MD5SIG) || defined(CONFIG_TCP_AO)
- tp->af_specific = &tcp_sock_ipv6_specific;
-#endif
+ tcp_v6_restore_af_ops(sk);
goto failure;
}
np->saddr = sk->sk_v6_rcv_saddr;
@@ -2093,6 +2099,19 @@ static int tcp_v6_init_sock(struct sock *sk)
return 0;
}
+static int tcp_v6_disconnect(struct sock *sk, int flags)
+{
+ int ret = tcp_disconnect(sk, flags);
+
+ /* A socket used for a v4-mapped connection must not keep IPv4
+ * handlers when it is reused for listen() or a native IPv6 connect().
+ */
+ if (sk->sk_backlog_rcv == tcp_v4_do_rcv)
+ tcp_v6_restore_af_ops(sk);
+
+ return ret;
+}
+
#ifdef CONFIG_PROC_FS
/* Proc filesystem TCPv6 sock list dumping. */
static void get_openreq6(struct seq_file *seq,
@@ -2283,7 +2302,7 @@ struct proto tcpv6_prot = {
.close = tcp_close,
.pre_connect = tcp_v6_pre_connect,
.connect = tcp_v6_connect,
- .disconnect = tcp_disconnect,
+ .disconnect = tcp_v6_disconnect,
.accept = inet_csk_accept,
.ioctl = tcp_ioctl,
.init = tcp_v6_init_sock,
diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index 7a22e9388e0e..fdfbc7de2ec0 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -2608,7 +2608,7 @@ static void __mptcp_subflow_disconnect(struct sock *ssk,
/* The MPTCP code never wait on the subflow sockets, TCP-level
* disconnect should never fail
*/
- WARN_ON_ONCE(tcp_disconnect(ssk, 0));
+ WARN_ON_ONCE(ssk->sk_prot->disconnect(ssk, 0));
mptcp_subflow_ctx_reset(subflow);
} else {
tcp_shutdown(ssk, SEND_SHUTDOWN);
@@ -3287,7 +3287,7 @@ void mptcp_subflow_shutdown(struct sock *sk, struct sock *ssk, int how)
break;
fallthrough;
case TCP_SYN_SENT:
- WARN_ON_ONCE(tcp_disconnect(ssk, O_NONBLOCK));
+ WARN_ON_ONCE(ssk->sk_prot->disconnect(ssk, O_NONBLOCK));
break;
default:
if (__mptcp_check_fallback(mptcp_sk(sk))) {
base-commit: 5838f10563d58afca6711edb5e4eda968ca181d9
--
2.34.1
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH net] tcp: restore IPv6 af_ops on disconnect of a v4-mapped socket
2026-10-30 3:23 [PATCH net] tcp: restore IPv6 af_ops on disconnect of a v4-mapped socket Zhiyu Zhang
@ 2026-10-30 3:29 ` netdev-bot+sinfo
2026-11-01 15:59 ` Kuniyuki Iwashima
2026-11-03 3:47 ` netdev-bot+sashiko
2 siblings, 0 replies; 4+ messages in thread
From: netdev-bot+sinfo @ 2026-10-30 3:29 UTC (permalink / raw)
To: Zhiyu Zhang
Cc: Eric Dumazet, Kuniyuki Iwashima, Neal Cardwell, David S. Miller,
Jakub Kicinski, Paolo Abeni, Simon Horman, Matthieu Baerts,
Mat Martineau, Geliang Tang, netdev, mptcp, linux-kernel,
Tong Liu, Qi Tang
Hi!
This is an automated message. This series looks like a fix, but its
commit messages seem to be missing some information:
- How the issue was discovered, e.g. hit in production, hit during
development, syzbot report, manual code inspection, LLM or static
analysis tool scan.
Please do not repost the series just to address the above. Instead,
reply to this email with the missing information, so that reviewers
can take it into account. If the series needs another revision for
other reasons, please include the information in the commit messages
then.
The evaluation is done by an LLM so it may be wrong, if you think
that is the case please reply and explain.
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH net] tcp: restore IPv6 af_ops on disconnect of a v4-mapped socket
2026-10-30 3:23 [PATCH net] tcp: restore IPv6 af_ops on disconnect of a v4-mapped socket Zhiyu Zhang
2026-10-30 3:29 ` netdev-bot+sinfo
@ 2026-11-01 15:59 ` Kuniyuki Iwashima
2026-11-03 3:47 ` netdev-bot+sashiko
2 siblings, 0 replies; 4+ messages in thread
From: Kuniyuki Iwashima @ 2026-11-01 15:59 UTC (permalink / raw)
To: Zhiyu Zhang
Cc: Eric Dumazet, Neal Cardwell, David S. Miller, Jakub Kicinski,
Paolo Abeni, Simon Horman, Matthieu Baerts, Mat Martineau,
Geliang Tang, netdev, mptcp, linux-kernel, Tong Liu, Qi Tang
On Thu, 29 Oct 2026 23:23:14 -0400, Zhiyu Zhang wrote:
> From: Eric Dumazet <edumazet@kernel.org>
>
> A successful connect() of an IPv6 socket to a v4-mapped address switches
> icsk_af_ops, sk_backlog_rcv and af_specific to their IPv4-mapped variants.
> These are only restored if the connect() fails.
>
> After connect(AF_UNSPEC), the socket can be reused for listen() or for a
> native IPv6 connect while still using the mapped handlers. IPv6 packets
> processed from the socket backlog are then handled by tcp_v4_do_rcv().
> tcp_v4_send_reset() and tcp_v4_send_ack() interpret
> TCP_SKB_CB(skb)->header.h6 as IPv4 options, and __ip_options_echo() can
> overflow its 40-byte destination buffer.
>
> Commit 8cc3aef0cb19 ("tcp: Do not allow buggy transitions between ehash
> and lhash2.") prevents listen() after connect(AF_UNSPEC), but a native IPv6
> connect is still allowed.
>
> Add tcp_v6_disconnect() to restore the IPv6 handlers when a v4-mapped
> socket is disconnected, and use the same helper in the connect error path.
>
> MPTCP subflows override sk_prot, and two teardown paths call
> tcp_disconnect() directly. Route those calls through the protocol-specific
> disconnect operation so IPv6 MPTCP subflows receive the same restoration.
>
> Tested with TCP and MPTCP by reconnecting an AF_INET6 socket from a
> v4-mapped peer to a native IPv6 peer after connect(AF_UNSPEC).
>
> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Reported-by: Tong Liu <lyutoon@gmail.com>
> Reported-by: Qi Tang <tpluszz77@gmail.com>
> Reported-by: Zhiyu Zhang <zhiyuzhang999@gmail.com>
This is not needed when you add Co-developed-by.
> Closes: https://lore.kernel.org/netdev/CALf2hKvp0NPVLwr=a3+jUHtPohxbra8gox+aYAfWathJnn-J+Q@mail.gmail.com/
> Link: https://lore.kernel.org/netdev/CAL4WiiqFJpmcV4=TWRpukCq6q=-729658FwGvieNcPXe97kLvQ@mail.gmail.com/
> Link: https://lore.kernel.org/lkml/20261029054136.1174216-1-zhiyuzhang999@gmail.com/
This link is not directly related, remove it.
> Signed-off-by: Eric Dumazet <edumazet@kernel.org>
> Co-developed-by: Zhiyu Zhang <zhiyuzhang999@gmail.com>
> Signed-off-by: Zhiyu Zhang <zhiyuzhang999@gmail.com>
Otherwise looks good.
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH net] tcp: restore IPv6 af_ops on disconnect of a v4-mapped socket
2026-10-30 3:23 [PATCH net] tcp: restore IPv6 af_ops on disconnect of a v4-mapped socket Zhiyu Zhang
2026-10-30 3:29 ` netdev-bot+sinfo
2026-11-01 15:59 ` Kuniyuki Iwashima
@ 2026-11-03 3:47 ` netdev-bot+sashiko
2 siblings, 0 replies; 4+ messages in thread
From: netdev-bot+sashiko @ 2026-11-03 3:47 UTC (permalink / raw)
To: zhiyuzhang999
Cc: edumazet, kuniyu, ncardwell, davem, kuba, pabeni, horms, matttbe,
martineau, geliang, netdev, mptcp, linux-kernel, lyutoon,
tpluszz77
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 0 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] The patch does not fully close the bug class it targets: an IPv6
skb handed from the socket backlog to tcp_v4_do_rcv(), which then…
--
tcp: restore IPv6 af_ops on disconnect of a v4-mapped socket
This adds tcp_v6_disconnect(). When a socket that was connected to a
v4-mapped address is disconnected, it restores icsk_af_ops,
sk_backlog_rcv and af_specific to the IPv6 handlers. The connect() error
path uses the same helper, and the two MPTCP teardown paths now call
sk_prot->disconnect instead of tcp_disconnect() directly.
Closes: https://lore.kernel.org/netdev/CALf2hKvp0NPVLwr=a3+jUHtPohxbra8gox+aYAfWathJnn-J+Q@mail.gmail.com/
Link: https://lore.kernel.org/netdev/CAL4WiiqFJpmcV4=TWRpukCq6q=-729658FwGvieNcPXe97kLvQ@mail.gmail.com/
Link: https://lore.kernel.org/lkml/20261029054136.1174216-1-zhiyuzhang999@gmail.com/
> diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c
> index e006a30360eae..a93410aab66e6 100644
> --- a/net/ipv6/tcp_ipv6.c
> +++ b/net/ipv6/tcp_ipv6.c
[ ... ]
> @@ -2093,6 +2099,19 @@ static int tcp_v6_init_sock(struct sock *sk)
> return 0;
> }
>
> +static int tcp_v6_disconnect(struct sock *sk, int flags)
> +{
> + int ret = tcp_disconnect(sk, flags);
> +
> + /* A socket used for a v4-mapped connection must not keep IPv4
> + * handlers when it is reused for listen() or a native IPv6 connect().
> + */
> + if (sk->sk_backlog_rcv == tcp_v4_do_rcv)
> + tcp_v6_restore_af_ops(sk);
[Severity: High]
Does this also cover the opposite transition? The check only restores the
handlers when going from mapped to native. Going from a native IPv6
connection, through connect(AF_UNSPEC), to a v4-mapped connect() still
looks like it can pass an IPv6 skb from the backlog to tcp_v4_do_rcv().
In tcp_v6_rcv(), the socket lookup, tcp_v6_fill_cb() and the TCP_LISTEN
check all run before the socket spinlock is taken. Once the lock is held,
nothing checks sk_backlog_rcv again:
net/ipv6/tcp_ipv6.c:tcp_v6_rcv() {
...
tcp_v6_fill_cb(skb, hdr, th);
...
bh_lock_sock_nested(sk);
tcp_segs_in(tcp_sk(sk), skb);
ret = 0;
if (!sock_owned_by_user(sk)) {
ret = tcp_v6_do_rcv(sk, skb);
} else {
drop_reason = tcp_add_backlog(sk, skb);
...
}
Can this ordering happen?
CPU1 (IPv6 segment for the old native connection)
tcp_v6_rcv()
__inet6_lookup_skb() finds sk
tcp_v6_fill_cb() /* fills header.h6 */
CPU0
connect(AF_UNSPEC)
tcp_v6_disconnect()
/* sk_backlog_rcv is tcp_v6_do_rcv, no restore */
connect(::ffff:a.b.c.d)
tcp_v6_connect()
sk->sk_backlog_rcv = tcp_v4_do_rcv;
tcp_v4_connect() /* SYN_SENT, still owned */
bh_lock_sock_nested()
sock_owned_by_user() is true
tcp_add_backlog()
release_sock()
__release_sock()
sk_backlog_rcv() -> tcp_v4_do_rcv()
tcp_v6_do_rcv() redirects ETH_P_IP skbs, but tcp_v4_do_rcv() does not
check skb->protocol at all.
In SYN_SENT, a stale ACK carries an ack_seq from the old connection.
tcp_rcv_synsent_state_process() rejects it, so tcp_v4_do_rcv() ends up
here:
net/ipv4/tcp_ipv4.c:tcp_v4_do_rcv() {
...
reason = tcp_rcv_state_process(sk, skb);
if (reason)
goto reset;
return 0;
reset:
tcp_v4_send_reset(sk, skb, sk_rst_convert_drop_reason(reason));
...
}
>From there, tcp_v4_send_reset() reads ip_hdr() of an IPv6 packet. It then
passes &TCP_SKB_CB(skb)->header.h4.opt, which actually holds h6, to
ip_send_unicast_reply() -> __ip_options_echo(). That is the same 40-byte
overflow the commit message describes.
The window is narrow. Hard irqs, vCPU preemption, preemptible softirqs on
PREEMPT_RT, or a socket filter run by tcp_filter() inside the window can
make it longer.
Is an skb->protocol check also needed in the mapped receive path, in
addition to restoring the handlers on disconnect? It could drop ETH_P_IPV6
skbs, or redirect them to tcp_v6_do_rcv(), like the existing ETH_P_IP check
in tcp_v6_do_rcv().
> +
> + return ret;
> +}
> +
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261030032314.1978575-1-zhiyuzhang999%40gmail.com
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-11-03 3:47 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-30 3:23 [PATCH net] tcp: restore IPv6 af_ops on disconnect of a v4-mapped socket Zhiyu Zhang
2026-10-30 3:29 ` netdev-bot+sinfo
2026-11-01 15:59 ` Kuniyuki Iwashima
2026-11-03 3:47 ` netdev-bot+sashiko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®