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

mrsas_sync_map_info writes firmware-controlled ldCount Γ— 4-byte entries into a fixed 172,200-byte RAID-map DMA buffer β€” unbounded linear kernel heap overflow (computed bound size_sync_info is dead code)

Summary

mrsas_sync_map_info takes the sync-loop bound from the firmware-authored RAID map: num_lds = map->raidMap.ldCount, a full u_int32_t, then writes one 4-byte MR_LD_TARGET_SYNC per iteration into raidmap_mem - a buffer allocated at exactly 172,200 bytes. The author's intended bound, size_sync_info, is computed and never used. Capacity is 43,050 entries; on-guest proof shows ldCount=65536 with the matching totalSize PASSES MR_ValidateMapInfo and makes the loop write 262,144 bytes - 89,944 bytes past the bus_dmamem allocation - with 3 of every 4 bytes firmware-chosen. The same loop OOB-reads the source map via ldSpanMap[i] for i >= 64. Validation cannot save it: MR_ValidateMapInfo only requires totalSize (also firmware-supplied) to be arithmetically consistent with ldCount in u32 math. Malicious or compromised controller firmware: arbitrary-length linear overwrite of kernel memory adjacent to the raidmap allocation, 3-of-4 controlled bytes per entry - full kernel compromise primitive. Strictly stronger than the known DF-1242/1243/1244 family (write, firmware-chosen length). Hardware-gated on this guest; source-certain with on-guest arithmetic proof.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3843 Β· 10 files
FileTypeDescriptionSize
README.md β€” 2.6 KB ↓ raw
VERDICT.md β€” 3.9 KB ↓ raw
map_sync_overflow_proof.c β€” 7.7 KB view raw
build.sh β€” 114 B view raw
run.sh β€” 98 B view raw
build.log β€” 9 B view raw
run.log β€” 1.1 KB view raw
env.txt β€” 260 B view raw
fix.diff β€” 1.9 KB view raw
verdict.json β€” 4.1 KB view raw

DF-3843 β€” mrsas_sync_map_info: firmware ldCount drives an unbounded OOB write past the RAID-map DMA buffer

Where

  • sys/dev/raid/mrsas/mrsas.c:3207 β€” num_lds = map->raidMap.ldCount; (full u32, firmware-authored)
  • sys/dev/raid/mrsas/mrsas.c:3210 β€” size_sync_info = sizeof(MR_LD_TARGET_SYNC) * num_lds; β€” computed and never used
  • sys/dev/raid/mrsas/mrsas.c:3213-3224 β€” write loop into raidmap_mem[(map_id-1)&1], a fixed map_sz = sizeof(MR_FW_RAID_MAP) + sizeof(MR_LD_SPAN_MAP)*(MAX_LOGICAL_DRIVES-1) = 172,200-byte bus_dmamem_alloc buffer (size set at mrsas.c:1645-1646)
  • Validation gap: sys/dev/raid/mrsas/mrsas_fp.c:257-265 (MR_ValidateMapInfo) only checks totalSize == sizeof(MR_FW_RAID_MAP) - sizeof(MR_LD_SPAN_MAP) + sizeof(MR_LD_SPAN_MAP)*ldCount in u32 arithmetic β€” the firmware supplies both sides of that equation, so any ldCount passes.

Class

Linear kernel-heap overflow past a DMA allocation: length fully firmware-chosen (u32 count Γ— 4 bytes), content = per 4-byte entry {targetId: FW u8, 0x00, seqNum: FW u16} (3 of 4 bytes attacker-chosen). The same loop also OOB-reads the source map via MR_LdRaidGet(i)/MR_GetLDTgtId(i) (mrsas_fp.c:174-182, unbounded ldSpanMap[i]).

Reachability

mrsas_sync_map_info is called from: 1. mrsas_setup_raidmap (mrsas.c:1680) at attach β€” after MR_ValidateMapInfo "passes", 2. mrsas_complete_mptmfi_passthru (mrsas.c:2965) on every RAID-map sync completion (AEN-driven), 3. mrsas_reset_ctrl (mrsas.c:2477) after OCR.

Threat actor: malicious/compromised controller firmware or a device that DMA-authors the map (the map buffer is filled by the card). No purely-local unprivileged trigger β€” this is the same gating as DF-1528 (mrsas_fp.c map reads), DF-1242/1243/1244.

Reproduce (arithmetic proof on the audit guest)

./build.sh   # cc -O2 -Wall -o /tmp/df3843_proof map_sync_overflow_proof.c
./run.sh     # runs the harness; struct definitions copied verbatim from mrsas.h

Expected decisive lines (see run.log):

map_sz (mrsas.c:1645)       = 172200  <- bus_dmamem_alloc size
capacity in sync entries    = 43050  (max ldCount before OOB)
Firmware supplies ldCount=65536, totalSize=0x9e028a8:
  MR_ValidateMapInfo: totalSize(0x9e028a8) == expected(0x9e028a8) -> PASSES
  => 89944 bytes written PAST the end of the DMA allocation

The kernel-side execution itself needs a MegaRAID SAS HBA (Thunderbolt/Invader/Fury); the audit QEMU guest has none (no LSI/Avago PCI device present, no /dev/mrsas* nodes) although the driver is compiled into X86_64_GENERIC (kldstat -v | grep mrsas β†’ 80 pci/mrsas).

VERDICT.md
↓ download raw

VERDICT β€” DF-3843

status: inconclusive (hardware-gated; source-certain) impact class: kernel memory corruption (linear OOB write, firmware-chosen length) confidence: certain (source + on-guest struct-math proof)

What was verified

  1. Source trace (certain). mrsas_sync_map_info() reads the loop bound straight from the firmware-authored RAID map: - sys/dev/raid/mrsas/mrsas.c:3207 num_lds = map->raidMap.ldCount; - sys/dev/raid/mrsas/mrsas.c:3210 computes size_sync_info = sizeof(MR_LD_TARGET_SYNC) * num_lds and never uses it β€” the bound the author intended is dead code. - sys/dev/raid/mrsas/mrsas.c:3220-3224 writes one MR_LD_TARGET_SYNC (4 bytes) per iteration into target_map = sc->raidmap_mem[(sc->map_id - 1) & 1] (mrsas.c:3213), a buffer of exactly map_sz bytes (mrsas.c:1645-1646). - MR_ValidateMapInfo (sys/dev/raid/mrsas/mrsas_fp.c:257-265) does not bound ldCount; it only requires totalSize (another firmware field) to be arithmetically consistent with it in u32 math. Every ldCount has a passing totalSize.

  2. Struct-math proof executed on the audit guest (map_sync_overflow_proof.c, run.log): - sizeof(MR_FW_RAID_MAP)=12936, sizeof(MR_LD_SPAN_MAP)=2528, sizeof(MR_FW_RAID_MAP_ALL)=172200 β€” matches the header's own comments (0x3288 / 0x9E0). - map_sz = 12936 + 2528*63 = 172200 bytes = 43,050 four-byte sync entries of capacity. - A map with ldCount=65536, totalSize=0x9e028a8 passes MR_ValidateMapInfo and makes the loop write 262,144 bytes β€” 89,944 bytes past the bus_dmamem_alloc region, with 22,486 fully-OOB entries of firmware-chosen {u8,u8=0,u16} payload. Larger counts (up to 2^32-1) overflow proportionally further; a malicious device picks length and content.

  3. Guest execution of the kernel path: impossible on this host. The driver is compiled into the running kernel (kldstat -v β†’ 80 pci/mrsas; sys/config/X86_64_GENERIC:123), but the guest has no LSI/Avago PCI device and no /dev/mrsas* node β€” the driver never attaches, so the firmware-facing trigger surface does not exist in the VM. Reproducing the corruption itself would require a MegaRAID HBA (or a hacked QEMU megasas model emitting a crafted map), which is outside this guest's capability. Hence inconclusive, not reproduced.

Exploit chain (if hardware/firmware control exists)

firmware DMA-authors RAID map with ldCount = K > 43050 (+consistent totalSize) β†’ attach/AEN/OCR triggers mrsas_sync_map_info β†’ linear overwrite of kernel memory adjacent to the 172,200-byte raidmap bus_dmamem_alloc, 3 controllable bytes per 4, length β‰ˆ 4Kβˆ’172200 β†’ classic heap-metadata/vtable overwrite β†’ code execution in kernel context. With K chosen near 43,050 the write stays small and surgical; with large K it is a destructive sledgehammer.

Why not false positive

The bound (size_sync_info) is computed and then ignored β€” the intent to bound exists in the code. Linux's megaraid_sas upstream equivalents allocate the sync buffer from ldCount (mr_update_load_balance_params/sync alloc paths) or clamp; this port dropped the clamp.

Fix validation

fix.diff adds an explicit ldCount > MAX_LOGICAL_DRIVES rejection in mrsas_sync_map_info (release cmd, return error) and a validation-time bound in MR_ValidateMapInfo so a bogus map never reaches fast-path code either. Not executable on this guest (no HBA) β†’ fix_status = not_testable; the fix is structurally minimal and obviously removes the only path to the unbounded loop.

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 bounds ldCount at both the sync path (mrsas.c) and validation (mrsas_fp.c); cannot be boot-tested on this guest (no HBA), so fix validation is not_testable rather than fixed. The bound provably removes the only writer of the overflow: the loop cannot execute past MAX_LOGICAL_DRIVES iterations after the guard.

fix.diff hunks; map_sync_overflow_proof.c capacity math (43050 entries capacity vs MAX_LOGICAL_DRIVES=64 bound).
↓ fix.diffper-fix-DF-3843

Confirmed kernel references

Detail

Exploit chain

firmware DMA-authors RAID map with ldCount K>43050 plus consistent totalSize -> attach / AEN-driven map-sync completion (mrsas.c:2965) or OCR (mrsas.c:2477) calls mrsas_sync_map_info -> linear overwrite of kernel memory adjacent to raidmap_mem[] with {u8 targetId, 0x00, u16 seqNum} x K -> heap corruption under firmware control -> kernel code execution. No local unprivileged trigger without the device.

Evidence (decisive lines)

run.log lines 'capacity in sync entries = 43050' and '=> 89944 bytes written PAST the end of the DMA allocation'; VERDICT.md full trace; map_sync_overflow_proof.c struct definitions copied verbatim from mrsas.h; env.txt shows guest has no LSI PCI device / no /dev/mrsas*.

PoC changes

No seed PoC existed; authored map_sync_overflow_proof.c standalone userland harness (verbatim mrsas.h structs) because the kernel path needs an HBA the guest does not have.

Verified recommended fix

Reject ldCount > MAX_LOGICAL_DRIVES in mrsas_sync_map_info before the copy loop (release the mfi cmd and return error) and add the same bound to MR_ValidateMapInfo so bogus maps never enable fast path.

Verdict

Source-certain unbounded firmware-driven OOB write: mrsas_sync_map_info (mrsas.c:3207,3220-3224) copies num_lds = raidMap.ldCount (full u32) 4-byte entries into a fixed 172,200-byte bus_dmamem buffer; the computed bound size_sync_info (mrsas.c:3210) is never used and MR_ValidateMapInfo (mrsas_fp.c:257-265) only checks totalSize/ldCount mutual consistency, both firmware-supplied. On-guest struct-math proof (run.log) shows ldCount=65536 with totalSize=0x9e028a8 passes validation and overwrites 89,944 bytes past the allocation with 3-of-4 firmware-chosen bytes per entry. Kernel-path execution requires a MegaRAID HBA (Thunderbolt/Invader/Fury) or crafted device DMA; the audit guest has the driver compiled in (kldstat: 80 pci/mrsas) but no LSI PCI device and no /dev/mrsas* node, so the trigger surface does not exist in the VM. Hardware-gated; malicious-firmware threat model (same gating as DF-1528/DF-1242..1244, but a write primitive of firmware-chosen length).