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

igb_rxeof trusts NIC-DMA'd descriptor length with no posted-buffer bound: unbounded m_len/m_pkthdr.len β†’ heap OOB read up to 63,487 bytes past the 2046/2048-byte RX cluster (DF-3604/3639/3663/3667 family)

Summary

igb_rxeof() copies the 16-bit write-back length out of the receive descriptor (DMA-written by the NIC) and assigns it directly to the replacement mbuf's m_len and m_pkthdr.len without ever comparing it to the buffer that was actually posted (one MCLBYTES=2048 cluster, front-trimmed by ETHER_ALIGN=2 to 2046 usable bytes whenever MTU <= 2028; the RX DMA map is loaded for exactly that post-trim span). Any device-side length beyond the posted buffer - descriptor corruption, device fault/erratum, or a malicious DMA source such as an SR-IOV VF assigned to an untrusted guest (the driver explicitly probes VF device IDs and runs sc->vf_ifp mode) - propagates into m_len/m_pkthdr.len and is handed to ether_input; the stack then parses up to ~63.5KB past the cluster. Chained jumbo frames additionally accumulate the unvalidated len into fmp->m_pkthdr.len. Same premise and Medium rating as the bce/bnx/bge/ix family findings. Fix: discard frames whose write-back length exceeds the posted buffer (dynamic bound).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3679 Β· 10 files
FileTypeDescriptionSize
README.md β€” 1.9 KB ↓ raw
VERDICT.md β€” 3.9 KB ↓ raw
trigger.c β€” 1.3 KB view raw
build.sh β€” 95 B view raw
run.sh β€” 163 B view raw
build.log β€” 53 B view raw
run.log β€” 98 B view raw
env.txt β€” 894 B view raw
fix.diff β€” 936 B view raw
verdict.json β€” 2.6 KB view raw

DF-3679 β€” igb_rxeof trusts NIC-DMA'd RX descriptor length (no buffer bound)

What

igb_rxeof() reads the receive descriptor write-back length (cur->wb.upper.length, 16-bit, DMA-written by the NIC) and assigns it directly to the freshly posted mbuf without clamping it to the posted buffer size:

The posted buffer (igb_newbuf, sys/dev/netif/igb/if_igb.c:2415-2460) is a single MCLBYTES (2048-byte) cluster, front-trimmed by ETHER_ALIGN (2) when max_frame_size <= MCLBYTES - ETHER_ALIGN (:2433-2434), i.e. an effective capacity of 2046 bytes for MTU <= 2028.

A descriptor length larger than the posted buffer (possible on descriptor corruption, device fault/erratum, or a malicious device β€” e.g. SR-IOV VF assigned to an untrusted guest writing back crafted descriptors, or Thunderbolt/PCIe attached attack hardware) makes m_len/m_pkthdr.len describe up to 65535 bytes. The network stack (ether_input, IP/TCP parsing, BPF taps) then walks past the end of the 2046/2048-byte cluster β†’ kernel heap OOB read of up to ~63.5 KB (info leak / panic). Chained (jumbo) frames additionally accumulate unvalidated len into m_pkthdr.len across the chain.

Same defect family as DF-3604 (bce), DF-3639 (bnx), DF-3663 (bge), DF-3667 (ix).

Reproduce / verify

Requires an igb(4) NIC (Intel 82575/82576/82580/I210/I211/I350/I354). The QEMU audit guest has no igb hardware (virtio vtnet0 only) β€” see run.log for the module-presence / device-absence proof. Source-level verification only; see VERDICT.md.

Expected result (on hardware)

Descriptor write-back with wb.upper.length > posted buffer size causes the stack to read past the RX cluster (leak/panic); with the fix the frame is counted as an input error and discarded.

VERDICT.md
↓ download raw

DF-3679 VERDICT β€” igb_rxeof unvalidated descriptor length

Status: inconclusive on guest (no igb hardware) β€” source-certain

Root cause (path:line)

igb_rxeof() (sys/dev/netif/igb/if_igb.c:2712-2838):

2744:  len = le16toh(cur->wb.upper.length);      /* NIC DMA write-back, untrusted */
...
2763:  if (igb_newbuf(rxr, i, FALSE) != 0) { ... }
2768:  mp->m_len = len;                          /* NO bound vs posted buffer */
2769:  if (rxr->fmp == NULL) {
2770:      mp->m_pkthdr.len = len;
...
2776:      rxr->fmp->m_pkthdr.len += len;        /* unbounded accumulation */

The posted buffer is one MCLBYTES cluster (igb_newbuf:2423-2434): m_getcl() β†’ 2048-byte cluster; m_adj(m, ETHER_ALIGN) front-trims 2 bytes whenever max_frame_size <= MCLBYTES - ETHER_ALIGN (:2433), so effective capacity is 2046 (MTU <= 2028) or 2048 (jumbo). The RX DMA map is loaded with the post-trim length (:2436-2437), i.e. exactly the usable region.

Legitimate lengths are bounded by hardware configuration only: - igb_init_rx_unit programs SRRCTL.BSIZEPKT = 2048 (:2537,:2542) and RLPML = max_frame_size + VLAN_TAG_SIZE (igb_set_vlan:2870-2871); RLPML counts the VLAN tag and FCS, both stripped before the buffer write, so the largest legit single-descriptor write is max_frame_size bytes. - NOTHING in the driver validates the write-back value.

Therefore any value in [max_frame_size+1 .. 65535] written by the device (descriptor corruption, device fault, erratum, or a malicious DMA source such as a VF assigned to an untrusted guest) propagates straight into m_len/m_pkthdr.len, and ifp->if_input(ifp, m, pi, cpuid) (:2820) hands the oversized mbuf to ether_input/IP parsing, which reads past the cluster (heap OOB read, up to 63,487 bytes past a 2048-byte cluster). INVARIANTS builds may catch some walks (m_length assertions elsewhere), production builds leak/panic non-deterministically.

This is exactly the DF-3604/DF-3639/DF-3663/DF-3667 family; ix(4) shares the identical pattern (sys/dev/netif/ix/if_ix.c:3031 mp->m_len = len;), i.e. the family defect is systemic across e1000-class drivers in this tree.

Why not reproduced on the guest

The audit guest (DragonFly 6.5-DEVELOPMENT x86_64, QEMU/KVM) has no igb device: pciconf shows no Intel 82575/82576/82580/I210/I350 class device and ifconfig -a lists only vtnet0 (virtio) + lo0. The driver is compiled into the kernel (kldload if_igb β†’ "module already loaded or in kernel"; /boot/kernel/if_igb.ko present). With no device, igb_attach() never runs, so igb_rxeof() cannot be exercised. See run.log.

Exploitability

Attacker model: the value is NIC-controlled DMA, not directly writable by an unprivileged local user or a remote peer. Realistic sources: descriptor corruption after device fault/erratum, PCIe device attacked/firmware-compromised, or SR-IOV VF passthrough to an untrusted guest (the igb driver explicitly supports VF device IDs, igb_devices[], sc->vf_ifp). Impact ceiling: kernel heap info leak via OOB-read packet data reaching user-visible paths, or panic. Medium severity, consistent with the family findings.

Fix

Clamp/discard before assigning to the mbuf (fix.diff, bound is dynamic because jumbo mode legitimately posts 2048-byte descriptors while aligned mode posts 2046):

if (len > (rxr->sc->max_frame_size <= MCLBYTES - ETHER_ALIGN ?
           MCLBYTES - ETHER_ALIGN : MCLBYTES)) {
        IFNET_STAT_INC(ifp, ierrors, 1);
        goto discard;
}

Fix not validated on guest (no hardware): 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; cannot be validated on a guest without igb hardware.

findings/poc/DF-3679/fix.diff
↓ fix.diffper-fix-DF-3679

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-3679/VERDICT.md (root-cause trace at if_igb.c:2744,2768,2770,2776; buffer math at :2423-2437); run.log (no igb device on guest); fix.diff (dynamic-bound discard)

PoC changes

No runnable PoC possible without igb hardware; trigger.c documents the on-hardware descriptor-injection procedure and the exact sink lines; run.sh performs the module-presence/device-absence probe.

Verified recommended fix

Discard frames whose write-back length exceeds the posted buffer size (MCLBYTES - ETHER_ALIGN when front-trimmed, else MCLBYTES) before assigning m_len.

Verdict

Source-certain, runtime-inconclusive: igb_rxeof() assigns the NIC-DMA'd write-back length directly to m_len/m_pkthdr.len with no bound against the posted MCLBYTES(-ETHER_ALIGN) cluster, so any device-side length > the posted buffer (descriptor corruption, device fault/erratum, malicious VF/DMA source) yields a heap OOB read of up to ~63.5KB past the RX cluster via ether_input. The audit guest has no igb hardware (only vtnet0; driver compiled into kernel and /boot/kernel/if_igb.ko present) so the path cannot be exercised at runtime; module/device presence proof in run.log. Same family as DF-3604/DF-3639/DF-3663/DF-3667.