git: 26248c370fad - main - if_bridge: pull up only the headers bridge_pfil() inspects

From: Alexander Leidinger <netchild_at_FreeBSD.org>
Date: Fri, 11 Sep 2026 17:16:26 UTC
The branch main has been updated by netchild:

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

commit 26248c370fade359e5303094fb57102d7fcc9c78
Author:     Alexander Leidinger <netchild@FreeBSD.org>
AuthorDate: 2026-09-04 07:35:59 +0000
Commit:     Alexander Leidinger <netchild@FreeBSD.org>
CommitDate: 2026-09-11 17:16:09 +0000

    if_bridge: pull up only the headers bridge_pfil() inspects
    
    bridge_pfil() pulled up min(m_pkthdr.len, max_protohdr) bytes.  When the
    mapped head is shorter than that and followed by an unmapped (M_EXTPG)
    mbuf -- a sendfile(2) or KTLS segment from a member advertising
    IFCAP_MEXTPG -- m_pullup() ran into it and dereferenced a NULL mtod(),
    panicking the kernel.
    
    Pull up the Ethernet header first, and the SNAP/LLC header only for an
    802.3 frame.  This is similar to pf and ip_output().
    
    m_pullup() and m_copyup() asserted only the first mbuf; assert inside both
    copy loops so the shape trips the check.
    
    Fixes:          c38abd64dbc1 ("if_epair: support IFCAP_MEXTPG")
    Suggested by:   markj
    Reviewed by:    markj, gallatin
    Assisted-by:    Claude Code (Fable 5, Opus 5)
---
 sys/kern/uipc_mbuf.c |  4 ++++
 sys/net/if_bridge.c  | 35 ++++++++++++++++++++++++-----------
 2 files changed, 28 insertions(+), 11 deletions(-)

diff --git a/sys/kern/uipc_mbuf.c b/sys/kern/uipc_mbuf.c
index 3f7841721a86..726d3ccb5536 100644
--- a/sys/kern/uipc_mbuf.c
+++ b/sys/kern/uipc_mbuf.c
@@ -957,6 +957,8 @@ m_pullup(struct mbuf *n, int len)
 	}
 	space = &m->m_dat[MLEN] - (m->m_data + m->m_len);
 	do {
+		KASSERT((n->m_flags & M_EXTPG) == 0,
+		    ("%s: unmapped mbuf %p in chain", __func__, n));
 		count = min(min(max(len, max_protohdr), space), n->m_len);
 		bcopy(mtod(n, caddr_t), mtod(m, caddr_t) + m->m_len,
 		  (u_int)count);
@@ -1001,6 +1003,8 @@ m_copyup(struct mbuf *n, int len, int dstoff)
 	m->m_data += dstoff;
 	space = &m->m_dat[MLEN] - (m->m_data + m->m_len);
 	do {
+		KASSERT((n->m_flags & M_EXTPG) == 0,
+		    ("%s: unmapped mbuf %p in chain", __func__, n));
 		count = min(min(max(len, max_protohdr), space), n->m_len);
 		memcpy(mtod(m, caddr_t) + m->m_len, mtod(n, caddr_t),
 		    (unsigned)count);
diff --git a/sys/net/if_bridge.c b/sys/net/if_bridge.c
index ef7101f13224..f45b0a5822c9 100644
--- a/sys/net/if_bridge.c
+++ b/sys/net/if_bridge.c
@@ -3956,9 +3956,8 @@ bridge_pfil(struct mbuf **mp, struct ifnet *bifp, struct ifnet *ifp, int dir)
 	if (V_pfil_bridge == 0 && V_pfil_member == 0 && V_pfil_ipfw == 0)
 		return (0); /* filtering is disabled */
 
-	i = min((*mp)->m_pkthdr.len, max_protohdr);
-	if ((*mp)->m_len < i) {
-	    *mp = m_pullup(*mp, i);
+	if ((*mp)->m_len < ETHER_HDR_LEN) {
+	    *mp = m_pullup(*mp, ETHER_HDR_LEN);
 	    if (*mp == NULL) {
 		printf("%s: m_pullup failed\n", __func__);
 		return (-1);
@@ -3972,14 +3971,28 @@ bridge_pfil(struct mbuf **mp, struct ifnet *bifp, struct ifnet *ifp, int dir)
 	 * Check for SNAP/LLC.
 	 */
 	if (ether_type < ETHERMTU) {
-		struct llc *llc2 = (struct llc *)(eh1 + 1);
-
-		if ((*mp)->m_len >= ETHER_HDR_LEN + 8 &&
-		    llc2->llc_dsap == LLC_SNAP_LSAP &&
-		    llc2->llc_ssap == LLC_SNAP_LSAP &&
-		    llc2->llc_control == LLC_UI) {
-			ether_type = htons(llc2->llc_un.type_snap.ether_type);
-			snap = 1;
+		struct llc *llc2;
+
+		i = min((*mp)->m_pkthdr.len,
+		    ETHER_HDR_LEN + sizeof(struct llc));
+		if ((*mp)->m_len < i) {
+			*mp = m_pullup(*mp, i);
+			if (*mp == NULL) {
+				printf("%s: m_pullup failed\n", __func__);
+				return (-1);
+			}
+			eh1 = mtod(*mp, struct ether_header *);
+		}
+
+		if ((*mp)->m_len >= ETHER_HDR_LEN + sizeof(struct llc)) {
+			llc2 = (struct llc *)(eh1 + 1);
+			if (llc2->llc_dsap == LLC_SNAP_LSAP &&
+			    llc2->llc_ssap == LLC_SNAP_LSAP &&
+			    llc2->llc_control == LLC_UI) {
+				ether_type =
+				    htons(llc2->llc_un.type_snap.ether_type);
+				snap = 1;
+			}
 		}
 	}