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

mpssas_fw_work dereferences fixed-offset event members without checking EventDataLength covers the struct: pre-loop ConfigElement[0].VolDevHandle at +0x0A and LOG_ENTRY LogData[0] at +0x14 read past the kmalloc'd event copy

Summary

The event copy is exactly EventDataLength*4 bytes but handlers read fixed-offset members unconditionally: the IR pre-loop element[0] read at +0x0A executes even when NumElements==0 (so a DF-1697 loop clamp does not remove it), and LOG_ENTRY reads LogData[0] at +0x14. Distinct sinks from DF-1696/1697 - a driver patched for both still takes these reads OOB on short-but-nonzero events. Guard-page harness verified on guest (IR EDL=2 faults at +0x0A; LOG EDL=4 covers the qualifier but faults at LogData[0]). Carry-the-size-and-gate fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4560 Β· 9 files
FileTypeDescriptionSize
df4560_harness.c β€” 6.5 KB view raw
build.sh β€” 94 B view raw
run.sh β€” 89 B view raw
build.log β€” 404 B view raw
run.log β€” 1.7 KB view raw
env.txt β€” 230 B view raw
fix.diff β€” 2.4 KB view raw
VERDICT.md β€” 2.8 KB ↓ raw
verdict.json β€” 3.9 KB view raw
VERDICT.md
↓ download raw

DF-4560 VERDICT β€” reproduced (harness), Low, OOB fixed-offset reads on short fw events

Bottom line

mpssas_fw_work() dereferences fixed-offset members of the EventDataLength*4-sized heap copy with no size validation at two sinks that are NOT covered by the known findings: (a) the pre-loop element->VolDevHandle read at offset 0x0A (sys/dev/raid/mps/mps_sas_lsi.c:276-279) which executes even when NumElements == 0, so clamping the DF-1697 loop does not remove it; and (b) the LOG ENTRY ADDED LogEntryQualifier/LogData[0] reads at 0x0E/0x14 (sys/dev/raid/mps/mps_sas_lsi.c:515-518). With firmware-supplied EventDataLength of 1-2 (IR) or 1-5 (LOG) words these reads run past the kmalloc'd buffer.

Evidence

Guard-page harness (df4560_harness.c), guest DragonFly 6.5-DEVELOPMENT, run.log:

  • IR, EDL=2 (8-byte alloc): read at +0x0A faults β€” 2 bytes OOB. Control EDL=3 (12 B): exact fit, clean.
  • LOG, EDL=1: faults at the qualifier read (+0x0E); EDL=4 (16 B): qualifier in-bounds but LogData[0] (+0x14) still faults β€” the sinks are independently reachable; control EDL=6: clean.
  • Arithmetic table in the same log enumerates every EDL 0..6.

Impact assessment (honest ceiling)

Bounded OOB reads (2 bytes / 1 byte) of heap memory adjacent to a fresh small kmalloc: the IR value feeds mps_mapping_get_raid_id_from_handle (a table walk; a garbage handle simply fails the lookup or yields an id within the IR range β€” whose use is DF-4559's problem, not made worse here), the LOG value only selects a kprintf branch. No bytes are returned to userspace β€” no disclosure; realistic worst case is a page-fault panic at a slab boundary (impact=panic class, low likelihood). Rated Low.

Root cause & fix

Root cause: mpssas_evt_handler records only the event type, not the validated length (sys/dev/raid/mps/mps_sas_lsi.c:83-87, 140-149), so handlers cannot check coverage. fix.diff adds u16 sz to mps_fw_event_work, sets it at enqueue, and gates both sinks (IR: sz >= sizeof(header)+sizeof(element) and NumElements > 0 before reading element[0]; LOG: sz >= offsetof(LogData)+1). Pure arithmetic model of the fix: the guarded minimums are exactly the EDL=3 / EDL=6 controls which run clean in run.log.

Notes

Distinct from DF-1696 (u16 truncation of sz itself / kmalloc(0)) and DF-1697 (loop counts vs. allocation): a driver patched for both would still take these two reads OOB on short-but-nonzero events.

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

Fix modeled in-harness by the exact-fit controls (IR EDL=3, LOG EDL=6 run clean β€” precisely the minimum sizes fix.diff enforces); baseline EDL=2/1/4 arms fault. fix.diff not applied to the read-only sys/ tree; kernel-build validation not meaningful for a harness-verified HBA driver finding.

run.log: EDL=3 and EDL=6 controls clean vs EDL=2/1/4 faulting; fix.diff in pack.
↓ fix.diffper-fix-DF-4560

Confirmed kernel references

Detail

Exploit chain

malicious fw sends IR CONFIG CHANGE LIST (or LOG ENTRY ADDED) with small nonzero EventDataLength -> kmalloc(EDL*4) copy -> mpssas_fw_work reads ConfigElement[0].VolDevHandle at +0x0A (unconditionally, before any NumElements check) / LogData[0] at +0x14 -> heap OOB read past the event copy; values feed mapping lookups and log-branch selection only.

Evidence (decisive lines)

run.log: '[IR  EDL=2] SIGSEGV β€” OOB read past the EventDataLength-sized allocation (expected)', '[IR  EDL=3] clean', '[LOG EDL=1] SIGSEGV', '[LOG EDL=4] SIGSEGV', '[LOG EDL=6] clean', plus the full EDL 0..6 arithmetic table.

PoC changes

No seed PoC (pass-2 new finding). Harness written from scratch: guard-placed EDL*4 buffers with the exact member offsets from mpi2_ioc.h structs (ConfigElement@0x08, VolDevHandle@0x0A, LogEntryQualifier@0x0E, LogData@0x14), qualifier pre-set to MPI2_WD_LOG_ENTRY (0x8002, mpsvar.h:111) to keep the LogData read on-path, fork()ed cases, plus the pure-arithmetic EDL table.

Verified recommended fix

Carry the validated size in mps_fw_event_work (u16 sz set at enqueue) and gate the IR pre-loop element deref (sz >= header+element && NumElements > 0) and the LOG_ENTRY handler (sz >= offsetof(LogData)+1) in mpssas_fw_work.

Verdict

REPRODUCED (harness; guest has no LSI mps HBA β€” same methodology as DF-1696/DF-1697/DF-4363). mpssas_evt_handler copies exactly EventDataLength*4 bytes (mps_sas_lsi.c:140-148) but mpssas_fw_work dereferences fixed-offset members with no coverage check at two sinks NOT covered by DF-1696/DF-1697: (1) the pre-loop element->VolDevHandle read at offset 0x0A (mps_sas_lsi.c:276-279) which executes even when NumElements==0, so a DF-1697 loop clamp does not remove it β€” with EventDataLength<=2 the 8-byte allocation is read 2 bytes OOB; (2) LOG ENTRY ADDED LogEntryQualifier@0x0E / LogData[0]@0x14 (mps_sas_lsi.c:515-518) β€” OOB for EventDataLength<=3 / <=5. Guard-page harness: IR EDL=2 faults at +0x0A (control EDL=3 clean); LOG EDL=1 faults at the qualifier, EDL=4 covers the qualifier but still faults at LogData[0] (control EDL=6 clean). Impact honestly Low: bounded 1-2 byte heap reads consumed internally (mapping-table lookup / kprintf branch), no userspace disclosure; panic only at a slab/page boundary. fix.diff (carry sz in mps_fw_event_work + gate both sinks) modeled by the clean controls.