bce_rx_intr trusts NIC-DMA'd l2_fhdr_pkt_len with no MCLBYTES bound: heap OOB read up to ~63.5KB past the 2048-byte RX cluster, and unsigned underflow to ~4GB m_len (guaranteed panic)
Summary
The RX completion path takes the received frame's length solely from the l2_fhdr that the NIC itself DMAd into the front of the posted mbuf cluster: len = l2fhdr->l2_fhdr_pkt_len (16-bit, NIC-written); len -= ETHER_CRC_LEN (unsigned underflow for pkt_len<4); m->m_pkthdr.len = m->m_len = len - with no comparison to MCLBYTES (2048) or to the posted BD length anywhere on the path. A faulty or malicious BCM5706-5716 reporting pkt_len=65535 yields an mbuf describing 65531 bytes in a 2048-byte cluster: the stack then reads up to 63507 bytes past the cluster (kernel heap info leak - bytes deliverable to an unprivileged user via bpf/raw socket on bce0; or panic on unmapped page). pkt_len in {0..3} underflows to m_len ~4GB -> immediate panic. Identical pattern filed Medium in pass 1 for seven sibling drivers (DF-1478/1481/1490/1514/1519/1562/1351); no bce finding existed (DB-verified). Linux's bnx2 clamps before skb_put; this driver does not. Requires device-side control of l2_fhdr_pkt_len (hostile PCIe peripheral, firmware bug, erratum); remote attackers cannot set it on a compliant NIC. Guest hardware-absent: honest inconclusive.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3604 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.2 KB | β raw | |
| VERDICT.md | β | 5.1 KB | β raw | |
| trigger.c | β | 2.7 KB | view raw | |
| build.sh | β | 93 B | view raw | |
| run.sh | β | 152 B | view raw | |
| build.log | β | 326 B | view raw | |
| run.log | β | 462 B | view raw | |
| env.txt | β | 1006 B | view raw | |
| fix.diff | β | 1.1 KB | view raw | |
| verdict.json | β | 3.9 KB | view raw | |
| manifest.json | β | 1.3 KB | view raw |
DF-3604 β bce(4) bce_rx_intr trusts NIC-reported l2_fhdr_pkt_len (no MCLBYTES bound) β kernel heap OOB read / unsigned underflow
- File:
sys/dev/netif/bce/if_bce.c(pass 2, GLM 5.3) - Severity: Medium Β· Confidence: certain (code fact) Β· CWE-125 / CWE-191
- Status: inconclusive (runtime) β the audit guest has no Broadcom NetXtreme II hardware; the trigger is device-dependent. Source-level verification is complete and certain.
Build / Run
./build.sh # cc -o trigger trigger.c (documentation/canary skeleton) ./run.sh # guest-side environment proof (module present, no device)
Expected output (on hardware-less guest)
run.sh shows:
pci/if_bceregistered in the kernel (driver code compiled in),kldload if_bceβ "module already loaded or in kernel",NO_BROADCOM_DEVICE/NO_BCE_PCIIDβ only virtio PCI devices,ifconfig -lβvtnet0 lo0,NO_BCE_SYSCTLβ no attach happened, nohw.bce.*tree exists.
On a guest with a BCM5706β5716 NIC, a device reporting
l2_fhdr_pkt_len > MCLBYTES (or < ETHER_CRC_LEN) yields an mbuf whose
m_len exceeds the 2048-byte cluster β heap over-read of up to ~63.5 KB
into packets handed to the stack (info leak) or an immediate panic
(underflow case / unmapped-page walk).
Root cause (path:line)
sys/dev/netif/bce/if_bce.c:4396βlen = l2fhdr->l2_fhdr_pkt_len;(l2fhdrlives at the start of the RX mbuf cluster and is written by the NIC via DMA; the only "validation" is the hardware's own error bits checked at:4402).sys/dev/netif/bce/if_bce.c:4399βlen -= ETHER_CRC_LEN;on anunsigned int:pkt_len β {0..3}wraps to ~4Γ10βΉ.sys/dev/netif/bce/if_bce.c:4437-4439βm_adj(m, 18)thenm->m_pkthdr.len = m->m_len = len;β no comparison againstMCLBYTES(2048) or against the posted BD length anywhere on the path.- The RX cluster is allocated/programmed at:
sys/dev/netif/bce/if_bce.c:3851-3855(m_getcl,m_len = MCLBYTES),sys/dev/netif/bce/if_bce.c:2298-2301(RX DMA tagmaxsize = MCLBYTES),sys/dev/netif/bce/if_bce.c:3905(rx_bd_len = MCLBYTESposted to the NIC).
Why this matters even though the NIC is "trusted"
A compliant NIC cannot DMA more than the posted BD length, but the
driver's job on the receive path is to not hand the stack an mbuf that
describes more data than the buffer holds. One faulty firmware push, a
malicious peripheral (PCIe attach / Thunderclap class), or a chip erratum
converts this into a kernel heap info leak (leaked bytes are deliverable
to an unprivileged user via a raw socket / pcap) or a trivial panic
(underflow). The DragonFly tree itself treats the identical pattern as a
finding in every comparable driver (pass-1 DF-1478 my, DF-1481 vr,
DF-1490 tx, DF-1514 ste, DF-1519 lge, DF-1562 pcn, DF-1351 nfe
β all "NIC length trusted without MCLBYTES bound", all Medium).
Linux's sibling driver (bnx2) clamps before skb_put; this driver does not.
Fix
fix.diff β drop the frame (count ierrors, recycle the buffer) when
pkt_len is outside [sizeof(l2_fhdr) + ETHER_ALIGN + ETHER_CRC_LEN,
MCLBYTES], before the subtraction. Zero-cost in the common case (two
compares).
DF-3604 VERDICT β bce_rx_int trusts l2_fhdr_pkt_len (heap OOB read path)
Status: inconclusive (runtime) Β· source-level: certain
Impact ceiling: kernel heap OOB read (info leak, up to ~63.5 KB per
frame) / panic via unsigned underflow.
What was verified, and how
1. Source-level verification (certain)
Read the full RX path in sys/dev/netif/bce/if_bce.c (guest /usr/src
copy is byte-identical to the audited tree β md5
786cd4c4a8e26f5eab0c12c3987f68b9 both sides):
- RX buffers are single 2048-byte mbuf clusters:
if_bce.c:3851m_new = m_getcl(...)/:3855m_new->m_len = m_new->m_pkthdr.len = MCLBYTES;sys/sys/param.h:497MCLBYTES = (1 << 11) == 2048if_bce.c:2298-2301RX DMA tag created withmaxsize/nmaxseg = MCLBYTES, MCLBYTES;:3905postsrx_bd_len = MCLBYTES.- The only length source for the handed-up frame is the DMA'd header:
if_bce.c:4394l2fhdr = mtod(m, struct l2_fhdr *);if_bce.c:4396len = l2fhdr->l2_fhdr_pkt_len;(16-bit field, NIC-written;if_bcereg.h:845)if_bce.c:4399len -= ETHER_CRC_LEN;βlenisunsigned int;pkt_len < 4wraps to β4 GB.if_bce.c:4402-4414the only sanity gating is the NIC's ownl2_fhdr_statuserror bits β i.e. trusting the same producer.if_bce.c:4437-4439m_adj(m, sizeof(struct l2_fhdr) + ETHER_ALIGN)(=18) thenm->m_pkthdr.len = m->m_len = len;- No clamp exists anywhere on this path. A
pkt_lenof 65535 yieldsm_len = 65531on a 2048-byte cluster β up to 63507 bytes of heap over-read when the stack consumes the mbuf (ether_input β IP reassembly/forwarding/socket copy; bytes land in user-visible packets). Apkt_lenof 0β3 yieldsm_len β 4 GBβ immediate OOB walk β panic (local DoS from the frame's mere presence).
Cross-checked ring math so nothing else bounds it: sw indices are masked
(RX_CHAIN_IDX, if_bcereg.h:5678, TOTAL_RX_BD = 256*pages β clean
power-of-two masks), and bce_get_hw_rx_cons (if_bce.c:4301-4309)
normalizes pointer slots β none of this constrains pkt_len.
2. Guest runtime verification (why inconclusive, not reproduced)
Audit guest: DragonFly 6.5-DEVELOPMENT #0 (X86_64_GENERIC), virtio-net
only. run.sh / run.log evidence:
- driver code present in the running kernel (
kldstat -vβpci/if_bce;kldload if_bceβ "module already loaded or in kernel"), - no Broadcom PCI device:
pciconf -lvshows only QEMU virtio devices (NO_BROADCOM_DEVICE,NO_BCE_PCIID), ifconfig -lβvtnet0 lo0;sysctl hw.bce.*β none (no attach).
β bce_attach() never runs β bce_rx_intr() is unreachable on this
guest. The trigger requires BCM5706/5708/5709/5716 silicon with
device-side control of l2_fhdr_pkt_len (malicious/faulty NIC
firmware, hostile peripheral, or chip erratum). Per the honest-status
table this is "guest reachable but the target device/feature is absent"
β inconclusive (source-certain, hardware-absent).
3. Exploit chain (what it would look like on hardware)
- Compromised/malicious NIC DMAs a frame with
l2_fhdr_pkt_len = 65535(error bits clear) into a posted 2048-byte cluster. bce_rx_intrhands the stack an mbuf withm_len = 65531(if_bce.c:4439).- Unprivileged local user runs a packet sniffer (raw bpf socket on
bce0) and receives up to ~63 KB of kernel heap following the cluster
β heap info leak (slab metadata, other packets, pointers β defeats
no-KASLR is irrelevant, but leaks live object data).
OR
pkt_len = 0βm_len β 4 GBβ guaranteed panic (DoS).
Not further escalatable to a write primitive from this bug alone (the over-read is consumer-side; the NIC's own DMA is bounded by the posted BD). Ceilings: info leak + local DoS, exactly the class of the pass-1 peer findings (DF-1478/DF-1481/DF-1490/DF-1514/DF-1519/DF-1562).
Fix validation
fix.diff applies the clamp (drop + recycle on out-of-range pkt_len).
Not runtime-testable on this guest (no device β same reason as above);
kernel build validation also skipped: the GENERIC kernel already
compiles this driver, and the patch adds two compares + an error branch
using only existing symbols (MCLBYTES, ierrors,
bce_setup_rxdesc_std, bce_rx_int_next_rx) β reviewed against the
surrounding error paths at if_bce.c:4402-4414 which use exactly this
recovery idiom. fix_status = not_testable (hardware absent).
Artifacts
trigger.cβ annotated trigger/canary skeleton (documents the exact device-side precondition).build.sh,run.shβ exact commands.run.logβ full untrimmed guest output proving driver-present / device-absent.env.txtβ guest uname, PCI inventory, kldstate, source md5.fix.diffβ the clamp.
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 MCLBYTES/minimum clamp using the file's own error-recovery idiom (ierrors + bce_setup_rxdesc_std + goto bce_rx_int_next_rx, cf. if_bce.c:4402-4414). Cannot be runtime-validated: the guest has no BCM5706-5716 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.
fix.diff; baseline absence proof in run.log (NO_BROADCOM_DEVICE / NO_BCE_PCIID)
Confirmed kernel references
Detail
Exploit chain
On real hardware: (1) device DMAs RX frame with l2_fhdr_pkt_len=65535 and clear error bits into a posted 2048B cluster; (2) driver hands stack mbuf with m_len=65531; (3) unprivileged user with a bpf/raw socket on bce0 reads up to ~63.5KB of adjacent kernel heap (info leak); pkt_len<4 variant gives m_len~4GB -> immediate panic (local DoS). No write primitive from this bug alone (NIC DMA bounded by posted BD len).
Evidence (decisive lines)
run.log (guest proof: driver present in kernel, zero Broadcom devices, no attach), VERDICT.md (full path:line trace), trigger.c (annotated hardware precondition), fix.diff (drop-frame clamp)
PoC changes
Seed sketch rewritten into an annotated hardware-precondition trigger + canary skeleton; the PoC as originally sketched (syscall-level) is meaningless for a DMA-device bug; honest hardware-absent verification substituted per contract.
Verified recommended fix
In bce_rx_intr, drop and recycle the frame when l2_fhdr_pkt_len is outside [sizeof(l2_fhdr)+ETHER_ALIGN+ETHER_CRC_LEN, MCLBYTES] before using it (see fix.diff).
Verdict
Source-level certain: bce_rx_intr (sys/dev/netif/bce/if_bce.c:4396-4439) sets m->m_pkthdr.len = m->m_len from the NIC-DMA'd l2_fhdr_pkt_len after only 'len -= ETHER_CRC_LEN' (unsigned underflow for pkt_len<4) and an 18-byte m_adj, with no MCLBYTES(2048) bound anywhere on the path β a faulty/malicious BCM5706-5716 can hand the stack an mbuf describing up to 65531 bytes in a 2048-byte cluster (heap OOB read / info leak) or ~4GB (guaranteed panic). Runtime verification is honestly inconclusive: the audit guest has virtio-net only; bce code is in the running kernel (kldstat -v shows pci/if_bce) but no Broadcom PCI device exists, so bce_attach/bce_rx_intr are unreachable (run.log: NO_BROADCOM_DEVICE, NO_BCE_PCIID, ifconfig=vtnet0 lo0, no hw.bce.* sysctls). Guest /usr/src if_bce.c is byte-identical to the audited tree (md5 786cd4c4a8e26f5eab0c12c3987f68b9).
No comments yet.