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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3800 Β· 7 files
FileTypeDescriptionSize
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.md for the full source chain.
  • run.sh β€” records the guest hardware gate (no SAS3 HBA; mpr unattached).
  • fix.diff β€” realloc to current facts + update sassc->maxtargets + clamp.
  • verdict.json β€” machine verdict (not_reproduced: hardware absent).
VERDICT.md
↓ download raw

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)

  1. 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:760 sassc->maxtargets = sc->facts->MaxTargets + sc->facts->MaxVolumes; - sys/dev/raid/mpr/mpr_sas.c:761-762 kmalloc(sizeof(struct mprsas_target) * sassc->maxtargets)

  2. After any Diag Reset (mpr_reinit β†’ mpr_iocfacts_allocate(sc, attaching=0)), new IOC facts are read into sc->facts; if any size-determining field changed, reallocating = TRUE (sys/dev/raid/mpr/mpr.c:551-576).

  3. The target array is then re-allocated to the OLD (pre-reset) size: - sys/dev/raid/mpr/mpr.c:630-634 calls mprsas_realloc_targets(sc, saved_facts.MaxTargets + saved_facts.MaxVolumes) where saved_facts is 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 updates sassc->maxtargets (nor takes the current facts).

  4. Meanwhile the mapping layer is rebuilt from the NEW facts: - sys/dev/raid/mpr/mpr_mapping.c:2564-2565 sc->max_volumes = sc->facts->MaxVolumes; sc->max_devices = sc->facts->MaxTargets + sc->max_volumes; - sys/dev/raid/mpr/mpr_mapping.c:2141-2146 mapping_table allocated with max_devices entries.

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-2674 mpr_mapping_get_tid() returns mt_entry->id for any entry in [0, sc->max_devices) β€” no clamp to sassc->maxtargets.
  • sys/dev/raid/mpr/mpr_sas_lsi.c:908 targ = &sassc->targets[id];
  • sys/dev/raid/mpr/mpr_sas_lsi.c:925-965 writes ~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:

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 match mpr_attach/mpr_attach_sas never run, no SIM is registered, sc->sassc == NULL forever.
  • Therefore no runtime path into mprsas_realloc_targets, mprsas_add_device, or the completion handlers exists on this guest. See env.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_testable
baseline no→ patch + rebuild →patched clean

fix.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
↓ fix.diffper-fix-DF-3800

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'.