bnx_rxeof trusts NIC-DMA'd return-ring bge_len with no MCLBYTES bound: heap OOB read / kernel info leak up to ~63.5KB past the 2048-byte RX cluster
Summary
bnx_rxeof assigns m->m_pkthdr.len = m->m_len = cur_rx->bge_len - ETHER_CRC_LEN straight from the return-ring descriptor the NIC fills via DMA, with no upper bound against the 2046-byte usable cluster and no lower bound. A faulty or malicious BCM5717/5719/5720/577xx writing bge_len=65535 hands the stack an mbuf describing 65531 bytes in a 2048-byte cluster; ether_input's ETHER_BPF_MTAP copies the bogus length to bpf listeners (kernel heap disclosure) and IP-stack parsing walks the same OOB range (fault/panic on unmapped pages). The bge_len<18 variant yields a negative m_len that if_ethersubr drops harmlessly - no underflow panic on DragonFly, unlike the bce sibling DF-3604. Hardware-side clamps (BD len 2046, RX_MTU 1522, RCB 1536) are all device-controlled and thus not a defense. The pass-1 sibling sweep and pass-2 bce never covered bnx (DB-verified) - not a re-report. Requires a NIC that writes an out-of-range bge_len (buggy firmware/silicon, or malicious PCIe/DMA peripheral); on such a device an unprivileged bpf listener reads up to ~63.5KB of adjacent kernel heap per frame. Guest hardware-absent: inconclusive, source-certain.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3639 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.7 KB | β raw | |
| VERDICT.md | β | 4.9 KB | β raw | |
| trigger.c | β | 3.4 KB | view raw | |
| build.sh | β | 117 B | view raw | |
| run.sh | β | 231 B | view raw | |
| build.log | β | 74 B | view raw | |
| run.log | β | 650 B | view raw | |
| env.txt | β | 800 B | view raw | |
| fix.diff | β | 982 B | view raw | |
| verdict.json | β | 4.4 KB | view raw |
DF-3639 β bnx(4) RX length trust (bge_len) β evidence pack
What: bnx_rxeof() (sys/dev/netif/bnx/if_bnx.c:2848) assigns
m->m_pkthdr.len = m->m_len = cur_rx->bge_len - ETHER_CRC_LEN from the
NIC-DMA'd return-ring descriptor with no bound against the 2046-byte
usable cluster and no lower bound.
Impact (hardware-conditional): faulty/malicious BCM5717/5719/5720/577xx
hands the stack an mbuf describing up to 65531 bytes in a 2048-byte cluster β
kernel heap OOB read of up to ~63.5 KB disclosed via bpf
(sys/net/if_ethersubr.c:1512) and the IP stack. bge_len < 18 variant is
dropped harmlessly by ether_input (if_ethersubr.c:1499-1503).
Reproduce (audit guest β honest negative):
cc -O -Wall -o trigger trigger.c # guest; builds clean ./run.sh # via dfbsd-qemu/vm.sh run_root
Expected on the audit guest: trigger prints the hardware precondition;
evidence block shows pci/if_bnx in the kernel, NO_BROADCOM_DEVICE,
ifconfig -l = vtnet0 lo0, sysctl hw.bnx unknown oid, and
/usr/src/sys/dev/netif/bnx/if_bnx.c md5 identical to the audited tree
β runtime verdict inconclusive (source-level certainty).
On real hardware: force the device to write bge_len = 65535 (error flag
clear) into an RX return-ring BD (modified firmware / malicious peripheral /
erratum), then tcpdump -i bnx0 -w out.pcap β captured frames exceed 2046
bytes and contain adjacent kernel heap.
Fix: fix.diff (drop out-of-range frames, bounded by the mbuf's own
posted length). Not runtime-validatable on this guest (no bnx hardware).
Files: VERDICT.md (full narrative), trigger.c, build.sh/run.sh, build.log/run.log (untrimmed guest transcripts), env.txt, manifest.json, verdict.json.
DF-3639 β bnx(4) trusts NIC-DMA'd return-ring bge_len with no buffer bound
File: sys/dev/netif/bnx/if_bnx.c (pass 2, GLM 5.3)
Class: kernel heap OOB read / info disclosure (CWE-125 / NIC-trust)
Severity filed: Medium, bucket kernleak, confidence certain (source-level)
Verdict: inconclusive at runtime β the audit guest has no Broadcom
hardware; the driver is compiled in but bnx_attach() is unreachable there.
1. Root cause (path:line)
bnx_rxeof() consumes RX return-ring buffer descriptors that the NIC has
filled in via DMA into host memory:
sys/dev/netif/bnx/if_bnx.c:2810βcur_rx = &ret->bnx_rx_ret_ring[...]sys/dev/netif/bnx/if_bnx.c:2848βm->m_pkthdr.len = m->m_len = cur_rx->bge_len - ETHER_CRC_LEN;
struct bge_rx_bd (sys/dev/netif/bge/if_bgereg.h:2327-2346) is the shared
host-memory descriptor; its bge_len field is a uint16_t written by the
device. The receiving mbuf was allocated as:
if_bnx.c:816-822βm_getcl(M_*, MT_DATA, M_PKTHDR)+m_adj(ETHER_ALIGN)=> a 2048-byte cluster with 2046 usable bytes.
Between 2842 (bnx_newbuf_std() swaps in a fresh cluster) and 2848 there is
no check that bge_len - ETHER_CRC_LEN fits the cluster. There is also no
lower bound check (a bge_len < 18 yields a negative m_len).
2. Consequence trace
For a device that writes bge_len = 65535 (firmware compromise, malicious
peripheral / Thunderclap-style DMA, or a silicon erratum) with the error flag
clear:
m->m_len = m->m_pkthdr.len = 65531describing a 2048-byte cluster (if_bnx.c:2848).ifp->if_input(ifp, m, pi, cpuid)(if_bnx.c:2881) βether_input().sys/net/if_ethersubr.c:1512βETHER_BPF_MTAP(ifp, m)copiesmin(m->m_pkthdr.len, ...)bytes out of the cluster to every attached bpf(4) listener β up to ~63.5 KB of kernel heap adjacent to the RX cluster is disclosed (pointers, stale packet data, etc.).- IP-stack parsing (
ip_input) and socket copies walk the same OOB range; crossing an unmapped page faults β panic (local DoS from a driver defect, or crash-for-remote if the OOB range hits a guard page).
For bge_len < ETHER_HDR_LEN + ETHER_CRC_LEN (18): m_len goes negative;
DragonFly's ether_input discards it at sys/net/if_ethersubr.c:1499-1503
(m->m_len < sizeof(struct ether_header) β m_freem) β a benign drop, no
underflow panic on this path (differs from bce DF-3604, where the length is
unsigned and the m_adj happens after the assignment).
3. Why hardware bounds don't save you
A healthy NIC clamps frames to the posted BD length (2046, if_bnx.c:843,871),
the MAC BGE_RX_MTU = 1522 (if_bnx.c:3620-3621) and the std RCB max frame
1536 (if_bnx.c:1348-1349). All of these are device-side registers/memory the
defecting device itself controls; the kernel-side check at 2848 is the missing
last line of defense. This is exactly the DF-3604 (bce) pattern; the pass-1
sibling sweep (DF-1478 my, DF-1481 vr, DF-1490 tx, DF-1514 ste, DF-1519 lge,
DF-1562 pcn, DF-1351 nfe, DF-1526 sf) never covered bnx (DB-verified), so this
is not a re-report.
4. Guest evidence (why inconclusive, honestly)
run.log:
- kldstat -v β 61 pci/if_bnx β driver compiled into the running kernel.
- pciconf -l | grep 14e4 β NO_BROADCOM_DEVICE β no Broadcom PCI device.
- ifconfig -l β vtnet0 lo0 β no bnx interface; sysctl hw.bnx β unknown
oid β bnx_attach() never executed.
- md5 /usr/src/sys/dev/netif/bnx/if_bnx.c = 49a18c4b7642b9b2ebd1d02ad11542e4
== audited tree β the unreachable code is byte-identical to what was audited.
trigger.c builds clean (cc -O -Wall) and runs, printing the hardware
precondition. No QEMU emulation of a BCM5717+ exists in this setup, so the
DMA-dependent primitive cannot be exercised: verdict inconclusive /
reproduced: 0, impact conditional on hostile/faulty hardware.
5. Exploit chain (on real hardware)
device writes bge_len=65535, error-flag clear β driver hands stack an mbuf with m_len=65531 in a 2048B cluster β unprivileged user with a bpf listener on bnx0 (or a TCP/UDP peer receiving forwarded data copied from the OOB range) reads adjacent kernel heap β info leak. No write primitive from this bug alone (the NIC's DMA write is bounded by the posted BD length it honors).
6. Fix
fix.diff β drop-and-count when bge_len is outside
[ETHER_HDR_LEN + ETHER_CRC_LEN, m->m_len + ETHER_CRC_LEN], using the mbuf's
own posted length as the bound (stays correct even if jumbo ring support is
ever enabled). Fix is not runtime-validatable on this guest (no bnx hardware):
fix_status: not_testable.
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 adds the clamp using the mbuf's own posted length as the bound (correct even if BNX_FLAG_JUMBO is ever enabled). Cannot be runtime-validated: the guest has no BCM5717+ device so neither baseline nor patched behavior is exercisable (same reason the PoC is inconclusive). The patch touches only existing symbols on an already-compiled path and mirrors the error-drop idiom used at if_bnx.c:2836-2841.
fix.diff; baseline absence proof in run.log (NO_BROADCOM_DEVICE, sysctl hw.bnx unknown oid)
Confirmed kernel references
- sys/dev/netif/bnx/if_bnx.c:2848
- sys/dev/netif/bnx/if_bnx.c:2842
- sys/dev/netif/bnx/if_bnx.c:816
- sys/dev/netif/bnx/if_bnx.c:821
- sys/dev/netif/bnx/if_bnx.c:843
- sys/dev/netif/bnx/if_bnx.c:1348
- sys/dev/netif/bnx/if_bnx.c:3620
- sys/dev/netif/bnx/if_bnx.c:2881
- sys/dev/netif/bge/if_bgereg.h:2327
- sys/net/if_ethersubr.c:1499
- 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 2048B cluster; (3) unprivileged user with a bpf listener on bnx0 reads up to ~63.5KB of adjacent kernel heap (info leak); IP-stack parsing of the same OOB range can also fault (panic/DoS). No write primitive from this bug alone (NIC DMA write is bounded by the posted BD len).
Evidence (decisive lines)
run.log (guest proof: driver in kernel, zero Broadcom devices, no attach, src md5 match), VERDICT.md (full path:line trace incl. the benign underflow path), trigger.c (annotated hardware-precondition trigger, builds clean -O -Wall), fix.diff (drop-frame clamp bounded by the mbuf's own posted length)
PoC changes
No seed existed (new pass-2 finding); trigger written as an annotated hardware-precondition skeleton per the DF-3604 precedent, built and executed on the guest to prove the code path is unreachable there.
Verified recommended fix
In bnx_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 using it (see fix.diff).
Verdict
Source-level certain: bnx_rxeof (sys/dev/netif/bnx/if_bnx.c:2848) 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 MCLBYTES bound anywhere on the path β a faulty or malicious BCM5717/5719/5720/577xx can hand the stack an mbuf describing up to 65531 bytes in a 2048-byte cluster (2046 usable after m_adj(ETHER_ALIGN), if_bnx.c:816-822). ether_input's ETHER_BPF_MTAP (sys/net/if_ethersubr.c:1512) then copies the bogus length out of the cluster 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:1499-1503 drops harmlessly (no underflow panic, unlike bce DF-3604). Runtime verification honestly inconclusive: the audit guest has virtio-net only; bnx is compiled into the running kernel (kldstat -v: pci/if_bnx) but no Broadcom PCI device exists, so bnx_attach/bnx_rxeof are unreachable (run.log: NO_BROADCOM_DEVICE, ifconfig=vtnet0 lo0, sysctl hw.bnx unknown oid). Guest /usr/src if_bnx.c md5 49a18c4b7642b9b2ebd1d02ad11542e4 is byte-identical to the audited tree.
No comments yet.