git: f1726686cb31 - main - iflib: Do not pull up trailing IPv4 fragments to the L4 header

From: R. Christian McDonald <rcm_at_FreeBSD.org>
Date: Fri, 23 Oct 2026 18:29:51 UTC
The branch main has been updated by rcm:

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

commit f1726686cb31ff141e507760124f44442c9f141a
Author:     R. Christian McDonald <rcm@FreeBSD.org>
AuthorDate: 2026-10-23 18:28:29 +0000
Commit:     R. Christian McDonald <rcm@FreeBSD.org>
CommitDate: 2026-10-23 18:28:29 +0000

    iflib: Do not pull up trailing IPv4 fragments to the L4 header
    
    iflib_parse_header() pulls every IPv4 packet up to the end of the TCP or
    UDP header, including fragments at a non-zero offset, which carry no L4
    header at all.  A trailing fragment shorter than that header (1-7 bytes
    of UDP, 1-19 bytes of TCP) makes m_pullup() fail and the frame is freed
    without a trace in the interface counters.
    
    Only parse the L4 header on the first fragment.  TSO never produces
    fragments, so assert that.
    
    With a 1500-byte MTU every UDP datagram of 1473-1479 bytes is lost
    behind a forwarding host whose ingress NIC sets receive checksum flags,
    or on any VLAN with hardware tagging.  SIP over UDP is a common victim.
    
    Reported by:    Alex Roberts
    Reviewed by:    gallatin
    Fixes:          c224b2ce7de0 ("iflib: don't pullup UDP payloads to the TCP header size")
    See also:       https://redmine.pfsense.org/issues/17196
    MFC after:      3 days
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
---
 sys/net/iflib.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/sys/net/iflib.c b/sys/net/iflib.c
index d47c108292ae..6682ccbcfca8 100644
--- a/sys/net/iflib.c
+++ b/sys/net/iflib.c
@@ -3674,7 +3674,7 @@ iflib_parse_header(iflib_txq_t txq, if_pkt_info_t pi, struct mbuf **mp)
 	case ETHERTYPE_IP:
 	{
 		struct ip *ip;
-		struct tcphdr *th;
+		struct tcphdr *th = NULL;
 		uint8_t hlen;
 
 		hlen = pi->ipi_ehdrlen + sizeof(*ip);
@@ -3685,7 +3685,13 @@ iflib_parse_header(iflib_txq_t txq, if_pkt_info_t pi, struct mbuf **mp)
 		}
 		ip = (struct ip *)(m->m_data + pi->ipi_ehdrlen);
 		hlen = pi->ipi_ehdrlen + (ip->ip_hl << 2);
-		if (ip->ip_p == IPPROTO_TCP) {
+		if ((ip->ip_off & htons(IP_OFFMASK)) != 0) {
+			/*
+			 * Only the first fragment carries the TCP/UDP header,
+			 * so do not extend hlen beyond the IP header.
+			 */
+			MPASS(!IS_TSO4(pi));
+		} else if (ip->ip_p == IPPROTO_TCP) {
 			hlen += sizeof(*th);
 			th = (struct tcphdr *)((char *)ip + (ip->ip_hl << 2));
 		} else if (ip->ip_p == IPPROTO_UDP) {
@@ -3697,7 +3703,8 @@ iflib_parse_header(iflib_txq_t txq, if_pkt_info_t pi, struct mbuf **mp)
 				return (ENOMEM);
 			/* reset pointers after pullup */
 			ip = (struct ip *)(m->m_data + pi->ipi_ehdrlen);
-			th = (struct tcphdr *)((char *)ip + (ip->ip_hl << 2));
+			if (th != NULL)
+				th = (struct tcphdr *)((char *)ip + (ip->ip_hl << 2));
 		}
 		pi->ipi_ip_hlen = ip->ip_hl << 2;
 		pi->ipi_ipproto = ip->ip_p;
@@ -3706,7 +3713,7 @@ iflib_parse_header(iflib_txq_t txq, if_pkt_info_t pi, struct mbuf **mp)
 
 		/* TCP checksum offload may require TCP header length */
 		if (IS_TX_OFFLOAD4(pi)) {
-			if (__predict_true(pi->ipi_ipproto == IPPROTO_TCP)) {
+			if (__predict_true(th != NULL)) {
 				pi->ipi_tcp_hflags = tcp_get_flags(th);
 				pi->ipi_tcp_hlen = th->th_off << 2;
 				pi->ipi_tcp_seq = th->th_seq;