mprsas_realloc_targets resizes the target array to stale pre-reset IOC facts and never updates sassc->maxtargets while the mapping layer is rebuilt from new facts: heap OOB write past sassc->targets[] (and past mapping_table[] in the opposite direction)
Summary
After a Diag Reset that changes IOC facts, mpr_iocfacts_allocate calls mprsas_realloc_targets(sc, saved_facts.MaxTargets + saved_facts.MaxVolumes) - the OLD size - and the helper never updates sassc->maxtargets. Simultaneously the mapping layer is rebuilt from the NEW facts. Direction A (new>old): discovery writes ~20 firmware-influenced fields at &sassc->targets[id] for mapping-derived id in [old_maxtargets, max_devices_new) - a large heap OOB write. Direction B (new<old): CAM targets surviving AC_BUS_RESET keep ids < old maxtargets and write sc->mapping_table[target_id].TLR_bits OOB plus OOB reads. The KASSERT guards are INVARIANTS-only and pass anyway in direction B. Firmware controls the facts delta (malicious IOC or on-line fw upgrade changing MaxTargets/MaxVolumes across a diag reset; diag reset reachable via ordinary error recovery). Hardware-gated on this guest (no SAS3 HBA - proven); evidence pack + fix.diff (realloc to current facts + set maxtargets + clamp mapping ids).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3800 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 446 B | β raw | |
| VERDICT.md | β | 5.4 KB | β raw | |
| run.sh | β | 111 B | view raw | |
| env.txt | β | 855 B | view raw | |
| fix.diff | β | 2.7 KB | view raw | |
| verdict.json | β | 2.8 KB | view raw | |
| manifest.json | β | 699 B | view raw |
DF-3800 evidence pack
Stale IOC-facts target-array reallocation in mprsas_realloc_targets
(sys/dev/raid/mpr/mpr_sas.c:3911) β heap OOB write after Diag Reset.
- Read
VERDICT.mdfor the full source chain. run.shβ records the guest hardware gate (no SAS3 HBA; mpr unattached).fix.diffβ realloc to current facts + updatesassc->maxtargets+ clamp.verdict.jsonβ machine verdict (not_reproduced: hardware absent).
DF-3800 β Stale IOC-facts target-array reallocation β heap OOB write after Diag Reset
- File:
sys/dev/raid/mpr/mpr_sas.c - Class: kernel heap OOB write (firmware-gated)
- Severity: Medium / Confidence: likely
- Status on guest: not_reproduced β no SAS3 HBA in the QEMU guest (driver module present, no PCI device, SIM never attaches)
What breaks (source chain)
-
At attach,
mprsas_attach_sas()sizes the target array from the then-current IOC facts and caches the count: -sys/dev/raid/mpr/mpr_sas.c:760sassc->maxtargets = sc->facts->MaxTargets + sc->facts->MaxVolumes;-sys/dev/raid/mpr/mpr_sas.c:761-762kmalloc(sizeof(struct mprsas_target) * sassc->maxtargets) -
After any Diag Reset (
mpr_reinitβmpr_iocfacts_allocate(sc, attaching=0)), new IOC facts are read intosc->facts; if any size-determining field changed,reallocating = TRUE(sys/dev/raid/mpr/mpr.c:551-576). -
The target array is then re-allocated to the OLD (pre-reset) size: -
sys/dev/raid/mpr/mpr.c:630-634callsmprsas_realloc_targets(sc, saved_facts.MaxTargets + saved_facts.MaxVolumes)wheresaved_factsis the pre-reset copy (sys/dev/raid/mpr/mpr.c:473-475). -sys/dev/raid/mpr/mpr_sas.c:3911-3937(mprsas_realloc_targets) frees the old array and kmallocs the new one with the passed-in count, and never updatessassc->maxtargets(nor takes the current facts). -
Meanwhile the mapping layer is rebuilt from the NEW facts: -
sys/dev/raid/mpr/mpr_mapping.c:2564-2565sc->max_volumes = sc->facts->MaxVolumes; sc->max_devices = sc->facts->MaxTargets + sc->max_volumes;-sys/dev/raid/mpr/mpr_mapping.c:2141-2146mapping_table allocated withmax_devicesentries.
Direction A: NEW facts > OLD facts β OOB write past sassc->targets[]
Discovery re-runs after the reset (mprsas_startup β port enable). For each device,
mprsas_add_device() computes the target id from the (new-sized) mapping table:
sys/dev/raid/mpr/mpr_mapping.c:2662-2674mpr_mapping_get_tid()returnsmt_entry->idfor any entry in[0, sc->max_devices)β no clamp tosassc->maxtargets.sys/dev/raid/mpr/mpr_sas_lsi.c:908targ = &sassc->targets[id];sys/dev/raid/mpr/mpr_sas_lsi.c:925-965writes ~20 fields (devinfo, devname(2Γu32), encl_handle, encl_slot, encl_level, connector_name[4], handle, parent_handle, sasaddr, parent_sasaddr, parent_devinfo, tid, linkrate, flags, scsi_req_desc_type, encl_level_valid, TAILQ_INITΓ2, SLIST_INIT β¦).
With id β₯ sassc->maxtargets (old size) this is a heap OOB write of
id * sizeof(struct mprsas_target)-offset, firmware-influenced content,
magnitude up to (MaxTargets_new β MaxTargets_old) * sizeof(struct mprsas_target).
Same for RAID volumes: sys/dev/raid/mpr/mpr_sas_lsi.c:1424/1433
(mpr_mapping_get_raid_tid β &sassc->targets[id]).
Direction B: NEW facts < OLD facts β OOB write past mapping_table[]
CAM targets enumerated before the reset (ids < maxtargets_old, they survive
AC_BUS_RESET announced by mprsas_handle_reinit) keep issuing I/O:
sys/dev/raid/mpr/mpr_sas.c:2706-2707sc->mapping_table[target_id].TLR_bits = (u8)MPI2_SCSIIO_CONTROL_NO_TLR;β 1-byte OOB write past the shrunk mapping_table.- OOB reads at
sys/dev/raid/mpr/mpr_sas.c:2092, 2764-2768, 2789(sc->mapping_table[target_id].TLR_bits,.device_info).
The KASSERT(target_id < sassc->maxtargets) guards in this file
(mpr_sas.c:1072, 1929, 3348, 3495, 3616, 3748) are INVARIANTS-only
(sys/sys/systm.h:94-118) β compiled out in production kernels β and in
Direction B they would pass anyway (target_id < maxtargets_old).
Threat model
The size delta is firmware-reported. A malicious or field-updated IOC image that
changes MaxTargets/MaxVolumes across a Diag Reset (diag reset itself is
reachable through ordinary error recovery: mprsas_tm_timeout β mpr_reinit,
sys/dev/raid/mpr/mpr_sas.c:1255-1270) triggers the mismatch. On-line firmware
upgrade is a supported operation for these HBAs, making even benign deltas
plausible; a hostile IOC trivially controls both sides.
Guest verification attempt (why not_reproduced)
- Guest kernel: DragonFly 6.5-DEVELOPMENT (#0 Thu Jul 2 06:02:54 UTC 2026) X86_64_GENERIC.
pciconf -l: no LSI/Avago/Broadcom SAS3 device β QEMU i440fx + PIIX ATA + virtio only.kldload mprβ "module pci/mpr already exists" (driver compiled into the kernel), but with no PCI probe matchmpr_attach/mpr_attach_sasnever run, no SIM is registered,sc->sassc == NULLforever.- Therefore no runtime path into
mprsas_realloc_targets,mprsas_add_device, or the completion handlers exists on this guest. Seeenv.txt.
Honest classification: not_reproduced (feature absent), impact none on this
guest; the defect itself is source-provable with certainty.
Fix
mprsas_realloc_targets must (a) be given the current facts size, (b) update
sassc->maxtargets, and (c) the caller must pass current facts. See fix.diff
(also adds the missing sassc->maxtargets update and clamps mapping lookups to
sassc->maxtargets as defense-in-depth).
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_testablefix.diff authored from line-accurate source analysis; cannot be kernel-built-and-exercised on this guest because the mpr SIM never attaches without SAS3 hardware.
findings/poc/DF-3800/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (full source chain with path:line), env.txt (guest hardware gate), fix.diff (realloc to current facts + maxtargets update + clamp)
PoC changes
No runnable PoC exists for a firmware-facts-delta trigger; run.sh records the hardware-gate proof instead.
Verified recommended fix
Realloc targets to current facts (sc->facts->MaxTargets+MaxVolumes) and update sassc->maxtargets in mprsas_realloc_targets; clamp mapping ids to sassc->maxtargets in mprsas_add_device.
Verdict
Source-level heap OOB write: after a Diag Reset that changes IOC facts, mprsas_realloc_targets (mpr_sas.c:3911) re-allocates sassc->targets to the PRE-reset size (caller mpr.c:632 passes saved_facts) and never updates sassc->maxtargets, while the mapping layer is rebuilt from the NEW facts (mpr_mapping.c:2564-2565). Discovery then writes ~20 firmware-influenced fields at &sassc->targets[id] (mpr_sas_lsi.c:908,925-965) with mapping-derived id bounded only by the new max_devices -> OOB write past the array. In the opposite direction (facts shrank), surviving CAM targets write sc->mapping_table[target_id].TLR_bits (mpr_sas.c:2706) past the shrunk mapping table. Not reproduced on the QEMU guest: mpr driver is present (pci/mpr registered) but the guest has no SAS3 PCI device, so mpr_attach_sas never runs and no runtime path exists; verified via pciconf/kldstat/camcontrol (env.txt). Defect is certain from source; the trigger requires a facts delta across a diag reset (malicious or online-upgraded firmware), hence confidence 'likely'.
No comments yet.