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

Reallocating diag reset tears down all interrupt handlers and never re-establishes them β€” controller permanently dead (hang/panic DoS)

Summary

On a facts-changing diag reset, mpr_iocfacts_free() performs a real interrupt teardown via mpr_pci_free_interrupts() (bus_teardown_intr + bus_release_resource for every queue) and kfrees sc->queues. The re-establishment mpr_pci_setup_interrupts() is gated 'if (attaching && ...)' and mpr_reinit() calls mpr_iocfacts_allocate(sc, FALSE) - so handlers are never re-armed. mpr_reinit() then unmasks interrupts: the firmware signals into a torn-down handler set forever. Firmware whose IOC Facts differ across a diag reset - root FW-download + reset, or a controller fault returning different facts. After that, all completions are lost: CAM freezes, sync waiters with timeout=0 sleep forever, repeated reinit timeouts hit panic. Local DoS of the storage subsystem/host. One-line gate fix (attaching || reallocating).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3814 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.4 KB ↓ raw
VERDICT.md β€” 2.4 KB ↓ raw
probe.log β€” 479 B view raw
fix.diff β€” 776 B view raw
manifest.json β€” 942 B view raw
verdict.json β€” 2.0 KB view raw

DF-3814 β€” reallocating diag reset tears down all interrupts and never re-establishes them β†’ controller permanently dead (hang/panic DoS)

What

On a diag reset whose IOC Facts changed (the reallocating path), mpr_iocfacts_free() (called from mpr_iocfacts_allocate(), sys/dev/raid/mpr/mpr.c:630-634) ends with:

mpr_pci_free_interrupts(sc);              /* mpr.c:801 */
kfree(sc->queues, M_MPR);                 /* mpr.c:802 */

mpr_pci_free_interrupts() (sys/dev/raid/mpr/mpr_pci.c:331-349) does a real teardown: bus_teardown_intr() + bus_release_resource(SYS_RES_IRQ) for every queue. The queues array is then rebuilt by mpr_alloc_queues() (mpr.c:650, M_ZERO β†’ q->irq == NULL), but the re-establishment is gated on attach only:

/* mpr.c:714-718 */
/*
 * XXX If the number of MSI-X vectors changes during re-init, this
 * won't see it and adjust.
 */
if (attaching && (error = mpr_pci_setup_interrupts(sc)) != 0) {

mpr_reinit() calls mpr_iocfacts_allocate(sc, FALSE) (mpr.c:861) β€” attaching is FALSE, so mpr_pci_setup_interrupts() is never called again. mpr_reinit() then even unmasks interrupts (mpr.c:879 mpr_unmask_intr): the firmware now raises interrupts that no longer have a handler. Every subsequent command completion is lost; synchronous waiters time out and re-enter mpr_reinit() (which cannot fix it), CAM freezes, and mpr_wait_command(..., 0, CAN_SLEEP) users (config-page reads, mpr.c:3740) sleep forever (timeout*hz == 0 β‡’ infinite lksleep). Realistic end states: storage subsystem wedge, boot-time hang, or panic via the reinit failure panics (mpr.c:846, mpr.c:862).

The code comment ("XXX … won't see it and adjust") shows the authors were aware of the vector-count aspect but missed that the entire handler set is dropped.

Trigger

Firmware whose IOC Facts differ across a diag reset β€” most commonly reached by a firmware download via the management ioctl (/dev/mprX) followed by the reset, or by a controller fault after which the IOC comes back with different facts (RequestCredit / MaxTargets / IOCCapabilities / MaxVolumes …). The realloc condition itself is mpr.c:551-576.

Not runnable here

Audit guest has no SAS3 controller (see probe.log): driver compiled in (142 pci/mpr in kldstat -v), no /dev/mpr0. Source-level verification only; fix validation not possible.

Env

  • DragonFly dfbsd 6.5-DEVELOPMENT #0 Thu Jul 2 06:02:54 UTC 2026 (X86_64_GENERIC), x86_64
VERDICT.md
↓ download raw

DF-3814 β€” VERDICT

status: inconclusive (runtime) β€” code path is unconditional and source-certain; the guest has no SAS3 HBA to boot a controller through a facts-changing reset.

Source trace (why it is certain)

  1. mpr_reinit() (mpr.c:815-902) holds mpr_lock, masks interrupts (mpr.c:842), diag-resets, then calls mpr_iocfacts_allocate(sc, FALSE) at mpr.c:861.
  2. mpr_iocfacts_allocate(): facts-changed test mpr.c:551-576 β†’ reallocating = TRUE β†’ mpr_iocfacts_free(sc) at mpr.c:631.
  3. mpr_iocfacts_free() mpr.c:801 calls mpr_pci_free_interrupts(sc) β€” real teardown per queue: bus_teardown_intr + bus_release_resource (mpr_pci.c:340-348). Then sc->queues is kfreed (mpr.c:802-803).
  4. Reallocation loop mpr.c:643-653 rebuilds everything, including a fresh zeroed sc->queues (mpr_alloc_queues, mpr.c:1269-1270).
  5. mpr_pci_setup_interrupts() runs only under if (attaching && …) (mpr.c:718). During reinit attaching == FALSE β‡’ never re-armed. The XXX comment above it (mpr.c:714-717) acknowledges partial awareness.
  6. mpr_reinit() proceeds to mpr_unmask_intr() (mpr.c:879): firmware interrupts with no handler installed.

Consequences after that point: no completion interrupts ever again; polling exists only inside mpr_wait_command's no-lock branch and mpr_request_polled; mpr_read_config_page passes timeout=0 (mpr.c:3740) β‡’ lksleep(..., 0) = sleep forever β‡’ permanent CAM/thread hang; repeated sync-command timeouts re-enter mpr_reinit and eventually hit panic("%s hard reset failed …") (mpr.c:846) or the IOC-facts panic (mpr.c:862).

Runtime

Not testable on this guest: kldstat -v | grep -w mpr β†’ 142 pci/mpr (driver present), but no LSI PCI device and no /dev/mpr* (probe.log). Neither baseline nor patched behavior can be observed β†’ inconclusive / not_testable for the fix.

Fix

fix.diff: also run mpr_pci_setup_interrupts() when reallocating (the IRQ resources were released with the same sc->irq_rid, so re-allocation by rid is symmetric with attach).

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

No SAS3 hardware on the guest: neither baseline nor single-fix kernel can exercise the reinit interrupt path; fix.diff authored against read-only sys/, not applied.

fix.diff; probe.log.
↓ fix.diffper-fix-DF-3814

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (6-step trace); probe.log (driver present, device absent); fix.diff (gate setup on attaching||reallocating).

PoC changes

No runnable PoC exists without hardware; probe-based verification only.

Verified recommended fix

In mpr_iocfacts_allocate(), call mpr_pci_setup_interrupts() when (attaching || reallocating).

Verdict

Source-certain: a reallocating diag reset (facts changed, mpr.c:551-576) calls mpr_iocfacts_free() (mpr.c:631) which tears down every interrupt handler via mpr_pci_free_interrupts() (mpr.c:801 -> mpr_pci.c:340-348), but re-establishment mpr_pci_setup_interrupts() is gated on 'attaching' (mpr.c:718) and mpr_reinit() runs with attaching=FALSE (mpr.c:861) β€” so after such a reset the controller has no completion interrupts for the rest of the system lifetime; mpr_unmask_intr() (mpr.c:879) then enables signaling into a torn-down handler set. Result: hangs (config-page waiters sleep forever via timeout=0 at mpr.c:3740) or panics (mpr.c:846/862) β€” a controller/host DoS. Runtime inconclusive: guest has the driver compiled in ('142 pci/mpr') but no SAS3 controller (probe.log).