⬢ DragonFlyBSD Kernel Audit
DF-4560 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-4560",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "REPRODUCED (harness; guest has no LSI mps HBA \u2014 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 \u2014 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) \u2014 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.",
  "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": "run.log: [IR  EDL=2] SIGSEGV \u2014 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.",
  "kernel_refs": [
    "sys/dev/raid/mps/mps_sas_lsi.c:273",
    "sys/dev/raid/mps/mps_sas_lsi.c:276",
    "sys/dev/raid/mps/mps_sas_lsi.c:279",
    "sys/dev/raid/mps/mps_sas_lsi.c:515",
    "sys/dev/raid/mps/mps_sas_lsi.c:518",
    "sys/dev/raid/mps/mps_sas_lsi.c:140",
    "sys/dev/raid/mps/mpi/mpi2_ioc.h:699",
    "sys/dev/raid/mps/mpi/mpi2_ioc.h:516"
  ],
  "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.",
  "attempts": 2,
  "guest_uname": "DragonFly 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",
  "runtime_sec": 30,
  "guest_dirty": 0,
  "build_cmd": "cc -O2 -Wall -o df4560 df4560_harness.c",
  "run_cmd": "./df4560",
  "code_hash": "2b7e93c559abe49330d99456fecaa0fea0bfa81f045dd2beab744243c893234c",
  "notes": "Distinct sinks from DF-1696 (sz truncation/kmalloc(0)) and DF-1697 (loop counts): a driver patched for both still takes these reads OOB on short-but-nonzero events. The same run.log tail carries the DF-4561 coverage arithmetic. Guest reset (with-src) after the run.",
  "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.",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Fix modeled in-harness by the exact-fit controls (IR EDL=3, LOG EDL=6 run clean \u2014 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.",
  "fix_evidence": "run.log: EDL=3 and EDL=6 controls clean vs EDL=2/1/4 faulting; fix.diff in pack."
}