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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4214 Β· 6 files
FileTypeDescriptionSize
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 with sys/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 on request_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-interruptible mpt_sleep β€” wedging the RAID thread and any thread touching the vol_member_wce/vol_resync_rate sysctls (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.md
↓ download raw

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:135 static raid_handler_id; attach register at mpt_raid.c:291-293; detach deregister at mpt_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_intr dispatches to mpt_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 on request_pending_list (mpt_verify_mwce mpt_raid.c:977-981 leaves them), pool exhaustion, and mpt_get_request sleeping 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_testable
baseline no→ patch + rebuild →patched clean

diff 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)
↓ fix.diffper-fix-DF-4214

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.