From: Eric Dumazet 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 Reported-by: Qi Tang Reported-by: Zhiyu Zhang 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 Co-developed-by: Zhiyu Zhang Signed-off-by: Zhiyu Zhang --- 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