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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3733 Β· 8 files
FileTypeDescriptionSize
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
README.md readme human reproduce doc
↓ download 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.

VERDICT.md verdict full narrative: mechanism, path:line, harness proof, fix
↓ download raw

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 _lk semantics, callout_reset/callout_stop and the handler are serialized under ciss_lock, closing the re-arm race.
  • fix_apply.log: clean dry-run; fix_build.log: ciss.ko rebuilt 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

inconclusive
baseline no→ patch + rebuild →patched clean

callout_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
↓ fix.diffper-fix-DF-3733

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.