iir_shutdown: shared flush ucmd freed (wrong malloc type) while still queued/in flight, and re-queued for the next drive while a gccb owns it β use-after-free at shutdown
Summary
iir_shutdown allocates ONE gdt_ucmd_t with M_GDTBUF and reuses it for every present host drive: queue-insert, dispatch (queue-removes it while the owning gccb keeps gc_ucmd pointing at it), then tsleep at most 10s. If a flush does not complete in time, the next iteration overwrites and re-queues the same still-in-flight ucmd (double dispatch), and the unconditional kfree at the end - with the wrong type - frees it while the pending gccb still references it. The later completion writes status/info, bcopies up to 3072 bytes into the freed ucmd->data, TAILQ-links the freed object, and wakeups it. A controller stalling >=10s at shutdown turns this into ~3KB-class write into freed heap. One-ucmd-per-drive + free-only-completed fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4173 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.2 KB | β raw | |
| VERDICT.md | β | 3.0 KB | β raw | |
| gate_check.sh | β | 779 B | view raw | |
| gate_check.log | β | 577 B | view raw | |
| fix.diff | β | 1.3 KB | view raw | |
| manifest.json | β | 963 B | view raw | |
| verdict.json | β | 2.6 KB | view raw |
DF-4173 β iir(4) iir_shutdown: flush ucmd freed while in flight (+ wrong malloc type, double-queue reuse)
Build / Run
There is no userland trigger: the flaw is in the
shutdown_post_sync event handler (iir_shutdown, sys/dev/raid/iir/iir.c:1478-1520)
and manifests when the controller is slow/hung at shutdown.
To document the hardware gate on the audit guest, run:
sh gate_check.sh # no /dev/iir, no iir PCI device, iir never attached
Expected on hardware-less guest: no iir/isp PCI device present.
Expected on a machine with iir hardware and a controller that fails to
complete a GDT_FLUSH within 10 s: the shared ucmd is (a) re-inserted into
sc_ucmd_queue for the next host drive while the previous dispatch still
owns it, and (b) unconditionally kfreed (with the wrong type M_DEVBUF)
while a pending gccb still points at it β the next GDT_GCF_IOCTL completion
(iir.c:1599-1633) writes ucmd->status, bcopys up to 3 KB into the freed
ucmd->data, and calls wakeup(ucmd) β use-after-free write into freed
kernel heap.
Fix
fix.diff allocates one ucmd per host drive, frees only completed ones, and
frees with the correct type (M_GDTBUF).
DF-4173 VERDICT β iir_shutdown ucmd use-after-free / double-queue / wrong free type
Status: untested on this guest (hardware-gated: shutdown path of the iir driver, no controller present). Bug existence: certain from code trace.
Reproduced?
No runtime reproduction: the guest has no iir controller (gate_check.log),
and the buggy path is the system-shutdown handler, driven by controller
completion latency. The gate is proven; the code trace below is line-accurate.
Code trace
iir_shutdown(iir.c:1478-1520) allocates onegdt_ucmd_twithkmalloc(..., M_GDTBUF, ...)(iir.c:1493).- For each present host drive it fills that single ucmd, queues it
(
TAILQ_INSERT_TAIL(&gdt->sc_ucmd_queue, ucmd, links), iir.c:1509), dispatches viagdt_next(), and waits at most 10 s (tsleep(..., 10*hz), iir.c:1514). gdt_next()removes the ucmd from the queue when dispatching it (iir.c:706) and the owning gccb keepsgccb->gc_ucmd = ucmd(iir.c:1006). Until the controller completes the command the ucmd is owned by that in-flight gccb.- Defect A (double-queue): if the 10 s sleep expires with
complete_flag == FALSE, the next loop iteration overwrites the same ucmd's fields while the controller still owns it, and inserts it intosc_ucmd_queueagain β one ucmd now referenced by an in-flight gccb and queued for a second gccb. - Defect B (UAF): after the loop,
kfree(ucmd, M_DEVBUF)(iir.c:1518) frees the ucmd unconditionally β including while it is still in flight and/or still linked intosc_ucmd_queue. The laterGDT_GCF_IOCTLcompletion path (iir.c:1599-1633) then does: -ucmd->status = gdt->sc_status; ucmd->info = β¦; ucmd->complete_flag = TRUE;(iir.c:1608-1610) β writes into freed memory; -bcopy(gccb->gc_scratch, ucmd->data, cnt)withcntup toGDT_SCRATCH_SZ= 3072 (iir.c:1613-1626) β a 3 KB-class write into a freed ~3.1 KB heap chunk (heap corruption if reallocated); -TAILQ_INSERT_HEAD(&gdt->sc_ucmd_queue, ucmd, links)on the BSY path (iir.c:1604) β links freed memory into a live list; -wakeup(ucmd)(iir.c:1632). - Defect C (type mismatch): allocated with
M_GDTBUF, freed withM_DEVBUF(iir.c:1493 vs 1518) β benign withoutMALLOC_DEBUG, but wrong.
Impact ceiling
Shutdown-time UAF write / list corruption, requiring an iir controller that stalls β₯10 s on a flush. Not software-triggerable. Severity Low; bucket: memcorrupt.
Fix validation
fix.diff (one ucmd per drive, free-only-when-complete, correct type) passes
git apply --check. No kernel-build/runtime validation possible without
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_testablefix.diff restructures the flush loop (per-drive ucmd + correct free discipline) and passes git apply --check; runtime validation requires iir hardware
findings/poc/DF-4173/fix.diff (git apply --check clean)
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (full path:line trace); gate_check.log (hardware absent); fix.diff (per-drive ucmd, free-only-complete, correct type, git apply --check clean)
PoC changes
no PoC source written: the defect is in the system-shutdown handler and requires a controller that stalls >=10s on GDT_FLUSH; only a hardware gate check was run
Verified recommended fix
Allocate one ucmd per host drive, free only completed ones with M_GDTBUF, never free/requeue an in-flight ucmd (see fix.diff)
Verdict
Bug certain from code trace, not runtime-testable: iir_shutdown (shutdown_post_sync handler) allocates one gdt_ucmd_t (iir.c:1493), reuses it across all host drives - re-queueing it for drive N+1 while drive N's dispatch still owns it if the 10s tsleep expires (iir.c:1514) - and unconditionally kfrees it with the wrong type (M_DEVBUF vs M_GDTBUF, iir.c:1518) while an in-flight gccb may still hold gc_ucmd pointing at it. The later GDT_GCF_IOCTL completion then writes ucmd->status/info/complete_flag (iir.c:1608-1610), bcopies up to 3KB into the freed ucmd->data (iir.c:1613-1626), TAILQ-links the freed object on the BSY path (iir.c:1604) and wakeups it (iir.c:1632): use-after-free heap write at shutdown. Guest has no iir controller (gate_check.log) so the driver never attaches.
No comments yet.