git: 80af0a32e48b - main - tcp: Preserve borrowed ICMPv6 error mbufs

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Mon, 05 Oct 2026 13:23:23 UTC
The branch main has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=80af0a32e48bff6ad97e768b16d2dbc01ac8d1c3

commit 80af0a32e48bff6ad97e768b16d2dbc01ac8d1c3
Author:     Andrew Griffiths <andrew@calif.io>
AuthorDate: 2026-10-05 13:14:38 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2026-10-05 13:21:20 +0000

    tcp: Preserve borrowed ICMPv6 error mbufs
    
    The TCP-over-UDP ICMP callback receives ip6c_m as a borrowed chain owned
    by icmp6_notify_error.  It used m_pulldown() to obtain a contiguous UDP
    header even though that API frees the complete chain when the requested
    range is unavailable.  The callback could then return without
    propagating the lost ownership, leaving raw ICMP delivery to read and
    free a stale head.
    
    Validate that the complete UDP header was quoted, copy it through the
    chain with m_copydata(), and temporarily advance the parser offset
    instead of consuming bytes from the caller-owned packet.
    
    A remote sender can reach the old path when TCP UDP tunneling is enabled
    by quoting the configured local UDP source port in an ICMPv6 error and
    including only four through seven UDP-header bytes.
    
    Signed-off-by: Andrew Griffiths <andrew@calif.io>
    
    Reviewed by:    pouria, tuexen, markj
    MFC after:      1 week
    Differential Revision:  https://reviews.freebsd.org/D60143
---
 sys/netinet/tcp_subr.c | 22 +++++++++-------------
 1 file changed, 9 insertions(+), 13 deletions(-)

diff --git a/sys/netinet/tcp_subr.c b/sys/netinet/tcp_subr.c
index 1be803ed443f..4650e8035260 100644
--- a/sys/netinet/tcp_subr.c
+++ b/sys/netinet/tcp_subr.c
@@ -3392,25 +3392,21 @@ static void
 tcp6_ctlinput_viaudp(udp_tun_icmp_param_t param)
 {
 	struct ip6ctlparam *ip6cp = param.ip6cp;
-	struct mbuf *m;
-	struct udphdr *udp;
+	struct udphdr udp;
 	uint16_t port;
 
-	m = m_pulldown(ip6cp->ip6c_m, ip6cp->ip6c_off, sizeof(struct udphdr), NULL);
-	if (m == NULL) {
+	if (ip6cp->ip6c_m->m_pkthdr.len <
+	    ip6cp->ip6c_off + sizeof(struct udphdr))
 		return;
-	}
-	udp = mtod(m, struct udphdr *);
-	if (ntohs(udp->uh_sport) != V_tcp_udp_tunneling_port) {
+	m_copydata(ip6cp->ip6c_m, ip6cp->ip6c_off, sizeof(udp),
+	    (caddr_t)&udp);
+	if (ntohs(udp.uh_sport) != V_tcp_udp_tunneling_port)
 		return;
-	}
-	port = udp->uh_dport;
-	m_adj(m, sizeof(struct udphdr));
-	if ((m->m_flags & M_PKTHDR) == 0) {
-		ip6cp->ip6c_m->m_pkthdr.len -= sizeof(struct udphdr);
-	}
+	port = udp.uh_dport;
+	ip6cp->ip6c_off += sizeof(struct udphdr);
 	/* Now call in to the normal handling code */
 	tcp6_ctlinput_with_port(ip6cp, port);
+	ip6cp->ip6c_off -= sizeof(struct udphdr);
 }
 
 #endif /* INET6 */