β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3736

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3736 Β· 12 files
FileTypeDescriptionSize
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 via E1000_RFCTL_EXTEN, if_emx.c:3134; E1000_RCTL_SECRC strips CRC in HW at :3283 so unlike em(4) there is no -4 adjustment anywhere).
  • mp->m_len = len; β€” if_emx.c:3401
  • m_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 iff hw.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).

VERDICT.md
↓ download raw

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=65535 in a 2046-byte (trimmed) or 2048-byte posted cluster β†’ 63489/63487-byte heap OOB read claim, demonstrated arithmetically against the exact posting math of emx_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)

  1. NIC DMA-writes the extended RX descriptor incl. 16-bit wb.upper.length (descriptor format sys/dev/netif/emx/if_emx.h:204-211).
  2. 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).
  3. Publishes it: mp->m_len = len (:3401), m_pkthdr.len = len (:3404), m_pkthdr.len += len (:3413 for chained fragments).
  4. Frame handed to ifp->if_input (:3453) β†’ ether_input/BPF/socket consumers walk m_len/m_pkthdr.len bytes 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_testable
baseline no→ patch + rebuild →patched clean

fix.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
↓ fix.diffper-fix-DF-3736

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.