2-byte DMA heap overflow in em(4) RX when MTU in (1500, 2028]: ETHER_ALIGN front-trim coexists with RCTL_LPE long-frame reception on 2048-byte hardware buffers
Summary
em_newbuf() front-trims every posted RX cluster by ETHER_ALIGN=2 whenever max_frame_size <= 2046 (MTU <= 2028), so the RX bus-dma map covers only 2046 bytes of the 2048-byte cluster. em_init_rx_unit() programs RCTL_SZ_2048 (2048-byte hardware receive buffers, never changed on MTU update) and sets RCTL_LPE for any MTU > 1500. For any admin-chosen MTU in (1500, 2028] - accepted on nearly all em silicon - the trim and long-frame reception coexist: the NIC splits a >2048-byte frame into 2048-byte descriptor chunks and DMA-fills the first descriptor's buffer with 2048 bytes at cluster+2, writing the last 2 ATTACKER-CONTROLLED frame bytes past the end of the 2048-byte mbuf cluster into the adjacent kernel heap object. Stack-side, em_rxeof assigns that fragment m_len=2048, claiming 2 bytes past the cluster for every consumer. Identical bug pattern to DF-1197 (emx); this is the em(4) copy. VERIFIED via a userspace bound-math replica on the guest (MTU=2000 -> write span [2,2050) into 2048-byte cluster = OVERFLOW; MTU=1500 and MTU=9000 both safe, isolating the coexistence window). No uid0 chain: 2 controlled bytes per jumbo frame into adjacent-cluster memory.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3712 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.6 KB | β raw | |
| VERDICT.md | β | 4.1 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 | β | 827 B | view raw | |
| verdict.json | β | 3.2 KB | view raw | |
| em_bounds_harness.c | β | 7.5 KB | view raw |
DF-3712 β 2-byte DMA heap overflow in em(4) RX when MTU in (1500, 2028]
File: sys/dev/netif/em/if_em.c (Intel e1000 8254x driver, 7.6.2)
Lines: 3376-3379 (em_newbuf trim), 3607-3632 (em_init_rx_unit RCTL), 684 (rx_buffer_len), 1225-1281 (em_ioctl SIOCSIFMTU)
Family: identical bug pattern to DF-1197 (emx, sys/dev/netif/emx/if_emx.c:2918)
Reproduce on a box with real em(4) hardware
# 1. root puts the interface in the dangerous MTU window (any value in
# (1500, 2028]; jumbo-capable switch path or direct attach):
ifconfig em0 mtu 2000 up
# 2. any unauthenticated peer on the same L2 segment sends one frame
# larger than 2048 bytes (e.g. a 3000-byte UDP/ICMP datagram):
ping -s 2900 -M do <host-behind-em0> # or scapy sendp(Ether()/IP()/Raw('A'*2950))
Expected on vulnerable driver: the NIC (RCTL SZ_2048 + LPE) splits the
frame across receive descriptors; the first descriptor's buffer is filled
with 2048 bytes at cluster+2 (ETHER_ALIGN front-trim) β the final 2 bytes
of the fill land 2 bytes past the 2048-byte mbuf cluster (attacker-
controlled frame bytes), corrupting the adjacent kernel heap object.
On this audit guest
No em hardware exists (virtio only). run.sh performs the module/device
presence probe; run.harness.log is a userspace bound-math replica of the
exact posting/fill arithmetic (compiled and executed on the guest,
build.sh), which demonstrates the 2-byte overrun with a poisoned redzone.
Build (guest)
sh build.sh # cc -O -o em_bounds_harness em_bounds_harness.c ./run.sh # runs harness + device-absence probe
DF-3712 β VERDICT
Status: source-certain, runtime hardware-gated (inconclusive on this guest β no em NIC).
Root cause
em_newbuf() posts one MCLBYTES (2048-byte) cluster per RX descriptor:
sys/dev/netif/em/if_em.c:3376βm->m_len = m->m_pkthdr.len = MCLBYTES;sys/dev/netif/em/if_em.c:3378-3379βif (adapter->hw.mac.max_frame_size <= MCLBYTES - ETHER_ALIGN) m_adj(m, ETHER_ALIGN);β formax_frame_size <= 2046the mbuf is front-trimmed by 2 bytes and the bus-dma map at:3381-3383therefore covers only 2046 bytes ([cluster+2, cluster+2048)).sys/dev/netif/em/if_em.c:684βadapter->rx_buffer_len = MCLBYTES;β set once at attach, never updated on MTU change.sys/dev/netif/em/if_em.c:3607-3611βem_init_rx_unit()programsRCTL_SZ_2048fromrx_buffer_len: the hardware receive-buffer size is 2048 bytes per descriptor.sys/dev/netif/em/if_em.c:3629-3630βif (ifp->if_mtu > ETHERMTU) rctl |= E1000_RCTL_LPE;β long-packet reception (frames > 1522 bytes) is enabled for any MTU > 1500.sys/dev/netif/em/if_em.c:1269-1277βSIOCSIFMTUaccepts any MTU up to the per-chip maximum (9234 for 82571/72/73/74/83/ES2/ICH9+/PCH; 4096 for PCH1; 16110 (MAX_JUMBO_FRAME_SIZE=0x3F00, e1000_defines.h:480) for the 8254x default case*).
The dangerous window is MTU β (1500, 2028]: max_frame_size = mtu+18 β€ 2046
keeps the ETHER_ALIGN trim active while LPE admits frames larger than one
descriptor. A frame > 2048 bytes is split by the NIC into 2048-byte chunks
(this multi-descriptor chaining is exactly what em_rxeof's fmp/lmp logic
at :3740-3762 reassembles); the first descriptor's buffer is DMA-filled with
2048 bytes at cluster+2 β bytes at cluster+2048 and cluster+2049
are written 2 bytes past the end of the 2048-byte cluster. The overflow
bytes are bytes 2047-2048 of the frame β fully attacker-controlled Ethernet
payload.
Secondary stack-side effect in the same configuration: em_rxeof:3705/:3738
sets the first fragment's m_len = 2048 (non-EOP len = desc_len), so the
mbuf claims data through cluster+2050 β every consumer that walks the chain
reads 2 bytes past the cluster as well.
Threat
Precondition: an administrator sets an MTU in (1500, 2028] β a plausible, intentional configuration on jumbo-enabled LANs (e.g. MTU 2000 overlays). After that, any unauthenticated host on the same L2 segment triggers the overflow with a single >2048-byte frame. Impact: 2-byte controlled heap corruption of the object following the RX cluster in the mbuf-cluster slab (another RX cluster's first bytes, or slab trailer) β memory-corruption class; adjacent-cluster corruption manifests as corrupted packet bytes and, depending on slab layout, potential accounting corruption.
Verification
- Guest has no em hardware (
ifconfig -lβvtnet0 lo0; pciconf shows no 8086 ethernet NIC;if_em.koexists in /boot/kernel but is not in the kernel and not attached) β the on-hardware trigger cannot run. - Bound-math harness (guest-executed,
run.harness.log): replicates the posting arithmetic and the RCTL fill arithmetic exactly; for MTU=2000, frame=3000 βmapped=2046, fill=2048, write span [2,2050)β[OVERFLOW] 2-byte attacker-controlled DMA write past 2048-byte mbuf cluster. MTU=1500 (LPE off) and MTU=9000 (trim off) both verified safe β isolating the bug to the coexistence window, exactly as derived. - Same-tree sibling with the identical code shape was filed as DF-1197 (emx). This is the em(4) copy.
Fix
fix.diff: apply the ETHER_ALIGN front-trim only while standard-size frames
are admitted (max_frame_size <= ETHER_MAX_LEN), so trim and LPE can never
coexist. 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-3712/fix.diff
Confirmed kernel references
- sys/dev/netif/em/if_em.c:3376
- sys/dev/netif/em/if_em.c:3378
- sys/dev/netif/em/if_em.c:3379
- sys/dev/netif/em/if_em.c:3381
- sys/dev/netif/em/if_em.c:684
- sys/dev/netif/em/if_em.c:3607
- sys/dev/netif/em/if_em.c:3609
- sys/dev/netif/em/if_em.c:3629
- sys/dev/netif/em/if_em.c:3630
- sys/dev/netif/em/if_em.c:1269
- sys/dev/netif/em/if_em.c:1276
- sys/dev/netif/em/if_em.h:218
Detail
Evidence (decisive lines)
findings/poc/DF-3712/VERDICT.md (root-cause trace); run.log (guest probe: no em device + harness section [A]: MTU=2000 -> 2-byte DMA overflow, negative configs safe); build.log; fix.diff (trim only when max_frame_size <= ETHER_MAX_LEN)
PoC changes
No seed PoC existed. Wrote em_bounds_harness.c (guest-executed userspace bound-math replica of em_newbuf posting + RCTL fill arithmetic with redzone checks); trigger.sh-style on-hardware procedure documented in README.md.
Verified recommended fix
Apply the ETHER_ALIGN front-trim only when max_frame_size <= ETHER_MAX_LEN so trim and RCTL_LPE never coexist.
Verdict
Source-certain, runtime hardware-gated: em_newbuf (sys/dev/netif/em/if_em.c:3376-3383) front-trims the posted MCLBYTES=2048 RX cluster by ETHER_ALIGN=2 whenever hw.mac.max_frame_size <= 2046, so the RX DMA map covers only 2046 bytes, while em_init_rx_unit (:3607-3632) programs RCTL_SZ_2048 (2048-byte hardware receive buffers; rx_buffer_len fixed at MCLBYTES since :684) and enables RCTL_LPE for any if_mtu > 1500 (:3629). For any MTU in (1500,2028] (SIOCSIFMTU :1225-1281 accepts up to 16110 on 8254x) trim and LPE coexist: the first descriptor of a multi-descriptor frame is DMA-filled with 2048 bytes at cluster+2, writing 2 attacker-controlled bytes past the 2048-byte mbuf cluster (heap corruption), and em_rxeof:3738 then sets m_len=2048 claiming 2 bytes past the cluster on the stack side. Identical pattern to DF-1197 (emx). Guest-executed bound-math harness proves the posting/fill arithmetic (MTU=2000: write span [2,2050) into a 2048-byte cluster; MTU=1500 and MTU=9000 both safe). Audit guest has no em hardware (vtnet only), so the on-hardware trigger cannot run.
No comments yet.