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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3716 Β· 6 files
FileTypeDescriptionSize
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'd rx_done->entry[] slots. Each mcp_slot_t (mxge_mcp.h:56-60) carries a 16-bit big-endian length written by the NIC via DMA (:2345).
  • Dispatch (:2350-2356): length <= MXGE_RX_SMALL_BUFLEN (= MHLEN-2, :91) goes to mxge_rx_done_small() (bounded by construction); everything else β€” up to 65535 β€” goes to mxge_rx_done_big() with no bound at all against the posted big-cluster size.
  • mxge_rx_done_big() (:2223-2278) sets m->m_len = m->m_pkthdr.len = len (:2261) on an mbuf whose cluster is rx->cl_size bytes β€” 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 to ifp->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 copies m_len bytes toward sockets), the kernel reads up to 65535 - (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)

VERDICT.md
↓ download raw

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)

  1. The 16-bit length consumed 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 and m->m_len.
  2. 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] reaches mxge_rx_done_big.
  3. mxge_rx_done_big sets m->m_len = m->m_pkthdr.len = len (:2261) on an mbuf backed by exactly rx->cl_size bytes (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.
  4. 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_testable
baseline no→ patch + rebuild →patched clean

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

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.