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)
PoC verification
Evidence pack
findings/poc/DF-3679 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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:
sys/dev/netif/igb/if_igb.c:2744βlen = le16toh(cur->wb.upper.length);sys/dev/netif/igb/if_igb.c:2768βmp->m_len = len;sys/dev/netif/igb/if_igb.c:2770/:2776βm_pkthdr.len = len/+= len
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.
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_testablefix.diff authored against the read-only sys/ tree; cannot be validated on a guest without igb hardware.
findings/poc/DF-3679/fix.diff
Confirmed kernel references
- sys/dev/netif/igb/if_igb.c:2744
- sys/dev/netif/igb/if_igb.c:2768
- sys/dev/netif/igb/if_igb.c:2770
- sys/dev/netif/igb/if_igb.c:2776
- sys/dev/netif/igb/if_igb.c:2431
- sys/dev/netif/igb/if_igb.c:2433
- sys/dev/netif/igb/if_igb.c:2436
- sys/dev/netif/igb/if_igb.c:2820
- sys/dev/netif/igb/if_igb.c:2537
- sys/dev/netif/igb/if_igb.c:2870
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.
No comments yet.