bge_rxeof trusts NIC-DMA'd return-ring bge_len with no MCLBYTES/jumbo bound: heap OOB read / kernel info leak up to ~63.5KB past the RX cluster (OOB write on i386 ALIGNBUG path)
Summary
bge_rxeof sets m->m_pkthdr.len = m->m_len = cur_rx->bge_len - ETHER_CRC_LEN straight from the NIC-DMA'd RX return-ring descriptor (uint16_t) with no bound by the posted buffer size - 2046 usable bytes for the std cluster or 9016 for a jumbo pool slot. A faulty or malicious BCM57xx handing up bge_len=65535 produces an mbuf describing 65531 bytes in that small buffer; ether_input's ETHER_BPF_MTAP copies the bogus length to bpf listeners => kernel heap disclosure; IP-stack parsing reads the same OOB range (leak/panic). On !x86_64 builds the BGE_FLAG_RX_ALIGNBUG path (BCM5701 PCI-X) additionally bcopy's cur_rx->bge_len bytes into the cluster => heap OOB WRITE; compiled out on x86_64. bge_len<18 gives a negative m_len harmlessly dropped (no bce/DF-3604-style underflow panic). Producer-side bounds (BD len, RX_MTU, RCB) are all device-enforced and thus no defense against the defecting device this check guards against. The bce/bnx lesson applied to bge; DB-verified not covered by DF-1159 or the DF-1478..1562 sweep. Fix: clamp-and-drop before ANY use of the length including the i386 bcopy.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3663 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trigger.c | β | 4.5 KB | view raw | |
| build.sh | β | 107 B | view raw | |
| run.sh | β | 167 B | view raw | |
| build.log | β | 84 B | view raw | |
| run.log | β | 735 B | view raw | |
| env.txt | β | 440 B | view raw | |
| fix.diff | β | 593 B | view raw | |
| README.md | β | 1.7 KB | β raw | |
| VERDICT.md | β | 4.9 KB | β raw | |
| manifest.json | β | 1.2 KB | view raw | |
| verdict.json | β | 5.3 KB | view raw |
DF-3663 β bge_rxeof trusts NIC-DMA'd return-ring bge_len with no buffer bound
Build / Run / Expected
./build.sh # cc -O -Wall -o trigger trigger.c (in a DragonFly guest) ./run.sh # runs the trigger + records the guest-side reachability proof
Expected on real BCM57xx hardware (NOT available in this environment):
the device DMAs an RX return-ring BD with bge_len larger than the posted
buffer (2046 std / 9016 jumbo usable bytes) and error flags clear;
bge_rxeof (sys/dev/netif/bge/if_bge.c:3144) hands the stack an mbuf whose
m_len/m_pkthdr.len is up to 65531 inside that small buffer. Any bpf(4)
listener (tcpdump -i bge0 -w out.pcap) then receives up to ~63.5 KB of
kernel heap adjacent to the RX cluster β a kernel info disclosure. On i386
builds the BGE_FLAG_RX_ALIGNBUG path (if_bge.c:3138-3141) first writes
that many bytes past the cluster (heap overflow).
Actual on the audit guest: no Broadcom PCI device exists (virtio-net
only), so bge_attach/bge_rxeof are unreachable β the trigger builds
clean and prints its hardware-precondition notice (run.log). Verification
is therefore honestly inconclusive (source-level certain; see
VERDICT.md).
Files
trigger.cβ annotated hardware-precondition triggerrun.logβ guest proof:pci/if_bgecompiled into the kernel,NO_BROADCOM_DEVICE,ifconfig -l=vtnet0 lo0,sysctl hw.bgeunknown, /usr/src md5 == audited tree, clean build, trigger outputfix.diffβ git-apply-able clamp-and-drop (bounds the NIC length by the mbuf's own posted length before ANY use, including the i386 ALIGNBUG bcopy)verdict.json/manifest.jsonβ machine verdict
DF-3663 VERDICT β bge_rxeof trusts NIC-DMA'd return-ring bge_len
Status: INCONCLUSIVE at runtime (no bge hardware on the audit guest); source-level CERTAIN.
Root cause (path:line, audited tree == guest /usr/src, md5-verified)
sys/dev/netif/bge/if_bge.c:3144:
m->m_pkthdr.len = m->m_len = cur_rx->bge_len - ETHER_CRC_LEN;
cur_rx is a struct bge_rx_bd in the host-memory RX return ring
(sc->bge_ldata.bge_rx_return_ring[sc->bge_rx_saved_considx],
if_bge.c:3058-3059) β memory the NIC fills in by DMA. bge_len is a
plain uint16_t (sys/dev/netif/bge/if_bgereg.h:2327-2331); nothing on
the path bounds it by the buffer that was actually posted to the device:
- std ring:
bge_newbuf_std()(if_bge.c:1016-1057) posts anm_getcl()cluster β MCLBYTES = 2048 β withm_adj(ETHER_ALIGN)(if_bge.c:1027-1030) leaving 2046 usable bytes; the producer BD length isrc->bge_mbuf->m_len(if_bge.c:1070). - jumbo ring (bge supports jumbo, unlike the bnx sibling DF-3639):
bge_newbuf_jumbo()(if_bge.c:1079-1123) attaches a driver-pool slot ofBGE_JUMBO_FRAMELEN= 9018 bytes (if_bgevar.h:56) withm_adj(ETHER_ALIGN)β 9016 usable; the 384 slots are carved from one contiguousBGE_JMEMDMA block (if_bge.c:910-921), so an oversizedbge_lenon the last slot reads past the entire allocation.
A faulty or malicious device that writes bge_len = 65535 (error flags
clear) makes the driver hand the stack an mbuf describing 65531 bytes
inside the 2046/9016-byte buffer.
Sinks
ifp->if_input(ifp, m, NULL, -1)(if_bge.c:3170) βether_input()runsETHER_BPF_MTAP(ifp, m)(sys/net/if_ethersubr.c:1512) before any protocol validation β the bpf tap copiesm_pkthdr.lenbytes out of the cluster to every capturing socket β kernel heap OOB read / info disclosure of up to ~63.5 KB (std) per frame.- IP/ARP/VLAN parsing and socket copies walk the same OOB range (further leak; fault/panic when crossing an unmapped page).
- i386-only write variant (CWE-787): with
BGE_FLAG_RX_ALIGNBUG(BCM5701 PCI-X, if_bge.c:2206-2208),#if !defined(__x86_64__)code at if_bge.c:3138-3141 executesbcopy(m->m_data, m->m_data + ETHER_ALIGN, cur_rx->bge_len)β the same untrusted length used as a copy size into the 2048-byte cluster β heap OOB write. Compiled out of x86_64 kernels (the audit guest's platform).
Non-issues checked (so the finding is exactly scoped)
bge_len < ETHER_HDR_LEN + ETHER_CRC_LEN(18) β negativem_len, harmlessly dropped at sys/net/if_ethersubr.c:1502-1506 (m_len < sizeof(struct ether_header)) β no bce/DF-3604-style unsigned-underflow panic here.- The producer-side BD length,
BGE_RX_MTUregister (if_bge.c:3769) and RCB max-frame (if_bge.c:1575) are all device-side limits a healthy NIC enforces; the defecting device this check guards against is precisely the one that ignores them. - The unvalidated
bge_idxindex issue is DF-1159 (pass 1) and is not re-reported here; this finding is the independent length trust.
Why inconclusive at runtime
The audit QEMU guest has virtio-net only:
pciconf -l | grep -i -E "bge|broadcom|14e4" β NO_BROADCOM_DEVICE,
ifconfig -l β vtnet0 lo0, sysctl hw.bge β unknown oid
(run.log). pci/if_bge is compiled into the running kernel
(kldstat -v id 59), and the guest's /usr/src/sys/dev/netif/bge/
md5-matches the audited tree, so the code is present and identical β
only the device (and therefore bge_attach β bge_rxeof) is
unreachable. No emulation of a BCM57xx RX engine exists in the harness.
Same classification as the bnx/bce siblings DF-3639/DF-3604.
Fix validation
fix.diff clamps the frame to the posted buffer (m->m_len) and drops
it (ierrors++, m_freem, continue) when bge_len is outside
[ETHER_HDR_LEN + ETHER_CRC_LEN, m->m_len + ETHER_CRC_LEN], placed
before the #if !defined(__x86_64__) block so the i386 ALIGNBUG
bcopy is covered too. git apply --check passes against the pristine
tree. Runtime baseline-vs-patched comparison is impossible for the same
hardware reason (fix_status: not_testable).
Exploitability ceiling
Local info-leak class (kernleak): unprivileged user + bpf listener reads adjacent kernel heap (adjacent mbuf clusters, potentially pointers/data). Not a write primitive on x86_64; on i386 with a BCM5701 PCI-X part it is additionally a NIC-directed heap overwrite. Requires a misbehaving device (malicious peripheral / firmware compromise / DMA attack), hence AC:H and Medium severity, matching the calibrated sibling findings.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testablefix.diff bounds the NIC-reported length by the mbuf's own posted length (correct for both std 2046/2048 and jumbo 9016/9018 configs, ALIGNBUG or not) and is placed before the #if !defined(x86_64) block so the OOB-write variant is covered too; git apply --check passes against the pristine tree. Cannot be runtime-validated: the guest has no BCM57xx device so neither baseline nor patched behavior is exercisable (same reason the PoC is inconclusive). The patch mirrors the error-drop idiom already used at if_bge.c:3111-3116.
fix.diff (git apply --check OK); baseline absence proof in run.log (NO_BROADCOM_DEVICE, sysctl hw.bge unknown oid)
Confirmed kernel references
- sys/dev/netif/bge/if_bge.c:3144
- sys/dev/netif/bge/if_bge.c:3058
- sys/dev/netif/bge/if_bge.c:3138
- sys/dev/netif/bge/if_bge.c:1027
- sys/dev/netif/bge/if_bge.c:1070
- sys/dev/netif/bge/if_bge.c:1103
- sys/dev/netif/bge/if_bge.c:1112
- sys/dev/netif/bge/if_bge.c:2206
- sys/dev/netif/bge/if_bgereg.h:2330
- sys/dev/netif/bge/if_bgevar.h:56
- sys/net/if_ethersubr.c:1502
- sys/net/if_ethersubr.c:1512
Detail
Exploit chain
On real hardware: (1) device DMAs an RX return-ring BD with bge_len=65535 and error flag clear; (2) driver hands the stack an mbuf with m_len=65531 in a 2046B std cluster (or past a 9016B jumbo slot); (3) unprivileged user with a bpf listener on bge0 reads up to ~63.5KB of adjacent kernel heap (info leak); IP-stack parsing of the same OOB range can also fault (panic/DoS); on i386 + BCM5701 PCI-X the ALIGNBUG bcopy turns the same trusted length into a heap OOB write. No write primitive on x86_64 from this bug alone.
Evidence (decisive lines)
run.log (guest proof: driver in kernel, zero Broadcom devices, no attach, src md5 match, clean -Wall build), VERDICT.md (full path:line trace incl. the benign underflow path and the i386 write variant), trigger.c (annotated hardware-precondition trigger, builds clean -O -Wall), fix.diff (git apply --check OK; clamp-and-drop bounded by the mbuf's own posted length, placed before the i386 bcopy)
PoC changes
No seed existed (new pass-2 finding); trigger written as an annotated hardware-precondition skeleton per the DF-3604/DF-3639 precedent, built and executed on the guest to prove the code path is unreachable there.
Verified recommended fix
In bge_rxeof, drop the frame (ierrors++ + m_freem) when cur_rx->bge_len is outside [ETHER_HDR_LEN + ETHER_CRC_LEN, m->m_len + ETHER_CRC_LEN] before any use β including the i386 ALIGNBUG bcopy (see fix.diff).
Verdict
Source-level certain: bge_rxeof (sys/dev/netif/bge/if_bge.c:3144) sets m->m_pkthdr.len = m->m_len = cur_rx->bge_len - ETHER_CRC_LEN directly from the NIC-DMA'd return-ring descriptor with no bound anywhere on the path β a faulty or malicious BCM570x/57xx can hand the stack an mbuf describing up to 65531 bytes in a 2046-byte std cluster (m_getcl + m_adj(ETHER_ALIGN), if_bge.c:1024-1030) or a 9016-byte jumbo pool slot (bge HAS jumbo support, unlike the bnx sibling DF-3639; if_bge.c:1087-1114, slots carved from one contiguous BGE_JMEM block at if_bge.c:910-921, so overflow of the last slot runs past the whole DMA allocation). ether_input's ETHER_BPF_MTAP (sys/net/if_ethersubr.c:1512) then copies the bogus length out of the buffer to bpf listeners => kernel heap OOB read / info disclosure of up to ~63.5KB; bge_len < 18 gives a negative m_len which if_ethersubr.c:1502-1506 drops harmlessly (no underflow panic, unlike bce DF-3604). Additional i386-only WRITE variant (CWE-787): the BGE_FLAG_RX_ALIGNBUG path (BCM5701 PCI-X, if_bge.c:2206-2208) bcopy's cur_rx->bge_len bytes into the cluster at if_bge.c:3138-3141 under #if !defined(x86_64) β compiled out of x86_64 kernels. Runtime verification honestly inconclusive: the audit guest has virtio-net only; bge is compiled into the running kernel (kldstat -v: pci/if_bge id 59) but no Broadcom PCI device exists, so bge_attach/bge_rxeof are unreachable (run.log: NO_BROADCOM_DEVICE, ifconfig=vtnet0 lo0, sysctl hw.bge unknown oid). Guest /usr/src if_bge.c md5 f6c2d771384a52775cd4bb399d5dcab1 (plus if_bgereg.h/if_bgevar.h) byte-identical to the audited tree.
No comments yet.