RAID reply-handler deregistration is not refcounted across mpt(4) controllers: detaching one controller strands the other's RAID actions (request leak β unkillable hang)
Summary
mpt_reply_handlers is global and registration is idempotent across controllers, but mpt_raid_detach deregisters the shared slot unconditionally. With >=2 controllers, the first detach clears the slot; the survivor's RAID-action replies then hit the default handler which never completes/frees the request - requests pin on the pending list, drain the 512-entry pool, and get_request blocks in non-interruptible sleep, wedging the RAID thread and sysctl writers. Refcount-the-registration fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4214 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.3 KB | β raw | |
| VERDICT.md | β | 1.7 KB | β raw | |
| env.txt | β | 992 B | view raw | |
| fix.diff | β | 1.3 KB | view raw | |
| manifest.json | β | 756 B | view raw | |
| verdict.json | β | 2.0 KB | view raw |
DF-4214 β RAID reply handler deregistration is not refcounted: detaching one mpt(4) controller breaks the other's RAID completion path
- File:
sys/dev/disk/mpt/mpt_raid.c(lifetime; interacts withsys/dev/disk/mpt/mpt.c:402-478) - Lines: 135 (static id), 291β297 (attach register), 338β340 (detach deregister)
- Class: shared-resource lifetime bug (CWE-666/CWE-404) β request leak / unkillable hang on surviving controller
- Preconditions: β₯2 mpt(4) RAID controllers; one detached (hot-removal/kldunload path) while the other runs RAID activity
Root cause
mpt_reply_handlers[] is a global table shared by all mpt controllers
(mpt.c:119). Registration is deliberately idempotent: a second controller
registering the same function gets the same slot back
(mpt.c:417-426: "If the same handler is registered multiple times, don't
error out"). Deregistration, however, is unconditional and un-refcounted
(mpt.c:466-471) β and mpt_raid_detach() calls it for the shared
raid_handler_id (mpt_raid.c:338-340, raid_handler_id is a file-static
at mpt_raid.c:135).
So: ctrl1 attach β slot X = mpt_raid_reply_handler; ctrl2 attach β reuses X
(no count); ctrl1 detach β slot X cleared; ctrl2 keeps issuing RAID
actions with MsgContext = index | raid_handler_id (mpt_raid.c:616).
Consequence on the surviving controller
Late replies for ctrl2's RAID actions now dispatch to
mpt_default_reply_handler (mpt.c:480-495), which only prints and returns
β it never completes, dequeues, or frees the request. Every RAID action on
ctrl2 then:
- times out after the 2 s busy-poll (
mpt_wait_req, sleep_ok=FALSE) and is either leaked forever onrequest_pending_list(mpt_verify_mwce / mpt_verify_resync_rate paths), or freed-while-pending (the DF-4211 bug), - drains the 512-entry request pool until
mpt_get_request(sleep_ok=TRUE)blocks forever in non-interruptiblempt_sleepβ wedging the RAID thread and any thread touching thevol_member_wce/vol_resync_ratesysctls (unkillable local DoS from what should be a scoped device event).
Verification status
untested β hardware-gated (needs two MPT Fusion HBAs; guest has none,
env.txt). Source-certain.
Fix
fix.diff: reference-count the personality's registration
(raid_handler_refs) and only deregister when the last controller detaches.
VERDICT β DF-4214
status: untested (hardware-gated) Β· impact: request leak β unkillable hang on surviving controller (dos) Β· confidence: certain (source)
Not runnable here: requires β₯2 LSI MPT Fusion HBAs (env.txt shows none).
Mechanism proven from source:
- Global handler table + idempotent register:
mpt.c:119,mpt.c:417-426. - Un-refcounted deregister:
mpt.c:466-471. mpt_raid.c:135staticraid_handler_id; attach register atmpt_raid.c:291-293; detach deregister atmpt_raid.c:338-340β with two controllers attached, the first detach clears the shared slot.- Surviving controller's RAID actions encode that handler id
(
mpt_raid.c:616); after the slot is cleared,mpt_intrdispatches tompt_default_reply_handler(mpt.c:809-821βmpt.c:480-495) which never completes/frees the request β ETIMEDOUT after the 2 s busy poll (mpt.c:1356-1361) β leaked requests pinned onrequest_pending_list(mpt_verify_mwcempt_raid.c:977-981leaves them), pool exhaustion, andmpt_get_requestsleeping forever (mpt.c:1273-1276) β wedging the RAID thread and sysctl writers in non-interruptible sleep.
Severity Low (unusual configuration: two RAID cards + detaching one). Fix
diff refcounts the registration; not runtime-validated (no hardware) β
fix_status: not_testable.
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_testablediff authored and git-apply-verified against the read-only sys/ tree; runtime fix validation impossible without the triggering hardware on the QEMU guest
findings/poc/DF-4214/fix.diff (git apply --check passed)
Confirmed kernel references
Detail
Evidence (decisive lines)
findings/poc/DF-4214/VERDICT.md, findings/poc/DF-4214/env.txt (guest pciconf: no triggering hardware), findings/poc/DF-4214/fix.diff
PoC changes
no runnable PoC: hardware-gated path absent from the audit guest (env.txt)
Verified recommended fix
refcount the shared registration (raid_handler_refs) and deregister only on last detach
Verdict
mpt_reply_handlers[] is global and registration idempotent across controllers (mpt.c:417-426) but mpt_raid_detach deregisters unconditionally (mpt_raid.c:338-340). With >=2 mpt RAID controllers, detaching one clears the shared handler slot; the survivor's RAID-action replies then hit mpt_default_reply_handler (mpt.c:480-495) which never completes or frees the request -> requests pinned on request_pending_list, 2s busy-poll timeouts, pool exhaustion and non-interruptible mpt_get_request sleeps wedging the RAID thread and sysctl writers.
No comments yet.