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

sassc->targets[] sized facts->MaxTargets but IR volume mapping IDs run to MaxTargets+MaxVolumes-1: mpssas_volume_add and the IR-config REMOVED branch write out of bounds (fw-controlled u64 at fixed offset)

Summary

The targets array is allocated with MaxTargets entries but the mapping layer legitimately assigns volume IDs up to MaxTargets+MaxVolumes-1; with the supported HIGH IRVolumeMappingFlags config every RAID volume ID lands past the end - writes include tid, fw u16 handle, an 8-byte fw-controlled wwid, and list-head NULL pairs. find_target_by_handle scanning only [0,MaxTargets) corroborates the divergence (the already-added check can never see a volume). Fires during normal discovery on HIGH-configured IR controllers - dirty-corruption reliability bug - or with fw-chosen IDs/payload in the attacker-controls-the-card model. Guard-page harness verified on guest: first volume write faults exactly at the boundary, 416 bytes clobbered past the end with the fw u64 at +51208; fixed-model arm clean. Size-for-full-ID-space fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4559 Β· 10 files
FileTypeDescriptionSize
df4559_harness.c β€” 11.3 KB view raw
build.sh β€” 94 B view raw
run.sh β€” 89 B view raw
build.log β€” 552 B view raw
run.log β€” 2.2 KB view raw
run.2.log β€” 2.2 KB view raw
env.txt β€” 230 B view raw
fix.diff β€” 2.5 KB view raw
VERDICT.md β€” 4.6 KB ↓ raw
verdict.json β€” 4.6 KB view raw
VERDICT.md
↓ download raw

DF-4559 VERDICT β€” reproduced (harness), High, OOB heap write in mps IR volume registration

Bottom line

sassc->targets[] is allocated with sc->facts->MaxTargets entries (sys/dev/raid/mps/mps_sas.c:696-697) while the mapping layer legitimately hands out target IDs up to sc->max_devices - 1 = facts->MaxTargets + facts->MaxVolumes - 1 (sys/dev/raid/mps/mps_mapping.c:1733-1734, :232-234). In HIGH IR-volume-mapping mode every RAID volume ID indexes past the end of the array. Both RAID-volume registration sinks live in the file under audit and write there: mpssas_volume_add() (sys/dev/raid/mps/mps_sas_lsi.c:847-853: tid, fw u16 handle, fw-controlled u64 devname = wwid, TAILQ_INIT/SLIST_INIT NULL pointer pairs) and the IR CONFIG CHANGE REMOVED branch (sys/dev/raid/mps/mps_sas_lsi.c:310-316: six zeroing writes), plus the OOB targetid = targ - sassc->targets computation fed into CAM (sys/dev/raid/mps/mps_sas.c:285 via mpssas_rescan_target from mps_sas_lsi.c:317/:855).

Why it reproduces (evidence)

Guard-page harness on the DragonFly 6.5-DEVELOPMENT guest (run.log, run.2.log β€” two consecutive runs, identical):

  • voladd-HIGH: the write sequence of mps_sas_lsi.c:848-853 at id = max_devices - max_volumes = MaxTargets SIGSEGVs on the guard page β€” the write begins at byte n*sizeof(mpssas_target) of an n*sizeof(mpssas_target)-byte allocation: unambiguously past the end.
  • removed-HIGH: zeroing writes of :311-316 at id = max_devices-1 fault 1600 bytes past the end.
  • voladd-LOW control: clean β€” proves the trigger is exactly the ID-range mismatch, not the write sequence.
  • Canary: 416 bytes corrupted past a 51200-byte allocation ([+51200..+52679]); readback of devname at +51208 returns the fw-supplied u64 verbatim β€” the controlled-write primitive is real.

Threat model / reachability

  • The gate is IOC page 8 IRVolumeMappingFlags selecting HIGH volume mapping plus any IR volume event (volume created / comes online (mps_sas_lsi.c:288/:406), or removed (:296-317)). That is a supported, operator-selectable configuration on LSI IR firmware β€” the corruption fires during normal discovery on such boxes, no malicious input needed.
  • In the malicious-firmware threat model (same one DF-1696/1697 use), the fw fully controls the ID (mapping table) and the 8-byte wwid payload.
  • What stops short of uid=0: the write content is constrained (fixed offsets inside struct mpssas_target, value set = {bounded id, fw u16 handle, fw u64 wwid, NULLs, zeros}) and the overflow distance is k*sizeof(mpssas_target) for k < MaxVolumes β€” adjacent-heap corruption of whatever follows the targets allocation, panic-grade in practice. The guest has no mps HBA, so an end-to-end in-driver escalation chain cannot be demonstrated on this hardware; per the DF-4363/DF-4013 precedent the primitive is proven by the byte-exact harness.

Root cause (path:line)

Fix validation

fix.diff sizes sassc->targets with sassc->maxtargets = facts->MaxTargets + facts->MaxVolumes and uses it for the by-handle scan. Harness arm voladd-HIGH-FIXED re-runs the identical write sequence against the fixed sizing: clean exit, zero canary clobber β€” the previously-observed OOB write is gone (baseline vs patched in run.log).

Notes

  • The informational harness arm also documents the max_devices u16 truncation corner (MaxTargets=65535/MaxVolumes=255 β†’ max_devices=254 β†’ start_idx wraps to 0xFFFFFFFF at mps_sas_lsi.c:901): malicious-fw only, wild mapping_table[0xffffffff] read at shutdown; folded into this pack's notes rather than filed separately (absurd facts values).
  • Guest dirtied only with /root harness binaries; guest reset (with-src) performed after the run.

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

Validated with the harness fixed-model arm (voladd-HIGH-FIXED): identical write sequence against MaxTargets+MaxVolumes sizing stays in bounds and exits clean; baseline sizing SIGSEGVs. fix.diff not applied to the read-only sys/ tree; kernel-build validation not meaningful for a harness-verified HBA driver finding (no mps controller in the guest).

run.log lines: '[voladd-HIGH ] -> KILLED by SIGSEGV ... (OOB write confirmed)' vs '[voladd-HIGH-FIXED] -> clean exit (all writes in bounds)'; fix.diff in pack.
↓ fix.diffper-fix-DF-4559

Confirmed kernel references

Detail

Exploit chain

fw/IR config: pg8 IRVolumeMappingFlags=HIGH + any IR volume event (created/online at mps_sas_lsi.c:288/:406, removed at :296-317) -> mapping assigns volume id >= MaxTargets -> &sassc->targets[id] (mps_sas_lsi.c:847/:310) OOB heap writes incl. fw-controlled 8-byte devname=wwid at fixed offset +8 within the slot, 200-byte slots, up to MaxVolumes slots past the end -> adjacent-kernel-heap corruption (panic-grade; controlled-content write primitive proven by canary readback).

Evidence (decisive lines)

run.log/run.2.log: '[voladd-HIGH ] KILLED by SIGSEGV ... (OOB write confirmed)', '[voladd-LOW ] clean exit', '[removed-HIGH] KILLED by SIGSEGV', '[voladd-HIGH-FIXED] clean exit', 'canary result: 416 bytes clobbered past the allocation end, spanning [+51200 .. +52679]', 'devname readback ... MATCH: fw-controlled 8B OOB write'; VERDICT.md has the full path:line trace.

PoC changes

No seed PoC existed (pass-2 new finding). Harness written from scratch: faithful x86-64 replica of struct mpssas_target (mps_sas.h:40-79, sizeof=200, offsets printed), replica of the alloc (:697), ID space (:1734), IR maprange (:232-234) and the verbatim write sequences of :848-853 / :311-316; guard-page + canary + fixed-model arms; fork()ed scenarios.

Verified recommended fix

Size sassc->targets for the full mapping ID space: sassc->maxtargets = facts->MaxTargets + facts->MaxVolumes used in the kmalloc (mps_sas.c:696-697) and in mpssas_find_target_by_handle's scan bound (mps_sas.c:181).

Verdict

REPRODUCED (harness; guest has no LSI mps HBA β€” same methodology as DF-4363/DF-1696/DF-1697). sassc->targets is allocated with facts->MaxTargets entries (mps_sas.c:696-697) while the mapping layer hands out target IDs up to max_devices-1 = MaxTargets+MaxVolumes-1 (mps_mapping.c:1733-1734); with IOC pg8 IRVolumeMappingFlags=HIGH the IR volume IDs are exactly [MaxTargets, MaxTargets+MaxVolumes-1] (mps_mapping.c:232-234) and mpssas_volume_add (mps_sas_lsi.c:847-853) writes tid/handle/devname(=fw-controlled u64 wwid)/TAILQ_INIT/SLIST_INIT at targets[id] β€” past the end of the allocation. Guard-page harness on the DragonFly guest: voladd-HIGH SIGSEGVs on the first volume write at byte nsizeof(mpssas_target) of an nsizeof allocation; removed-HIGH (zeroing branch :310-316) faults at the last slot; LOW-mapping control runs clean; canary run measures 416 bytes clobbered past a 51200-byte allocation spanning [+51200..+52679] with the fw-supplied u64 verified to land at +51208. Fixed-model arm (array sized MaxTargets+MaxVolumes per fix.diff) exits clean β€” bad behavior gone. Escalation ceiling honestly noted: constrained-content, fixed-offset overflow; end-to-end in-driver chain not demonstrable without the HBA.