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

Unvalidated firmware-returned SRAM offsets used as kernel write pointers (lanai TX/RX rings, intr_coal_delay_ptr, irq_claim/irq_deassert, RSS table and key): OOB MMIO writes past the BAR mapping on a hostile device; int16_t ring-mask truncation on device ring sizes

Summary

Every GET_*_OFFSET response (cmd.data0, device-written) becomes a raw pointer into the mapped SRAM and is then written through, unchecked: intr_coal_delay_ptr (live via root sysctl), irq_deassert/irq_claim, TX/RX lanai descriptor rings PIO-written by submit paths, 128-byte RSS table loop and 16-byte RSS key. Offsets >= sram_size produce kernel MMIO writes past the pmap_mapdev window with partially host-chosen data. Related: rx ring mask/cl_size are int16_t fed by device ring sizes - entries >= 32768 wrap the mask negative. The file already validates the adopt-path offset; these loci lack the same check. Hostile/compromised PCIe device (worst sink: RSS table - 128 host-derived bytes at a device-chosen OOB offset). Helper+checks fix git-apply-clean.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3720 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.7 KB ↓ raw
VERDICT.md β€” 1.4 KB ↓ raw
fix.diff β€” 3.3 KB view raw
verdict.json β€” 2.5 KB view raw
manifest.json β€” 863 B view raw
env.txt β€” 583 B view raw

DF-3720 β€” Firmware-returned SRAM offsets used as kernel write pointers with no validation (hostile-device family); plus int16_t ring-mask truncation

Vulnerability

sys/dev/netif/mxge/if_mxge.c repeatedly turns a device-returned offset (mxge_send_cmd response cmd.data0) into a raw pointer into the mapped SRAM BAR (sc->sram, size sc->sram_size :4290) and then writes through it, with no range check anywhere:

Locus Command Sink
mxge_reset :1207-1215 GET_INTR_COAL_DELAY_OFFSET / GET_IRQ_ACK_OFFSET / GET_IRQ_DEASSERT_OFFSET *sc->intr_coal_delay_ptr = htobe32(...) write :1222 and live sysctl write mxge_change_intr_coal :1339 (*sc->intr_coal_delay_ptr = ...); irq_claim stores :2717-2718/:2779-2780/:2853-2854
mxge_slice_open :3255-3283 GET_SEND_OFFSET / GET_SMALL_RX_OFFSET / GET_BIG_RX_OFFSET tx->lanai / rx_*->lanai β€” descriptor rings written by mxge_submit_req/mxge_submit_8rx PIO (e.g. :1580, :2030-2036)
mxge_open :3351-3364 GET_RSS_TABLE_OFFSET 128-byte table write loop :3363-3364
mxge_open :3373-3384 GET_RSS_KEY_OFFSET 16-byte key write loop :3382-3383

A hostile/compromised NIC (or one returning garbage after a fault) supplies an offset β‰₯ sram_size and the kernel performs MMIO writes past the pmap_mapdev window β€” kernel-VA memory corruption with partially attacker-chosen data (RSS table bytes are host-derived; intr_coal value is a root-settable int; descriptors contain DMA addresses).

Related same-family sloppiness in the same flow: mxge_rx_ring_t.mask and cl_size are int16_t (if_mxge_var.h:65-66) while the entry counts derive from the NIC-returned GET_RX_RING_SIZE/GET_SEND_RING_SIZE (mxge_alloc_slices :3960-3961, mxge_alloc_rings :3194-3196): a ring size β‰₯ 512 KiB (entries β‰₯ 32768) wraps the mask negative (:3007-3009) and idx = rx->cnt & rx->mask sign-extends garbage.

Contrast: mxge_adopt_running_firmware does bounds-check its device-supplied header offset against sc->sram_size (:867) β€” the check pattern exists in the file; the loci above simply never got it.

Threat model

Hostile/faulting PCIe device (same premise as DF-3716/3718 and the igb/bge/em device-trust findings). Not reachable by network input alone.

Verification

Hardware-gated; source-certain; inconclusive per family convention.

Fix

fix.diff adds mxge_sram_range_valid() and applies it at all four loci (intr-coal/ack/deassert offsets, both send-offset branches, both rx-offset commands, RSS table and key), failing open with ENXIO/EIO. git-apply clean. (int16_t mask widening is a larger mechanical change; flagged in the diff comment in the finding text only.)

VERDICT.md
↓ download raw

DF-3720 VERDICT

Status: inconclusive (hardware-gated), confidence: certain source-level.

Source proof

  • Every locus in the README table: cmd.data0 (device response) -> pointer into sc->sram -> unconditional write. No sram_size comparison anywhere in mxge_reset (:1207-1215), mxge_slice_open (:3255-3283), or the RSS table/key writes (:3362-3364, :3379-3384).
  • The check pattern demonstrably exists in-file: mxge_adopt_running_firmware validates its device offset against sc->sram_size at :867.
  • int16_t mask/cl_size (if_mxge_var.h:65-66) fed by device ring sizes (:3960, :3194-3196) -> entries >= 32768 wraps negative (:3007-3009).
  • Sinks are reachable on every interface up/reset (attach does reset+open), and intr_coal_delay_ptr is writable live by root via sysctl (:1339).

Not reproduced

No Myricom device on the guest (env.txt); offsets come from the device.

Fix

fix.diff: mxge_sram_range_valid() applied at all four loci (git apply --check clean). fix_status=not_testable (no hardware to build against).

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 git apply --check clean; not buildable without mxge hardware.

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

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-3720/VERDICT.md; fix.diff (mxge_sram_range_valid() at all four loci, git-apply clean); env.txt

PoC changes

No seed PoC; hardware-gated.

Verified recommended fix

Add mxge_sram_range_valid(sc, off, len) and validate every firmware-returned offset before first use; widen mask/cl_size to int.

Verdict

Source-certain, hardware-gated (hostile-device premise, DF-3716/3718 family): if_mxge.c converts firmware-returned offsets (mxge_send_cmd cmd.data0) into raw pointers into the mapped SRAM BAR and writes through them with no range check - intr_coal_delay_ptr/irq_deassert/irq_claim (mxge_reset :1207-1215, written :1222 and via sysctl :1339, claimed :2717-2718), the TX/RX lanai descriptor rings (mxge_slice_open :3255-3283, PIO-written by mxge_submit_req/mxge_submit_8rx), the 128-byte RSS indirection table (:3362-3364) and the 16-byte RSS key (:3379-3384). A device returning an offset >= sram_size gets kernel MMIO writes past the pmap_mapdev window. Related: mxge_rx_ring_t.mask/cl_size are int16_t (if_mxge_var.h:65-66) filled from device ring sizes (:3960/:3194-3196), so entries >= 32768 wraps the mask negative (:3007-3009). The file already validates the adopt-path offset at :867 - the pattern exists, these loci lack it. Guest has no Myricom device; not runtime-testable.