mpt_refresh_raid_vol frees a request on ETIMEDOUT that is still queued to the IOC and on request_pending_list β request-pool double-link / cross-command use-after-free
Summary
On progress-indicator timeout the code frees a request the chip still owns (REQ_STATE_QUEUED, linked on request_pending_list). In production kernels (KASSERT INVARIANTS-only) the request returns to the free list while in flight; handed to a new command (double pending-list link), and when the IOC finally completes the RAID action its reply context dispatches the handler on the new owner's request, completing and freeing it - UAF/double-free. Every sibling helper correctly leaves the request for the late-reply free path - the contrast that convicts. Trigger: IOC answering the progress indicator slower than the 2s poll during volume resync. Don't-free-on-timeout fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4211 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.9 KB | β raw | |
| VERDICT.md | β | 3.7 KB | β raw | |
| env.txt | β | 992 B | view raw | |
| fix.diff | β | 516 B | view raw | |
| manifest.json | β | 816 B | view raw | |
| verdict.json | β | 2.0 KB | view raw |
DF-4211 β mpt_refresh_raid_vol frees an in-flight RAID request on ETIMEDOUT
- File:
sys/dev/disk/mpt/mpt_raid.c - Lines: 1327β1334
- Class: use-after-free / request-pool corruption (CWE-415/CWE-672)
- Bucket: memcorrupt
- Hardware gate: LSI MPT Fusion RAID controller (mpt(4) RAID personality)
What happens
mpt_refresh_raid_vol() issues MPI_RAID_ACTION_INDICATOR_STRUCT synchronously
(mpt_issue_raid_req(..., /*wait*/TRUE) β mpt_wait_req(..., sleep_ok=FALSE,
time_ms=2000), sys/dev/disk/mpt/mpt_raid.c:1327-1328,
sys/dev/disk/mpt/mpt.c:1316-1363). On timeout the request is still
- queued to the IOC (
REQ_STATE_QUEUED, frame owned by the chip), and - linked on
mpt->request_pending_list(inserted bympt_send_cmd,mpt.c:1298; nothing removes it on the timeout path).
yet the code immediately does mpt_free_request(mpt, req) (mpt_raid.c:1332).
Every sibling helper in the same file handles this correctly by leaving the
request for the late-completion path (mpt_verify_mwce 977β981,
mpt_verify_resync_rate 1030β1034 and 1066β1070 all return without
freeing; the late reply is freed by mpt_raid_reply_handler via
free_req=TRUE, mpt_raid.c:543-547, exactly like
mpt_config_reply_handler's documented "late completion" path,
mpt.c:520-525).
Corruption chain (production kernel, no INVARIANTS)
mpt_free_requestinserts the request onrequest_free_listwhile it is still onrequest_pending_list(theKASSERT(mpt_req_on_pending_list == 0)guard,mpt.c:1213, only exists in INVARIANTS builds βsys/sys/systm.h:95-119).mpt_get_requesthands the same request to a new owner (e.g. a CAM SCSI I/O frommpt_cam.c);mpt_send_cmdinserts it intorequest_pending_lista second time β the TAILQ now contains the same node twice.- The IOC eventually completes the original RAID action (slow controller
during resync β the exact condition that produced the timeout). The
reply's
MsgContextisreq->index | raid_handler_id(mpt_raid.c:616), sompt_intrdispatchesmpt_raid_reply_handleron the request that now belongs to the new owner: itTAILQ_REMOVEs it, marks it DONE and frees it β the new owner completes/frees a request it no longer owns β double-free / cross-command use-after-free with attacker-influenced (frame) contents.
Trigger is firmware/hardware timing (IOC > 2 s to answer a RAID action while
a volume resync is in progress β this code path only runs when
MPI_RAIDVOL0_STATUS_FLAG_RESYNC_IN_PROGRESS is set). Same trust boundary as
the known fw-fed findings DF-1449/DF-2484/DF-2485 (excluded here β this is a
different bug class: request lifecycle, not index validation).
Verification status
untested β hardware-gated. The QEMU audit guest has no LSI MPT Fusion
HBA (pciconf -l shows only virtio/bochs devices, see env.txt); mpt.ko
never attaches, so the RAID personality (and this path) cannot execute.
Source-level certainty: the timeout/free/late-reply protocol is fully
traceable in-tree (see VERDICT.md). No uid=0 chain is developed because the
primitive cannot be exercised without the controller.
Reproduce (on hardware)
- System with mpt(4) RAID volume, start a resync
(
mpt_raid.c:1317requires RESYNC_IN_PROGRESS for the indicator fetch). - Saturate/wedge the IOC so the
MPI_RAID_ACTION_INDICATOR_STRUCTreply takes > 2 s (or drop it and let it complete late). - Observe
mpt_refresh_raid_vol: Progress Indicator fetch timeouton console, then either the INVARIANTS panic (mpt_free_request: req %p:%u func %x on pending list) or, on a production kernel, later list-corruption panics fromrequest_pending_list/request_free_listdouble-linking.
Fix
fix.diff β drop the mpt_free_request() on the ETIMEDOUT path so the
request is reclaimed by the late reply, matching every sibling helper.
VERDICT β DF-4211
status: untested (hardware-gated) Β· impact: memory corruption (panic-class, uid0 theoretical) Β· confidence: certain (source)
Reproduced?
No. The single QEMU guest available to this run exposes no LSI MPT Fusion
controller (env.txt: pciconf -l lists only hostb/isab/atapci/virtio/bochs
VGA), so mpt.ko never attaches and mpt_raid_attach/mpt_refresh_raid_vol
cannot run. This is a device-present code path: the entire RAID personality
only loads when mpt_raid_probe sees ioc_page2->MaxPhysDisks != 0
(sys/dev/disk/mpt/mpt_raid.c:264-272), which requires a real IOC. Per the
honest-status table this is "guest reachable but the target device/feature is
absent" β recorded as untested with the device-absence evidence in env.txt.
Why the bug is certain anyway (line-level trace)
mpt_issue_raid_req(..., wait=TRUE)βmpt_wait_req(mpt, req, REQ_STATE_DONE, REQ_STATE_DONE, sleep_ok=FALSE, 2000)(mpt_raid.c:1327-1328,mpt.c:621-623).- Timeout path in
mpt_wait_req(mpt.c:1344-1361): busy-pollsDELAY(500)+mpt_intr()until--timeout == 0β breaks, setsREQ_STATE_TIMEDOUT, returnsETIMEDOUT. The request is never removed fromrequest_pending_listand staysREQ_STATE_QUEUEDβ the chip still owns the frame. mpt_refresh_raid_volthen callsmpt_free_request(mpt, req)(mpt_raid.c:1332).mpt_free_request(mpt.c:1194-1251) puts the request onrequest_free_list. Its pending-list guard isKASSERT(mpt.c:1213-1215) β compiled out without INVARIANTS (sys/sys/systm.h:95-119shows KASSERT is a no-op unless INVARIANTS).- From here either
- the request is re-allocated (
mpt_get_request,mpt.c:1255-1279) and re-queued (mpt_send_cmdTAILQ_INSERT_HEAD,mpt.c:1298) while still linked once on the pending list β double-link corruption; or - the late RAID-action reply arrives: context isreq->index | raid_handler_id(mpt_raid.c:616), sompt_intr(mpt.c:809-821) dispatchesmpt_raid_reply_handler(mpt_raid.c:521-550) on a request now owned by an unrelated user β it completes and frees the new owner's request (use-after-free / double-free). - Sibling helpers prove intent:
mpt_verify_mwce(mpt_raid.c:977-981) andmpt_verify_resync_rate(mpt_raid.c:1030-1034,1066-1070) allreturnonETIMEDOUTwithout freeing; the late reply frees viafree_req=TRUE(mpt_raid.c:545-547), exactly the protocol documented inmpt_config_reply_handler("Whew- we can free this request (late completion)",mpt.c:520-525).mpt_refresh_raid_volis the one deviant.
Exploit chain
Not developed: no device. With hardware: primitive = one request_t (double-linked on two TAILQs / completed-cross-command). The request frame is IOC-DMA memory; control over contents is indirect. Realistic ceiling on this hardware-fault trigger is a reliable panic; a uid0 chain would require the attacker to control the IOC (malicious firmware / PCIe device), in which case they already own the bus. Severity Medium is the right call.
Fix
Remove the free on the timeout path (fix.diff, mirrors the sibling
helpers; late replies are reclaimed by mpt_raid_reply_handler). Fix not
runtime-validated for the same hardware reason β 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_testableConfirmed kernel references
Detail
Evidence (decisive lines)
findings/poc/DF-4211/VERDICT.md (line-level trace), findings/poc/DF-4211/env.txt (guest pciconf: no MPT HBA), findings/poc/DF-4211/fix.diff
PoC changes
no runnable PoC: path requires LSI MPT Fusion RAID hardware absent from the audit guest (env.txt)
Verified recommended fix
do not free the request on ETIMEDOUT; let the late reply reclaim it via mpt_raid_reply_handler
Verdict
mpt_refresh_raid_vol frees a request on ETIMEDOUT that is still queued to the IOC and linked on request_pending_list (mpt_raid.c:1332). Every sibling helper leaves it for the late-reply path; here the request returns to the free list while in flight, so it is handed to a new command and the late RAID-action reply (context = index|raid_handler_id) completes/frees the new owner's request -> double-link corruption / UAF. Hardware-gated: guest has no MPT HBA (env.txt), so untested at runtime; the protocol violation itself is line-certain.
No comments yet.