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)
PoC verification
Evidence pack
findings/poc/DF-3814 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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
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)
mpr_reinit()(mpr.c:815-902) holdsmpr_lock, masks interrupts (mpr.c:842), diag-resets, then callsmpr_iocfacts_allocate(sc, FALSE)at mpr.c:861.mpr_iocfacts_allocate(): facts-changed test mpr.c:551-576 βreallocating = TRUEβmpr_iocfacts_free(sc)at mpr.c:631.mpr_iocfacts_free()mpr.c:801 callsmpr_pci_free_interrupts(sc)β real teardown per queue:bus_teardown_intr+bus_release_resource(mpr_pci.c:340-348). Thensc->queuesis kfreed (mpr.c:802-803).- Reallocation loop mpr.c:643-653 rebuilds everything, including a fresh
zeroed
sc->queues(mpr_alloc_queues, mpr.c:1269-1270). mpr_pci_setup_interrupts()runs only underif (attaching && β¦)(mpr.c:718). During reinitattaching == FALSEβ never re-armed. The XXX comment above it (mpr.c:714-717) acknowledges partial awareness.mpr_reinit()proceeds tompr_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_testableNo 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.
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).
No comments yet.