ciss_periodic re-arms after unlock vs callout_stop in ciss_free -> post-free callout execution (UAF)
Summary
ciss_periodic releases ciss_lock and THEN re-arms itself, while ciss_free stops the callout under the lock and later terminates the callout and uninits the lock mid-teardown. A detach racing the heartbeat tick finds the callout non-pending at stop, tears down, and the preempted handler then re-arms a terminated callout on a freed softc -> ciss_periodic executes ~10s later against freed memory (reads heartbeat, takes an uninited lock). Same callout discipline lesson as mpt DF-3672..3678/arcmsr DF-3708..3711. Privileged-detach-gated. Fix: callout_init_lk + handler lock removal (5-hunk diff, compile-validated).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3733 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | full narrative: mechanism, path:line, harness proof, fix | 3.3 KB | β raw |
| fix.diff | suggested-fix | convert the periodic callout to callout_init_lk(&sc->ciss_periodic, &sc->ciss_lock) | 1.7 KB | view raw |
| fix_apply.log | apply-log | patch --dry-run on guest /usr/src (clean) | 2.8 KB | view raw |
| fix_build.log | build-log | ciss.ko rebuilt with all 7 pass-2 fixes at -Werror, link rc=0 | 1.0 KB | view raw |
| env.txt | environment | guest uname, no-HW proof, module presence | 538 B | view raw |
| README.md | readme | human reproduce doc | 1.3 KB | β raw |
| manifest.json | manifest | this file | 2.0 KB | view raw |
| verdict.json | verdict | machine verdict (persist_poc.py schema) | 2.3 KB | view raw |
DF-3733 β ciss_periodic/ciss_free teardown race: callout re-armed after callout_stop β post-free execution of ciss_periodic
File: sys/dev/raid/ciss/ciss.c:3431-3436 (ciss_periodic releases
ciss_lock, then calls callout_reset) vs ciss_free ciss.c:1874
(callout_stop under the lock) and ciss.c:1902-1908 (lock released,
callout_terminate, lockuninit, then the softc is torn down).
Class: detach-vs-callout race β use-after-free execution of
ciss_periodic on a freed softc (CWE-362/DF-3672-family callout
discipline). Requires privileged detach (kldunload/devctl) racing the 10 s
heartbeat tick β Low severity.
Verification status
Source-level analysis; inconclusive at runtime β no CISS hardware on the
guest, and the race needs a real controller detach to have a meaningful
window (env.txt). The fix applies the callout_init_lk discipline validated
in the mpt/arcmsr findings.
Fix
fix.diff (5 hunks: callout_init_lk(&sc->ciss_periodic, &sc->ciss_lock),
handler no longer takes/drops the lock itself, attach-time direct call
wrapped). Compile-validated with the other 6 fixes at -Werror
(fix_build.log, fix_apply.log).
Artifacts
VERDICT.md, fix.diff, fix_apply.log, fix_build.log, env.txt,
manifest.json, verdict.json.
DF-3733 VERDICT
Finding
The periodic heartbeat is an MP callout that manages its own locking:
ciss_attach: callout_init_mp(&sc->ciss_periodic); /* ciss.c:429 */
ciss_periodic: lockmgr(&sc->ciss_lock, LK_EXCLUSIVE); /* ciss.c:3383 */
... work ...
lockmgr(&sc->ciss_lock, LK_RELEASE); /* ciss.c:3431 */
callout_reset(&sc->ciss_periodic, ...); /* ciss.c:3436 <-- RE-ARM AFTER UNLOCK */
ciss_free: lockmgr(&sc->ciss_lock, LK_EXCLUSIVE); /* caller */
callout_stop(&sc->ciss_periodic); /* ciss.c:1874 */
Race: ciss_periodic running on CPU1 finishes its work, drops the lock
(ciss.c:3431) and is preempted before callout_reset (ciss.c:3436). CPU0
(detach) takes the lock, runs ciss_free β callout_stop() finds the
callout not pending (it already fired) and does nothing; teardown
continues β the lock is even released mid-free for destroy_dev
(ciss.c:1902), then callout_terminate (ciss.c:1907) and lockuninit
(ciss.c:1908) run on a callout that CPU1 subsequently re-arms. The
re-armed callout fires ~10 s later into a softc whose lock is uninited and
whose resources are freed (regs released ciss.c:1915+, ciss_logical freed
ciss.c:1952+, DMA tags destroyed) β ciss_periodic reads
sc->ciss_cfg->heartbeat and takes an uninited lock on freed state β a
use-after-free execution with kernel-crash-to-corruption potential.
Secondary exposure on the same path: between ciss.c:1902 (lock release) and
lockuninit (ciss.c:1908), a blocked ciss_periodic can acquire the lock and
run against a half-torn-down adapter.
This is exactly the mpt (DF-3672..3678) / arcmsr (DF-3708..3711) lesson:
periodic callouts that self-lock must be converted to callout_init_lk so
that stop/reset/handler are serialized by the same lock.
Reachability
Requires ciss_detach (privileged: kldunload / devctl) racing the 10-second
heartbeat tick on a machine with the hardware. No unprivileged trigger β
Low severity, correctness/race class.
Status
- Source-certain interleaving (the re-arm after unlock and the stop-under- lock are unambiguous in the code).
- Runtime: inconclusive β no CISS hardware on the guest (env.txt) and no meaningful race window without a real controller; not exercised live.
Fix validation
fix.diff(5 hunks):callout_init_lk(&sc->ciss_periodic, &sc->ciss_lock)at ciss.c:429; the handler drops its own acquire/release (ciss.c:3383, 3396, 3431); the attach-time direct invocation (ciss.c:486) now takes the lock around the call. With_lksemantics, callout_reset/callout_stop and the handler are serialized underciss_lock, closing the re-arm race.fix_apply.log: clean dry-run;fix_build.log:ciss.korebuilt with all 7 ciss pass-2 fixes at-Werror, link rc=0.
Bottom line: inconclusive at runtime (hardware absent, race needs privileged detach); source-certain callout-discipline race; fix compile-validated.
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
inconclusivecallout_init_lk(&sc->ciss_periodic, &sc->ciss_lock) + handler-side lock removal + wrapped attach-time call: stop/reset/handler serialized under one lock. Compile-validated (-Werror, link rc=0). Race not live-testable.
fix.diff (5 hunks); fix_apply.log; fix_build.log
Confirmed kernel references
Detail
Exploit chain
kldunload/detach races heartbeat tick -> callout_stop sees non-pending callout -> handler re-arms after terminate -> ciss_periodic on freed softc
Evidence (decisive lines)
VERDICT.md interleaving analysis; fix_apply.log + fix_build.log
PoC changes
no runnable PoC (race, hardware-gated); source-level pack
Verified recommended fix
convert the periodic callout to callout_init_lk(&sc->ciss_periodic, &sc->ciss_lock)
Verdict
INCONCLUSIVE at runtime (no hardware; race needs a real controller detach), source-certain: ciss_periodic re-arms itself AFTER releasing ciss_lock (ciss.c:3431 vs 3436) while ciss_free stops the callout under the lock (ciss.c:1874) and then terminates/uninits around further teardown (ciss.c:1902-1908) -> stop-vs-rearm race leaves an armed callout on a freed softc -> post-free ciss_periodic execution (UAF). Privileged-detach-gated -> Low severity. Same callout_init_lk discipline as the mpt/arcmsr findings.
No comments yet.