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

Unguarded tws_reset re-entry: unlocked state check races the TWS_RESET_START transition β€” KASSERT panic (INVARIANTS) or leaked xpt_freeze_simq count freezing the adapter forever (twa sibling DF-4336)

Summary

The reset guards re-entry with an unlocked read but transitions under gen_lock with a KASSERT single-entry. Three concurrent callers (ccb timeout callouts, attn-error interrupt, passthru ETIMEDOUT). Double entry: panic on INVARIANTS kernels; on production the second freeze is never matched by reinit's single release - SIM queue frozen forever. Plus duplicated resets and reinit static retry counters shared across adapters. Lock+re-check fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4401 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.3 KB ↓ raw
VERDICT.md β€” 2.4 KB ↓ raw
trigger.c β€” 833 B view raw
fix.diff β€” 2.1 KB view raw
verdict.json β€” 2.5 KB view raw
env.txt β€” 541 B view raw

DF-4401 β€” Unguarded tws_reset() re-entry: check-then-act race corrupts the reset state machine (panic / frozen simq)

What

tws_reset() (sys/dev/raid/tws/tws_cam.c:1197-1218) guards re-entry with an unlocked read:

if ( tws_get_state(sc) == TWS_RESET ) {
    return;
}
...
lockmgr(&sc->gen_lock, LK_EXCLUSIVE);
tws_send_event(sc, TWS_RESET_START);   /* KASSERT(state != TWS_RESET), tws.c:666 */
lockmgr(&sc->gen_lock, LK_RELEASE);

Three independent callers can run it concurrently: - tws_timeout ccb-timeout callout (tws_cam.c:1185-1195) β€” one per queued I/O; - tws_intr_attn_error microcontroller-error interrupt (tws_cam.c:1143-1153); - tws_passthru ioctl thread on ETIMEDOUT (tws_user.c:143-147, holding gen_lock β€” only survivable because it is LK_CANRECURSE, tws.c:199).

Two threads that both pass the unlocked check both execute the reset prologue:

  1. INVARIANTS kernels: the second TWS_RESET_START trips KASSERT(sc->tws_state != TWS_RESET) (tws.c:666) β†’ panic (KASSERT/KKASSERT are compiled in only with INVARIANTS, sys/sys/systm.h:94-122); the second TWS_RESET_COMPLETE later trips tws.c:674 similarly.
  2. Production kernels: duplicated xpt_freeze_simq(sc->sim, 1) (tws_cam.c:1212-1214) with only ONE matching xpt_release_simq(...,1) in tws_reinit (:1268-1270) β†’ qfrozen_cnt leaks β‰₯1 (sys/bus/cam/cam_xpt.c:4852-4888) β†’ the SIM queue stays frozen forever, all I/O on the adapter stalls permanently; plus a duplicated soft reset (tws_assert_soft_reset, :1216) and duplicated reset_cb/reinit callout chains.
  3. tws_reinit() (:1241-1281) keeps its retry counters (timeout_val, try) in static storage β€” shared across all adapters in the system.

Sibling: DF-4336 (twa tw_cl_reset_ctlr has no entry guard) β€” same defect class carried into the tws CAM layer.

Impact

Local DoS (panic on debug kernels; permanent adapter I/O freeze on production kernels) triggered by controller distress that fires β‰₯2 of the reset callers near-simultaneously (e.g. two ccb timeouts racing a controller-error interrupt). No privilege needed beyond ordinary I/O to a distressed adapter.

Status

QEMU guest has no 3ware HBA β†’ inconclusive; race proven from source.

VERDICT.md
↓ download raw

VERDICT β€” DF-4401

Status: inconclusive (hardware-gated). The check-then-act race is certain from source; triggering needs the 3ware controller to be distressed enough that two reset callers overlap.

Proof chain

  1. tws_reset entry guard is a plain read: tws_cam.c:1203-1205 if (tws_get_state(sc) == TWS_RESET) return; β€” no lock held; the state transition happens later under gen_lock at :1207-1209.
  2. tws_send_event(TWS_RESET_START) asserts single-entry: tws.c:664-671 KASSERT(sc->tws_state != TWS_RESET, ...); KASSERT panics only on INVARIANTS kernels (sys/sys/systm.h:94-122).
  3. Concurrent callers: - ccb timeout callout per I/O: tws_cam.c:807-808 arms tws_timeout, which calls tws_reset (:1191-1194) for every timed-out request; - controller-error interrupt: tws_intr_attn_error β†’ tws_reset (:1143-1153); - passthrough ioctl timeout: tws_user.c:142-147 β†’ tws_reset while holding gen_lock (recursive; tws.c:199). Nothing serializes them before the state check.
  4. Production-kernel consequence of a double entry: - xpt_freeze_simq(sc->sim, 1) executed twice (tws_cam.c:1212-1214) vs a single xpt_release_simq(sc->sim, 1) in tws_reinit (:1268-1270): sendq.qfrozen_cnt is a counter (sys/bus/cam/cam_xpt.c:4863-4865), so a leaked increment keeps the devq frozen permanently β†’ every subsequent tws_execute_scsi sees a frozen SIM; disk I/O on the adapter never resumes. - duplicated tws_assert_soft_reset (:1216) and callout chains (:1217, :1238) β†’ duplicated reinit sequences.
  5. Multi-adapter defect: tws_reinit's static int timeout_val=0, try=2 (:1246) is shared by every controller instance.

Impact ceiling

Panic (INVARIANTS) or permanent adapter-wide I/O hang (production) β€” a local denial of service; no info leak or privilege path. Rated Low consistent with DF-4336.

Fix

Serialize tws_reset entry under gen_lock with a re-check after acquisition (fix.diff), and move tws_reinit's retry counters into the softc.

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_testable
baseline no→ patch + rebuild →patched clean

fix.diff moves the state check under gen_lock and makes reinit counters per-adapter; not validatable on the guest (no controller).

fix.diff
↓ fix.diffper-fix-DF-4401

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (race proof + freeze-count arithmetic); fix.diff (locked re-check + per-adapter counters); trigger.c (reproduction recipe)

PoC changes

no seed; recipe stub only

Verified recommended fix

Serialize tws_reset entry: take gen_lock, re-check TWS_RESET, then run the RESET_START transition; move tws_reinit retry counters into the softc.

Verdict

Certain from source, hardware-gated to trigger. tws_reset() guards re-entry with an unlocked state read (tws_cam.c:1203-1205) while the TWS_RESET transition happens later under gen_lock (:1207-1209) and tws_send_event asserts single entry (KASSERT at tws.c:666). Three concurrent callers exist (ccb-timeout callout :1191-1194 armed per I/O at :807; controller-error interrupt :1143-1153; passthrough ETIMEDOUT thread tws_user.c:146 holding gen_lock, survivable only via LK_CANRECURSE). Double entry: INVARIANTS kernels panic at tws.c:666/674; production kernels leak an xpt_freeze_simq count (double freeze at :1212-1214 vs single release in tws_reinit :1268-1270, counter semantics in sys/bus/cam/cam_xpt.c:4863-4865) leaving the SIM queue frozen forever = permanent adapter I/O hang, plus duplicated soft resets. tws_reinit also keeps retry counters in function-static storage shared across adapters (:1246). Guest lacks the HBA.