git: 26248c370fad - main - if_bridge: pull up only the headers bridge_pfil() inspects
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;
+ }
}
}