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

mr_update_span_set writes firmware-indexed strip_offset[count] OOB - linear kernel-heap overflow past sc->log_to_span[] (last softc member)

Summary

The inner loop uses the firmware u8 spanDepth (0..255) as the bound for writes into u_int8_t strip_offset[8]. With spanDepth>8 each element's writes run up to ~230 bytes past its 88-byte LD_SPAN_SET, cascading through ldSpanInfo and, for ld=63, past the end of sc->log_to_span[64] - the LAST member of the softc - into adjacent kernel heap. Written bytes are the running sum of firmware-chosen spanRowDataSize values (fw-authored content). VERIFIED on the guest with a probe KLD embedding the guest's UNMODIFIED mrsas_fp.c calling the real MR_ValidateMapInfo with a fw-shaped map: 40/4096 guard bytes clobbered past the softc tail with exactly the accumulated widths - deterministic, fw-controlled. Same precondition as DF-1528's reads but a WRITE primitive. Per-LD-geometry validation fix (validated: malicious map rejected, benign accepted).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4330 Β· 16 files
FileTypeDescriptionSize
fp_probe.c β€” 7.4 KB view raw
rename.h β€” 1.1 KB view raw
Makefile β€” 518 B ↓ download
build.sh β€” 158 B view raw
run.sh β€” 165 B view raw
fix.diff β€” 2.1 KB view raw
build.log β€” 13.8 KB view raw
run.log β€” 5.9 KB view raw
fix_build.log β€” 13.8 KB view raw
fix_run.log β€” 1.3 KB view raw
serial_decisive.log β€” 2.6 KB view raw
VERDICT.md β€” 5.4 KB ↓ raw
README.md β€” 3.2 KB ↓ raw
verdict.json β€” 4.4 KB view raw
manifest.json β€” 1.1 KB view raw
env.txt β€” 363 B view raw

DF-4330 β€” mrsas mr_update_span_set OOB kernel write via firmware spanDepth

File: sys/dev/raid/mrsas/mrsas_fp.c:949-959 (write at :955), reached from MR_ValidateMapInfo (:247-274) at adapter attach (mrsas.c:1762) and on every AEN RAID-map sync (mrsas.c:2961).

What the PoC does

fp_probe.ko embeds the unmodified guest kernel source /usr/src/sys/dev/raid/mrsas/mrsas_fp.c (globals renamed via rename.h so the KLD does not collide with the built-in driver) and calls the real MR_ValidateMapInfo() β€” the exact entry the driver calls at attach and on map sync β€” on a firmware-shaped MR_FW_RAID_MAP_ALL with:

  • ldTgtIdToLd[63] = 63 (expose LD 63),
  • ldSpanMap[63].ldRaid.spanDepth = 64 (> MAX_RAIDMAP_SPAN_DEPTH = 8),
  • totalSize forged consistent with ldCount = 1 (passes the driver's only check), and
  • a real struct mrsas_softc whose tail (log_to_span[] is the last member, mrsas.h:2424) is followed by a 4096-byte 0xAA guard.

mr_update_span_set's inner loop for (count = 0; count < raid->spanDepth; count++) span_set->strip_offset[count] = span_row_width; writes strip_offset[MAX_SPAN_DEPTH = 8] far out of bounds β€” across the following LD_SPAN_SETs, across ldSpanInfo[ld+1..], and for LD 63 past the end of sc->log_to_span[64], i.e. past the end of the mrsas_softc allocation into adjacent kernel heap. The written bytes are the running sum of firmware-chosen spanRowDataSize values.

Build (guest, root)

sh build.sh            # builds /root/fp4330/fp_probe.ko (unmodified mrsas_fp.c)
                       # and    /root/fp4330_fixed/fp_probe.ko (fix.diff applied)

Run (guest, root)

kldload /root/fp4330/fp_probe.ko
sysctl hw.fp_probe_run=0        # sanity: benign map -> accepted, guard intact
sysctl hw.fp_probe_run=1        # DF-4330: spanDepth=64 -> OOB write
kldunload fp_probe

Expected output (see run.log)

fp_probe: MR_ValidateMapInfo returned 0 (0 == map accepted, spansets built)
fp_probe: *** DF-4330 CONFIRMED: OOB WRITE past end of sc->log_to_span[64] ...
fp_probe: guard integrity: 40/4096 bytes clobbered past softc end

40 bytes past the softc tail, values exactly the accumulated widths (0x00..0x27 at +0..+39 with per-span spanRowDataSize = 1) β€” deterministic, firmware-controlled content.

Fix validation (see fix_run.log, fix.diff)

kldload /root/fp4330_fixed/fp_probe.ko     # mrsas_fp.c patched with fix.diff
sysctl hw.fp_probe_run=0        # benign map still ACCEPTED (no regression)
sysctl hw.fp_probe_run=1        # bad map REJECTED: "invalid RAID map geometry",
                                # returned 1, guard 0/4096 - OOB write GONE

The fix adds per-LD geometry validation (spanDepth, noElements, quad diff, spanRowSize) to MR_ValidateMapInfo before any spanset is built.

Threat model

The RAID map is DMA'd verbatim from the adapter (mrsas.c:3135-3174, MR_DCMD_LD_MAP_GET_INFO) and nothing bounds spanDepth before the write loop. Trigger requires a malicious/buggy MegaRAID SAS firmware or PCIe device (same precondition as the known read-side finding DF-1528, but this is a write primitive into kernel heap with attacker(fw)-chosen bytes).

VERDICT.md
↓ download raw

DF-4330 β€” VERDICT

Status: reproduced (certain). Impact demonstrated: deterministic OOB kernel write past the softc allocation with firmware-controlled byte values.

Root cause (code trace)

  1. The RAID map is DMA'd from the adapter into sc->raidmap_mem[] (mrsas.c:3135-3174, MR_DCMD_LD_MAP_GET_INFO), map_sz = sizeof(MR_FW_RAID_MAP) + sizeof(MR_LD_SPAN_MAP)*63 (mrsas.c:1645-1646).
  2. MR_ValidateMapInfo (mrsas_fp.c:247-274) validates only pFwRaidMap->totalSize against a ldCount-derived size (:257-265). No per-LD geometry is validated.
  3. When fw advertises supportUnevenSpans (mrsas.c:1757-1762, also re-run at every AEN map update mrsas.c:2961), MR_ValidateMapInfo calls mr_update_span_set (:267-268).
  4. mr_update_span_set (:916-1021) iterates span < raid->spanDepth (fw u_int8_t, 0..255) over spanBlock[MAX_RAIDMAP_SPAN_DEPTH = 8] (OOB reads β€” DF-1528 class) and, for each element, runs for (count = 0; count < raid->spanDepth; count++) ... span_set->strip_offset[count] = span_row_width; (:949-959) β€” strip_offset is u_int8_t[8] (mrsas.h:733). With spanDepth = 64..255 this writes up to 247 bytes past each LD_SPAN_SET (88 bytes each), cascading through ldSpanInfo[ld].span_set[0..7] and, for ld = 63, past the end of sc->log_to_span[MAX_LOGICAL_DRIVES] β€” the last member of struct mrsas_softc (mrsas.h:2423-2425) β€” into whatever kernel memory follows the softc allocation.
  5. Written values: span_row_width accumulates fw-chosen spanRowDataSize (u_int8_t) per qualifying span (:958), so the overflow content is fw-chosen bytes (PoC shows exact running-sum values 0x00..0x3f at guard offsets +0..+39).

Reproduction (guest, unmodified driver source)

Probe KLD fp_probe.ko compiles the guest's own /usr/src/sys/dev/raid/mrsas/ mrsas_fp.c (symbol-renamed, zero source edits) and calls the real MR_ValidateMapInfo() with a fw-shaped map (spanDepth=64, consistent totalSize) against a real struct mrsas_softc followed by a 0xAA guard.

Result (run.log, reproduced across 3 independent runs/boot cycles):

fp_probe: MR_ValidateMapInfo returned 0 (0 == map accepted, spansets built)
fp_probe: *** DF-4330 CONFIRMED: OOB WRITE past end of sc->log_to_span[64]
          (last softc member): first clobbered byte at +0 = 0x18
...
fp_probe: guard integrity: 40/4096 bytes clobbered past softc end

Baseline behavior: the current validation accepts the malicious map (returns 0) and the write happens; guard bytes +0..+39 hold the exact accumulated widths (0x18,0x19,...,0x3f with this map shape). LD 63 / element 7 / count 8..63 arithmetic: offset into ldSpanInfo[63] = 7*88 + 64 + count = 680..743 vs array size 704 β†’ 39-40 bytes past the 64-entry array, i.e. past the softc tail.

Exploit chain

Not pushed to uid=0: the write source is DMA'd firmware data and no mrsas HBA exists in the QEMU guest, so in-situ heap shaping cannot be demonstrated. Primitive as proven: a linear, firmware-authored overflow of up to ~230 bytes past sc->log_to_span[] into adjacent kernel heap (the softc is the driver's root object; on real hardware whatever follows it in the kmalloc/device zone gets fw-chosen bytes), plus mass corruption of the log_to_span interior itself (span_set geometry fields), which feeds later divisions and index walks. Classification: kernel memory corruption (bucket memcorrupt); realistic impact ceiling DoS→controlled corruption under a compromised firmware/PCIe device.

Fix validation

fix.diff adds per-LD geometry validation to MR_ValidateMapInfo (spanDepth ≀ MAX_RAIDMAP_SPAN_DEPTH and != 0, noElements ≀ MAX_QUAD_DEPTH, every live quad diff != 0, spanRowSize != 0 for RAID 0/5/6) before any spanset build. Validated by compiling the patched mrsas_fp.c into the same probe (same kernel build flags, same call path β€” the file is compiled into the kernel either way):

  • baseline (unpatched): map accepted, 40/4096 guard bytes clobbered (run.log)
  • patched: mode 0 benign map still accepted (fix_run.log: "returned 0", "sanity OK", guard 0/4096) β€” no valid-map regression;
  • patched: mode 1 malicious map rejected ("nexus0: invalid RAID map geometry (ld/span/quad), rejecting map", returned 1, guard 0/4096);
  • patched: mode 2 (diff=0, DF-4331 input) also rejected β€” no panic.

Module-level validation was used instead of a full kernel rebuild: the compiled unit, flags and entry function are identical to the in-kernel build (mrsas is device mrsas in X86_64_GENERIC); a whole-kernel rebuild adds no additional coverage of this file's code path.

Artifacts

  • fp_probe.c, rename.h, Makefile, build.sh β€” probe
  • run.log β€” baseline (unpatched): sanity + OOB write
  • fix_run.log β€” patched: benign accepted, malicious rejected twice
  • build.log / fix_build.log β€” full untrimmed builds of both variants
  • serial_decisive.log β€” serial-console record incl. DF-4331 panic
  • fix.diff β€” git-apply-able (git apply --check passes vs this tree)

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

fixed
baseline reproduced→ patch + rebuild →patched clean

Patched mrsas_fp.c (fix.diff) rejects the spanDepth=64 map at MR_ValidateMapInfo (returns 1, 'invalid RAID map geometry'), guard 0/4096 - OOB write eliminated; benign mode-0 map still accepted (returned 0, spansets built) - no regression; DF-4331 input (diff=0) also rejected without panic

fix_run.log lines: 'invalid RAID map geometry (ld/span/quad), rejecting map' + 'MR_ValidateMapInfo returned 1' + 'guard integrity: 0/4096' for mode 1 AND mode 2; mode 0: 'returned 0' + 'sanity OK'
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Exploit chain

compromised fw/PCIe device -> MR_DCMD_LD_MAP_GET_INFO DMA with spanDepth>8 for an exposed LD -> MR_ValidateMapInfo accepts (totalSize forged) -> mr_update_span_set strip_offset[] linear OOB write past sc->log_to_span[] into adjacent kernel heap with fw-chosen bytes (demonstrated: 40 bytes, values 0x00-0x3f running sum) -> corrupt neighbor objects/interior span_set geometry (feeds later divisors/indices). uid0 chain not developable without the HBA (no in-situ heap to shape).

Evidence (decisive lines)

run.log: 'MR_ValidateMapInfo returned 0' + 'DF-4330 CONFIRMED: OOB WRITE past end of sc->log_to_span[64]' + 'guard integrity: 40/4096 bytes clobbered'; fix_run.log: benign map still accepted (no regression), malicious map rejected with guard 0/4096; serial_decisive.log: full console record; VERDICT.md: line-by-line trace

PoC changes

probe embeds the guest's unmodified /usr/src mrsas_fp.c with symbols renamed (rename.h) to avoid collision with the built-in driver; calls the real MR_ValidateMapInfo on a real mrsas_softc with a 0xAA guard behind log_to_span; craft: ldTgtIdToLd 0xFF-filled with [63]=63 (real-fw convention), spanDepth=64, consistent totalSize, 0x01 slack for the DF-1528-class OOB reads

Verified recommended fix

Validate per-LD geometry in MR_ValidateMapInfo before building spansets: reject maps with spanDepth==0 or >MAX_RAIDMAP_SPAN_DEPTH, noElements==0 or >MAX_QUAD_DEPTH, any quad diff==0, or spanRowSize==0 on RAID 0/5/6 (see fix.diff)

Verdict

REPRODUCED on the guest with the unmodified kernel source: mr_update_span_set (mrsas_fp.c:949-959) writes firmware-indexed strip_offset[count] for count < fw spanDepth (up to 255) into u_int8_t strip_offset[8], cascading across LD_SPAN_SETs and, for LD 63, past the end of sc->log_to_span[64] - the last member of struct mrsas_softc - into adjacent kernel heap. The driver's only map validation (totalSize equality, mrsas_fp.c:257-265) accepts the malicious map (returned 0) and 40 guard bytes past a correctly-sized softc were overwritten with the exact accumulated fw spanRowDataSize sum. Ceiling: kernel memory corruption authored by a malicious/compromised MegaRAID firmware or PCIe device (same precondition as DF-1528, but a WRITE primitive); in-guest privesc not reachable because no mrsas HBA exists to host the DMA source.