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)
PoC verification
Evidence pack
findings/poc/DF-4330 Β· 16 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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),totalSizeforged consistent withldCount = 1(passes the driver's only check), and- a real
struct mrsas_softcwhose tail (log_to_span[]is the last member,mrsas.h:2424) is followed by a 4096-byte0xAAguard.
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).
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)
- 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). MR_ValidateMapInfo(mrsas_fp.c:247-274) validates onlypFwRaidMap->totalSizeagainst aldCount-derived size (:257-265). No per-LD geometry is validated.- When fw advertises
supportUnevenSpans(mrsas.c:1757-1762, also re-run at every AEN map updatemrsas.c:2961),MR_ValidateMapInfocallsmr_update_span_set(:267-268). mr_update_span_set(:916-1021) iteratesspan < raid->spanDepth(fwu_int8_t, 0..255) overspanBlock[MAX_RAIDMAP_SPAN_DEPTH = 8](OOB reads β DF-1528 class) and, for each element, runsfor (count = 0; count < raid->spanDepth; count++) ... span_set->strip_offset[count] = span_row_width;(:949-959) βstrip_offsetisu_int8_t[8](mrsas.h:733). WithspanDepth = 64..255this writes up to 247 bytes past eachLD_SPAN_SET(88 bytes each), cascading throughldSpanInfo[ld].span_set[0..7]and, forld = 63, past the end ofsc->log_to_span[MAX_LOGICAL_DRIVES]β the last member ofstruct mrsas_softc(mrsas.h:2423-2425) β into whatever kernel memory follows the softc allocation.- Written values:
span_row_widthaccumulates fw-chosenspanRowDataSize(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β proberun.logβ baseline (unpatched): sanity + OOB writefix_run.logβ patched: benign accepted, malicious rejected twicebuild.log/fix_build.logβ full untrimmed builds of both variantsserial_decisive.logβ serial-console record incl. DF-4331 panicfix.diffβ git-apply-able (git apply --checkpasses 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
fixedPatched 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'
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.
No comments yet.