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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3712 Β· 10 files
FileTypeDescriptionSize
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
VERDICT.md
↓ download raw

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); β†’ for max_frame_size <= 2046 the mbuf is front-trimmed by 2 bytes and the bus-dma map at :3381-3383 therefore 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() programs RCTL_SZ_2048 from rx_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 β€” SIOCSIFMTU accepts 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.ko exists 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_testable
baseline no→ patch + rebuild →patched clean

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

Confirmed kernel references

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.