dev.mpr.N.dump_reqs sysctl: unlocked walk of live command/chain lists (UAF read) plus fixed 128-byte reads past device-sized frames
Summary
mpr_dump_reqs() reads cm->cm_state and walks cm->cm_chain_list with no lock ('Best effort, no locking') while every other consumer mutates those structures under sc->mpr_lock - a concurrent completion (or the realloc path that kfrees sc->chains) yields use-after-free reads/garbage into the sysctl buffer or a panic. It also copies fixed 128 bytes from request/chain frames whose sizes are device-reported (can be < 128) - OOB read past the last frame of the req_frames/chain_frames DMA allocations. Privileged-only racing the storage completion path: freed/adjacent DMA-heap contents into a sysctl reply; panic under race. Lock + clamp fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3816 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.5 KB | β raw | |
| VERDICT.md | β | 2.2 KB | β raw | |
| probe.log | β | 479 B | view raw | |
| fix.diff | β | 1.1 KB | view raw | |
| manifest.json | β | 876 B | view raw | |
| verdict.json | β | 1.7 KB | view raw |
DF-3816 β mpr_dump_reqs sysctl: unlocked walk of live command/chain lists (UAF read) and fixed 128-byte reads past frame allocations
What
mpr_dump_reqs() (sys/dev/raid/mpr/mpr.c:1875-1927) is the
dev.mpr.N.dump_reqs sysctl (CTLFLAG_SKIP, so hidden but callable), gated by
caps_priv_check_self(SYSCAP_NODRIVER) β privileged-only.
/* Best effort, no locking */ /* mpr.c:1904 */
for (i = smid; i < numreqs; i++) {
cm = &sc->commands[i];
if (cm->cm_state != state)
continue;
...
TAILQ_FOREACH_MUTABLE(chain, &cm->cm_chain_list, chain_link, chain1)
hdr.numframes++;
sbuf_bcat(sb, cm->cm_req, 128); /* mpr.c:1918 */
TAILQ_FOREACH_MUTABLE(chain, &cm->cm_chain_list, chain_link, chain1)
sbuf_bcat(sb, chain->chain, 128); /* mpr.c:1921 */
}
Two defects:
- Unlocked concurrent access: the command state machine,
cm_chain_listmutation (chains are attached/detached undersc->mpr_lockby the completion path, mprvar.h:592-595) and the command completion are all lock-protected everywhere else; this handler reads them with no lock. A command completing concurrently on the interrupt path frees its chains intosc->chain_listwhile we walkcm->cm_chain_listβ TAILQ traversal over freed elements (use-after-free reads) or over a command that flips state mid-dump β stale/garbagehdrand leaked heap contents into the sysctl return buffer (privileged info leak; contents are attacker-shaped only insofar as freed slab data is). Panic possible via corrupted TAILQ pointers. - Fixed 128-byte copies:
sbuf_bcat(sb, cm->cm_req, 128)assumes request frames are β₯128 bytes, but frame size issc->facts->IOCRequestFrameSize * 4(mpr.c:387) β device-reported. For the last frame (i == num_reqs-1) a frame size < 128 reads past the end of thereq_framesDMA allocation; same forchain->chain/sc->chain_frame_size(= IOCMaxChainSegmentSize*16, can legitimately be 32/48/β¦).
Threat
Privileged-only trigger (root-equivalent syscap) racing the storage completion path β Low. Value: hardening + correctness for the debugging facility.
Not runnable here
No SAS3 controller on the audit guest β dev.mpr.N tree doesn't even
instantiate (probe.log).
Fix
Take mpr_lock around the walk and clamp each copy to
min(reqframesz,128) / min(chain_frame_size,128) β see fix.diff.
DF-3816 β VERDICT
status: inconclusive (runtime) β sysctl node requires an instantiated
controller (dev.mpr.N does not exist on this guest, probe.log); source
defects are certain.
Source trace
- sysctl registration: mpr.c:1846-1848 (CTLTYPE_OPAQUE|CTLFLAG_RD|CTLFLAG_SKIP,
handler
mpr_dump_reqs, arg1 = sc). - privilege gate: mpr.c:1888 (
caps_priv_check_self(SYSCAP_NODRIVER)). - read-only guard: mpr.c:1895 (
req->newptr != NULL β EINVAL). - unlocked walk: mpr.c:1904 comment "Best effort, no locking"; loop
mpr.c:1905-1922 reads
cm->cm_state,cm->cm_desc, walkscm->cm_chain_listtwice. Every other consumer of these fields holdssc->mpr_lock(intr path mpr.c:2269-2271, completion/freepath mprvar.h:568-602); chains are inserted at mpr.c:3182 under the lock and freed tosc->chain_listat mprvar.h:592-595 β a concurrent completion mutates the list mid-TAILQ_FOREACH_MUTABLEβ traversal can follow achain_linkthat was just kfreed... (chains aren't kfreed individually β they return to the pool β but the element is simultaneously re-armed onto another command's list, so the dump can follow into another command's chain or loop; on the realloc path mpr.c:787-788 the entiresc->chainsarray IS kfreed β true UAF). - fixed-size copies: mpr.c:1918
sbuf_bcat(sb, cm->cm_req, 128)withcm->cm_req = req_frames + i*reqframesz(mpr.c:1530) β last frame reads128 - reqframeszbytes past the allocation when the device reports IOCRequestFrameSize < 32 dwords; mpr.c:1921 same for chain frames ofsc->chain_frame_sizebytes (mpr.c:397-405, can be 32/48/64β¦).
Impact ceiling
Privileged debug interface β info-leak of freed/adjacent DMA-coherent heap into a sysctl buffer + panic under race. Low severity, defense-in-depth fix.
Fix
fix.diff: hold mpr_lock across the loop; clamp copies with min().
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 controller on guest; the sysctl node cannot be exercised in either kernel.
fix.diff; probe.log.
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md trace; fix.diff (lock + min() clamps).
PoC changes
No runnable PoC without hardware.
Verified recommended fix
Hold mpr_lock across the dump walk and clamp each sbuf_bcat to the device-reported frame size.
Verdict
Source-certain defects in the privileged dev.mpr.N.dump_reqs handler: it walks cm->cm_chain_list and reads cm state with no lock (mpr.c:1904-1922) while every other consumer mutates those lists under sc->mpr_lock (real UAF on the realloc path where sc->chains is kfreed, mpr.c:787-788), and copies fixed 128 bytes from request/chain frames whose sizes are device-reported and can be smaller (mpr.c:1918/1921 vs mpr.c:387/397-405) β OOB read past the req_frames/chain_frames DMA allocations. Runtime inconclusive: no controller on the guest, sysctl tree dev.mpr.* never instantiates (probe.log).
No comments yet.