mxge_rx_done_big trusts the NIC-DMA'd 16-bit completion length with no posted-cluster bound: heap OOB read up to ~63.5KB past the 2/4KiB RX cluster (DF-3604/3639/3663/3679/3713 family)
Summary
mxge_clean_rx_done bounds only the small-buffer dispatch (length <= MXGE_RX_SMALL_BUFLEN); every other NIC-DMA'd 16-bit length (up to 65535) flows into mxge_rx_done_big which assigns it verbatim to m->m_len/m_pkthdr.len on an mbuf backed by exactly rx->cl_size bytes (2048 or 4096). The stack (rx_csum, vlan_tag_remove, if_input consumers) then reads up to ~63.5KB past the cluster - heap OOB read / kernel info leak to receiving sockets, identical shape to the bce/bnx/bge/igb/em family. Hostile/compromised Myri10G NIC or device-side length fault. One-line clamp fix git-apply-clean.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3716 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.0 KB | β raw | |
| VERDICT.md | β | 2.7 KB | β raw | |
| fix.diff | β | 456 B | view raw | |
| verdict.json | β | 2.8 KB | view raw | |
| manifest.json | β | 927 B | view raw | |
| env.txt | β | 583 B | view raw |
DF-3716 β mxge_rx_done_big trusts the NIC-DMA'd 16-bit completion length with no posted-cluster bound
Vulnerability
sys/dev/netif/mxge/if_mxge.c RX completion path:
mxge_clean_rx_done()(:2337-2362) drains the NIC-DMA'drx_done->entry[]slots. Eachmcp_slot_t(mxge_mcp.h:56-60) carries a 16-bit big-endianlengthwritten by the NIC via DMA (:2345).- Dispatch (:2350-2356):
length <= MXGE_RX_SMALL_BUFLEN(= MHLEN-2, :91) goes tomxge_rx_done_small()(bounded by construction); everything else β up to 65535 β goes tomxge_rx_done_big()with no bound at all against the posted big-cluster size. mxge_rx_done_big()(:2223-2278) setsm->m_len = m->m_pkthdr.len = len(:2261) on an mbuf whose cluster isrx->cl_sizebytes βMCLBYTES(2048) for MTU β€ 2027,MJUMPAGESIZE(4096) for MTU up to the driver max 4075 (mxge_choose_params:3228-3239,max_mtu:4429-4430) β and hands it toifp->if_input(:2277).- Downstream (
mxge_rx_csum:2150-2176 reads the IP header at eh+1,mxge_vlan_tag_remove:2178-2220 reads 4 bytes at offset 14, then the protocol stack copiesm_lenbytes toward sockets), the kernel reads up to65535 - (cl_size - MXGEFW_PAD)bytes past the end of the 2/4 KiB cluster β a heap OOB read / kernel-info-leak primitive of the same shape and size class as DF-3604 (bce), DF-3639 (bnx), DF-3663 (bge), DF-3679 (igb) and DF-3713 (em).
Contrast: the small-buffer path is bounded (the <= MXGE_RX_SMALL_BUFLEN
test at :2350), which makes the missing big-path bound an oversight, not a
design choice. Also note mxge_get_buf_big (:2089-2140) posts exactly
cl_size bytes (m_getcl/m_getjcl), so the posted-buffer size is
driver-known and the fix is a one-line comparison.
Threat model
Attacker-influenced input = NIC-DMA'd completion slot (malicious/compromised PCIe device, or a device firmware bug / SRAM corruption producing a bogus length). Same trust boundary the family findings were filed under; NOT triggerable by network packets alone on a well-behaved NIC (the firmware clips to the negotiated MTU and posted buffer size).
PoC
Hardware-gated: the audit guest has no Myricom NIC (no 0x14c1:0x0008/0009
PCI device; if_mxge.ko present but unloaded). Per family convention
(DF-3604/3639/3663/3679/3713 all inconclusive), this is source-certain
but not runtime-testable here. fix.diff clamps the length against the
posted cluster (len > rx->cl_size - MXGEFW_PAD β drop, ierrors++).
Fix
--- a/sys/dev/netif/mxge/if_mxge.c
+++ b/sys/dev/netif/mxge/if_mxge.c
@@ mxge_rx_done_big()
idx = rx->cnt & rx->mask;
rx->cnt++;
+ /*
+ * The length is NIC-supplied DMA data; never let it describe
+ * more bytes than the posted cluster actually holds.
+ */
+ if (__predict_false(len > (uint32_t)rx->cl_size - MXGEFW_PAD)) {
+ IFNET_STAT_INC(ifp, ierrors, 1);
+ return;
+ }
+
/* Save a pointer to the received mbuf */
m = rx->info[idx].m;
(full git-apply-clean diff in fix.diff)
DF-3716 VERDICT
Status: inconclusive (hardware-gated, per family convention β identical classification to DF-3604/3639/3663/3679/3713), confidence: certain at the source level.
What is certain (source)
- The 16-bit
lengthconsumed at sys/dev/netif/mxge/if_mxge.c:2345 is NIC-DMA'd data (mcp_slot_t, mxge_mcp.h:56-60) β no host-side bounds are applied anywhere between the slot andm->m_len. - The dispatch at :2350-2356 bounds ONLY the small path
(
length <= MXGE_RX_SMALL_BUFLEN, :91 = MHLEN-2). Any length in (MXGE_RX_SMALL_BUFLEN, 65535] reachesmxge_rx_done_big. mxge_rx_done_bigsetsm->m_len = m->m_pkthdr.len = len(:2261) on an mbuf backed by exactlyrx->cl_sizebytes (2048 for MTU β€ 2027 via m_getcl; 4096 for MTU β€ 4075 via m_getjcl; posted by mxge_get_buf_big :2101-2116; cl_size chosen by mxge_choose_params :3228-3239 with max_mtu :4429-4430) β the maximum OOB read is 65535 β (cl_size β MXGEFW_PAD) β 63.5 KB past a 2 KiB cluster.- The over-long mbuf is delivered to the stack (:2277 if_input); header
consumers (mxge_rx_csum :2150-2176, mxge_vlan_tag_remove :2178-2220)
and the protocol stack read/copy from
m_data..m_data+m_len, turning the over-description into heap-content disclosure and (with m_pullup/m_copydata on short clusters) potential panics.
Why not reproduced on the guest
pciconf -lv shows no Myricom vendor 0x14c1 device; if_mxge.ko exists in
/boot/kernel but never attaches. The bug needs the device to DMA a
completion length exceeding the posted buffer β not constructible by
network input on a compliant NIC, and not constructible at all without the
hardware. No software-only trigger path exists (the rx_done ring is
write-only for the NIC).
Impact ceiling
Same as the family: kernel heap OOB read β disclosure of adjacent heap contents (up to ~63.5 KB per forged slot) to a receiving socket; panic via later mbuf invariants is plausible (m_pullup on a phantom length). Gated on a hostile/compromised device or device-side fault, hence Medium.
Fix validation
fix.diff (one guarded early-return before the refill, mirroring the
existing recycle-on-failure path so the slot stays consistent) β
git apply --check clean against sys/; not buildable on the guest without
hardware (fix_status=not_testable per family convention).
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, git apply --check clean; cannot be build/run without mxge hardware.
findings/poc/DF-3716/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
findings/poc/DF-3716/VERDICT.md (four-step source proof); fix.diff (git-apply clean bound check before the refill, mirroring the existing recycle-on-failure path); env.txt (no-hardware presence probe)
PoC changes
No seed PoC; hardware-gated finding - evidence pack documents the source-level proof and the one-line bound fix per family convention.
Verified recommended fix
In mxge_rx_done_big, drop (ierrors++) when len > (uint32_t)rx->cl_size - MXGEFW_PAD, before the refill.
Verdict
Source-certain, hardware-gated (family convention DF-3604/3639/3663/3679/3713): mxge_clean_rx_done (if_mxge.c:2345-2356) bounds only the small-buffer dispatch path (length <= MXGE_RX_SMALL_BUFLEN, :91); every other NIC-DMA'd 16-bit length - up to 65535 - flows into mxge_rx_done_big which assigns it straight to m->m_len/m_pkthdr.len (:2261) on an mbuf backed by exactly rx->cl_size bytes (2048 via m_getcl for MTU<=2027, 4096 via m_getjcl up to max_mtu 4075; posted by mxge_get_buf_big :2101-2116, sized by mxge_choose_params :3228-3239). The stack then reads up to ~63.5KB past the 2/4KiB cluster (mxge_rx_csum :2150-2176 and mxge_vlan_tag_remove :2178-2220 parse at fixed offsets; if_input consumers copy m_len bytes) - heap OOB read / kernel info leak, DF-3604 family shape. Guest has no Myricom NIC (no 0x14c1 device; if_mxge.ko unloaded) so no runtime reproduction is possible; the rx_done ring is device-write-only, so there is no software-only trigger.
No comments yet.