emx_rxeof trusts the 16-bit NIC descriptor length with no posted-buffer bound: unbounded m_len/m_pkthdr.len -> heap OOB read up to ~63.5KB past the 2046/2048-byte RX cluster (DF-3713 em(4) twin)
Summary
emx_rxeof() reads the 16-bit extended-descriptor write-back length (DMA-written by the NIC) and publishes it directly as m_len/m_pkthdr.len with no comparison against the posted buffer span (2046 bytes when ETHER_ALIGN-trimmed, else 2048). A faulty, erratum-hit, surprise-removed, or malicious DMA source writing 0xFFFF makes the network stack consume up to 65535 bytes from a 2048-byte mbuf cluster - kernel heap OOB read of up to 63489 bytes per descriptor into ether_input/BPF/sockets. Unlike em(4) there is no CRC subtraction and no prev_len_adj logic (SECRC strips CRC in HW), so only the positive overshoot exists. VERIFIED: harness section [B] on the guest - desc_len=0xFFFF produces 63489/63487-byte OOB claims against the exact posting math; honest 1500/2048-byte frames verified in-bounds. Same device-trust boundary as DF-3713/DF-1188.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3736 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| emx_bounds_harness.c | β | 5.5 KB | view raw | |
| build.sh | β | 115 B | view raw | |
| run.sh | β | 105 B | view raw | |
| build.log | β | 139 B | view raw | |
| run.log | β | 347 B | view raw | |
| run.harness.log | β | 2.0 KB | view raw | |
| env.txt | β | 819 B | view raw | |
| fix.diff | β | 755 B | view raw | |
| VERDICT.md | β | 3.0 KB | β raw | |
| README.md | β | 2.2 KB | β raw | |
| verdict.json | β | 3.6 KB | view raw | |
| manifest.json | β | 1.1 KB | view raw |
DF-3736 β emx(4) RX descriptor-length trust (DF-3713 em(4) twin)
What
emx_rxeof() publishes the 16-bit NIC-DMA'd extended-descriptor length with
no bound against the posted buffer:
len = le16toh(current_desc->rxd_length);β sys/dev/netif/emx/if_emx.c:3369 (rxd_length==wb.upper.length, 16 bits, DMA-written by the NIC; extended descriptors enabled viaE1000_RFCTL_EXTEN, if_emx.c:3134;E1000_RCTL_SECRCstrips CRC in HW at :3283 so unlike em(4) there is no-4adjustment anywhere).mp->m_len = len;β if_emx.c:3401m_pkthdr.len = len/+= lenβ if_emx.c:3404 / :3413- posted buffer:
m_getcl()MCLBYTES=2048 cluster (emx_newbuf:2908-2916), front-trimmed by ETHER_ALIGN iffhw.mac.max_frame_size <= 2046(:2918-2919) β usable DMA span 2046/2048 bytes.
A faulty, erratum-hit, or malicious DMA source writing 0xFFFF into the
write-back length makes the stack treat 65535 bytes of adjacent kernel heap
as packet data β heap OOB read of up to ~63.5KB per descriptor into
ether_input/BPF/forwarding consumers (kernel heap info leak / panic).
This is the exact twin of em(4)'s DF-3713 (positive direction of DF-1188). The 2-byte ETHER_ALIGN DMA overflow window is the already-filed DF-1197 (emx) / DF-3712 (em) β NOT re-filed here.
Build / run (guest or any DragonFly box)
cc -O -o /root/emx_bounds_harness /root/emx_bounds_harness.c /root/emx_bounds_harness
Expected output
Section [B] prints, for desc_len=0xFFFF: m_len=65535 claims 63489 bytes
past the 2046-byte posted cluster: kernel heap OOB read of up to 63489 bytes
per descriptor (and 63487 past the 2048-byte untrimmed cluster), plus the
honest-frame safe cases. Exit code 1.
Device-side injection (not possible on this guest)
The audit guest has no emx hardware (kldload if_emx loads the module, no
interface attaches). On hardware, injecting a corrupt write-back length
requires one of: (a) surprise-removal/erratum register-DMA state, (b) a
malicious PCIe device performing peer-to-peer DMA into the descriptor ring,
or (c) an emulated-e1000 hypervisor bug. Per the em(4) family convention
this is recorded as source-certain / runtime-hardware-gated
(inconclusive).
DF-3736 β VERDICT
Status: inconclusive (source-certain, runtime hardware-gated) β same convention as the em(4) twin DF-3713. reproduced=0, impact=none on this guest.
Reproduced what, and why not more
The guest kernel is DragonFly 6.5-DEVELOPMENT with if_emx.ko loadable but
no emx PCI device present (kldload if_emx β module loads, ifconfig -l β
vtnet0 lo0). The descriptor write-back path can therefore only be
exercised by source-line analysis plus the bound-math harness, which was
built and run on the guest (run.harness.log, exit 1):
desc_len=0xFFFF(non-EOP and EOP) βm_len=m_pkthdr.len=65535in a 2046-byte (trimmed) or 2048-byte posted cluster β 63489/63487-byte heap OOB read claim, demonstrated arithmetically against the exact posting math ofemx_newbuf()(if_emx.c:2908-2919).- honest frames (1500 std, 2048 jumbo fragment) verified in-bounds β the check I propose never rejects honest hardware.
Root cause chain (path:line)
- NIC DMA-writes the extended RX descriptor incl. 16-bit
wb.upper.length(descriptor formatsys/dev/netif/emx/if_emx.h:204-211). emx_rxeof()reads it:len = le16toh(current_desc->rxd_length)βsys/dev/netif/emx/if_emx.c:3369. No comparison against the posted span anywhere in the loop (:3352-3461).- Publishes it:
mp->m_len = len(:3401),m_pkthdr.len = len(:3404),m_pkthdr.len += len(:3413 for chained fragments). - Frame handed to
ifp->if_input(:3453) β ether_input/BPF/socket consumers walkm_len/m_pkthdr.lenbytes of a 2048-byte cluster.
Unlike em(4) (DF-3713) there is no CRC adjustment and no prev_len_adj
logic (emx sets E1000_RCTL_SECRC, CRC stripped in HW, :3283) β the
em(4)-specific negative-m_len variant does not exist here; the positive
overshoot (OOB read / info leak) does, identically.
Threat model
Not reachable by an unauthenticated remote peer with a functioning NIC (the MAC writes the true received length). Reachable via: faulty/erratum NIC writing corrupt write-backs, surprise removal (stale/coherent-memory corruption), or a malicious/emulated PCIe DMA source. Impact ceiling: kernel heap disclosure into the network stack (leak) or panic. Family severity: Medium (matches DF-3713).
Fix
fix.diff (git-apply clean against the read-only tree): discard the frame
(same discard: path the error/newbuf-failure cases already use) when
len == 0 or len exceeds the trim-aware posted span. Cannot be runtime-
validated on this guest (no hardware) β fix_status: not_testable, but the
harness's honest-frame cases prove the bound never rejects legitimate
traffic (fragment β€ 2048 untrimmed; β€ 2046 trimmed).
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 authored against the read-only sys/ tree (git apply --check clean); cannot be runtime-validated on a guest without emx hardware. Harness honest-frame cases show the bound never rejects legitimate traffic.
findings/poc/DF-3736/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
findings/poc/DF-3736/VERDICT.md; run.harness.log (section [B]: desc_len=0xFFFF -> 'm_len=65535 claims 63489 bytes past the 2046-byte posted cluster'); build.log; env.txt (no emx hardware); fix.diff (discard len==0 or len > trim-aware posted span, git-apply clean)
PoC changes
No seed PoC. emx_bounds_harness.c authored for this finding ([A] excluded - that is the known DF-1197 window, deliberately not re-filed).
Verified recommended fix
In emx_rxeof(), discard the frame (existing discard: path) when len == 0 or len exceeds the trim-aware posted span MCLBYTES - (max_frame_size <= MCLBYTES - ETHER_ALIGN ? ETHER_ALIGN : 0).
Verdict
Source-certain, runtime hardware-gated (same convention as the em(4) twin DF-3713): emx_rxeof (sys/dev/netif/emx/if_emx.c:3337-3468) publishes the 16-bit NIC-DMA'd extended-descriptor length with no bound against the posted buffer: len = le16toh(current_desc->rxd_length) at :3369 (rxd_length == wb.upper.length, 16 bits, if_emx.h:207; extended descriptors via E1000_RFCTL_EXTEN :3134; E1000_RCTL_SECRC at :3283 strips CRC in HW so there is no -4 adjustment and no prev_len_adj logic - the em(4)-specific negative-m_len variant does not exist here) -> mp->m_len = len at :3401, m_pkthdr.len =/+= len at :3404/:3413, handoff at :3453. Posted buffer is an MCLBYTES=2048 m_getcl() cluster (emx_newbuf:2908-2916), front-trimmed by ETHER_ALIGN iff hw.mac.max_frame_size <= 2046 (:2918-2919) -> usable DMA span 2046/2048. A faulty/erratum/malicious DMA source writing 0xFFFF into the write-back length makes the stack walk up to 63489 bytes past the cluster (heap OOB read into ether_input/BPF/sockets = kernel heap info leak, or panic). Bound-math harness built and run on the guest (run.harness.log: 63489/63487-byte overshoot claims demonstrated; honest 1500/2048-byte frames verified in-bounds, so the proposed check never rejects legitimate traffic). Device-side injection needs faulty/removal/malicious-PCIe DMA - not present on the audit guest (kldload if_emx loads, no interface attaches) -> inconclusive per family convention.
No comments yet.