em_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 2048-byte RX cluster (positive direction of DF-1188)
Summary
em_rxeof() reads the 16-bit length field from the device-DMA'd legacy RX descriptor and publishes it without any bound against the posted buffer (rx_buffer_len = MCLBYTES = 2048): non-EOP fragments get len = desc_len, EOP fragments len = desc_len - ETHER_CRC_LEN, assigned straight to m_len and accumulated into m_pkthdr.len. A device-side write-back of 0xFFFF yields an mbuf claiming 65535 bytes inside a 2048-byte cluster - every downstream consumer (ether parsing, m_pullup, BPF taps, forwarding, m_copydata) reads up to 63487 bytes of adjacent kernel heap per descriptor, compounded by the pkthdr accumulator. The error-path probe last_byte = *(mtod+len-1) reads up to cluster+65534 (positive direction; the ==0 direction is the known DF-1188). Additionally prev_len_adj can subtract up to 4 from a previous fragment whose m_len < 4, injecting a NEGATIVE m_len into the chain. Same premise as the accepted family DF-3604/3639/3663/3667/3679. VERIFIED: guest-executed bound-math harness demonstrates all three variants.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3713 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.6 KB | β raw | |
| VERDICT.md | β | 3.0 KB | β raw | |
| build.sh | β | 113 B | view raw | |
| run.sh | β | 153 B | view raw | |
| build.log | β | 11 B | view raw | |
| run.log | β | 2.5 KB | view raw | |
| env.txt | β | 634 B | view raw | |
| fix.diff | β | 847 B | view raw | |
| verdict.json | β | 2.8 KB | view raw | |
| em_bounds_harness.c | β | 7.5 KB | view raw |
DF-3713 β em_rxeof trusts the 16-bit NIC descriptor length with no posted-buffer bound
File: sys/dev/netif/em/if_em.c
Lines: 3692 (desc_len = le16toh(current_desc->length)), 3694-3706 (len math), 3715 (error-path probe), 3738 (mp->m_len = len), 3761 (fmp->m_pkthdr.len += len)
Family: DF-3604 (bce), DF-3639 (bnx), DF-3663 (bge), DF-3667 (ix), DF-3679 (igb) β this is the em(4) member of the unbounded-RX-length family. The known em finding DF-1188 covers only desc_len == 0 on an error descriptor (negative-direction probe); this finding is the positive direction (desc_len > posted buffer).
Reproduce on a box with real em(4) hardware
Device-side trigger (requires a faulty/malicious NIC DMA source β descriptor corruption, erratum, SR-IOV/emulation fault):
# Post RX buffers (interface up), then DMA-write a legacy RX descriptor # write-back with length = 0xFFFF and DD|!EOP set: # desc->length = 0xFFFF; desc->status = E1000_RXD_STAT_DD; # -> em_rxeof computes len = 65535, assigns mp->m_len = 65535 on a # 2048-byte cluster; ether_input/BPF/forwarding walk ~63.5KB of # adjacent kernel heap.
On this audit guest
No em hardware. run.sh performs the presence probe; run.harness.log
(guest-executed userspace replica of the exact arithmetic, build.sh)
demonstrates: desc_len=0xFFFF (non-EOP) β m_len claims 63487 bytes past the
cluster; desc_len=0xFFFF (EOP) β 63483 bytes past; error-path probe at
:3715 reads at mp->m_data + 65534; prev_len_adj underflow (previous
fragment m_len 0, EOP desc_len 2) β lmp->m_len = -2 (negative m_len
injected into the RX chain).
sh build.sh && ./run.sh
DF-3713 β VERDICT
Status: source-certain, runtime hardware-gated (inconclusive on this guest β no em NIC).
Root cause
em_rxeof() (sys/dev/netif/em/if_em.c:3661-3821) reads the 16-bit length
field out of the legacy RX descriptor β a value DMA-written by the device
(struct e1000_rx_desc, e1000_hw.h:410-417, __le16 length):
:3692desc_len = le16toh(current_desc->length);β no bound against the posted buffer (adapter->rx_buffer_len, = MCLBYTES = 2048, set at:684, never changed).:3705non-EOP fragment:len = desc_len;β a legitimate fragment is at most 2048; nothing rejects larger values.:3701EOP fragment:len = desc_len - ETHER_CRC_LEN.:3738mp->m_len = len;and:3761adapter->fmp->m_pkthdr.len += len;β the unbounded value becomes the mbuf's advertised data length and the packet's total length, handed toifp->if_inputat:3808.:3715error path:last_byte = *(mtod(mp, caddr_t) + desc_len - 1);β OOB probe at up tocluster + 65534(thedesc_len == 0negative direction of this line is the already-filed DF-1188).:3697-3699+:3754-3758: if a previous non-EOP fragment hadm_len < 4(e.g. desc_len 0..3),prev_len_adj = ETHER_CRC_LEN - desc_lensubtracts up to 4 from it β negativem_lenin the chain (lmp->m_len -= prev_len_adjwith no floor).
A device-side length of 0xFFFF yields an mbuf claiming 65535/65531 bytes
inside a 2048-byte cluster: every downstream consumer (ether parsing,
m_pullup, BPF taps, forwarding, m_copydata) reads up to ~63.5KB past the
cluster β per descriptor, and compounded by the m_pkthdr.len accumulator
for multi-descriptor frames.
Threat
Device-trust boundary: descriptor corruption, device fault/erratum, or a malicious DMA source (PCIe device spoofing an e1000, flawed emulated e1000 β the default QEMU NIC model, or SR-IOV style injected writes). Not reachable by a network peer against well-behaved hardware. Same premise and severity rationale as the accepted family findings DF-3604/DF-3639/DF-3663/DF-3667 (bce/bnx/bge/ix) and DF-3679 (igb).
Verification
- Guest has no em hardware (
run.shprobe:vtnet0 lo0only). - Guest-executed bound-math harness (
run.harness.log): desc_len=0xFFFF non-EOP β 63487-byte OOB claim; EOP β 63483-byte; error-path probe OOB; prev_len_adj βlmp->m_len = -2demonstrated. Honest frames (1500) safe.
Fix
fix.diff: after reading desc_len, discard (whole frame chain) when
desc_len == 0 (also closes DF-1188) or desc_len > adapter->rx_buffer_len.
Cannot be hardware-validated on this guest (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 authored against the read-only sys/ tree (patch --dry-run clean); cannot be validated on a guest without em hardware.
findings/poc/DF-3713/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
findings/poc/DF-3713/VERDICT.md; run.log (probe + harness section [B]/[B2]: 63487/63483-byte OOB claims, probe OOB, negative m_len -2); build.log; fix.diff (discard desc_len==0 or > rx_buffer_len, also closing DF-1188)
PoC changes
No seed PoC. em_bounds_harness.c section [B]/[B2] written for this finding; device-side descriptor-injection procedure documented in README.md.
Verified recommended fix
Discard the frame (whole fmp chain) when desc_len == 0 or desc_len > adapter->rx_buffer_len before assigning m_len.
Verdict
Source-certain, runtime hardware-gated: em_rxeof (sys/dev/netif/em/if_em.c:3661-3821) publishes the 16-bit NIC-DMA'd descriptor length with no bound against the posted buffer: desc_len at :3692, len=desc_len (:3705) or desc_len-4 (:3701) -> mp->m_len=len at :3738 and fmp->m_pkthdr.len += len at :3761. A malicious/faulty device write-back of 0xFFFF yields an mbuf claiming 65535/65531 bytes in a 2048-byte cluster -> heap OOB read of up to 63487/63483 bytes via ether_input/BPF/forwarding, compounded per descriptor by the pkthdr accumulator; the error-path probe at :3715 reads cluster+desc_len-1 (positive direction of DF-1188); prev_len_adj (:3697-3699, :3754-3758) can drive a previous fragment's m_len negative. Same family/rationale as DF-3604/DF-3639/DF-3663/DF-3667/DF-3679; DF-1188 covers only the desc_len==0 direction. Guest-executed bound-math harness demonstrates all three spans; no em hardware on the audit guest.
No comments yet.