# DF-4590 VERDICT — not_reproduced (hardware-gated), defect certain from source

## Finding

`tws_detach()` (sys/dev/raid/tws/tws.c:346-410) frees the softc's dynamic
state — `kfree(sc->reqs)` at :398, `kfree(sc->sense_bufs)` :399,
`xpt_free_ccb` :400, `kfree(sc->aen_q.q)` :401, `kfree(sc->trace_q.q)`
:402, `lockuninit()` :403-406 — while driver callouts that reference
exactly that memory remain armed.  Nothing between :346 and :410 calls
callout_stop/callout_cancel on:

* `sc->reset_cb_handle` — armed at tws_cam.c:1217, re-armed at :1232,
* `sc->reinit_handle` — armed at tws_cam.c:1238, **self-re-arming every
  5 s** at :1256 while `!tws_ctlr_ready(sc)` (i.e. forever on a wedged
  controller),
* `sc->reqs[i].thandle` — 60 s timeouts armed at tws_cam.c:847, :893
  (callout_init happens in tws_get_request, tws.c:805).

The attach-failure unwinds (tws.c:309-341) have the identical hole.

A pending callout executing after detach fires `tws_reinit()` /
`tws_reset_cb()` / `tws_timeout()` on freed heap and freed lock objects →
use-after-free in softirq context (page-fault panic or silent slab
corruption).  With a module build (sys/dev/raid/tws/Makefile, KMOD=tws)
the module text itself is also freed, so the callout target may be
unmapped.

## Why it is real and when it fires

The realistic operator sequence is: controller wedges (tws_reset runs,
"Resetting controller" on console, reinit poll cycles every 5 s — the
recovery path DF-1924/DF-1925 already documented as hang-prone) → operator
unloads the driver to re-probe → `kldunload tws` → detach frees everything
→ ≤5 s later the reinit poll fires into freed memory.  Root-privileged,
hardware-dependent — hence severity Low — but the defect class is a
kernel UAF (memcorrupt bucket).

## Reproduction attempt on the audit guest

* Guest: DragonFly 6.5-DEVELOPMENT X86_64_GENERIC (env.txt).
* run.sh / run.log / guest_probe.log: no LSI 3ware (0x13C1) PCI device;
  `dev.tws` absent; tws compiled into the kernel and attached to nothing.
  QEMU has no 3ware 9700 emulation, so the trigger cannot execute:
  **status not_reproduced, reproduced=0, impact none (not manifested
  here)**.  Realistic impact ceiling on real hardware: UAF panic /
  heap corruption at driver unload (privileged trigger).

Confidence: certain — the missing cancellation is directly visible in the
teardown function; every callout consumer pointer is softc/reqs memory
that the same function frees first.

## Fix validation

fix.diff: (1) `callout_cancel()` all three softc callouts and every
`reqs[i].thandle` in tws_detach after `bus_setup_intr` teardown /
`pci_release_msi` and before `tws_cam_detach`/frees; (2)
`callout_init(&sc->reqs[i].thandle)` in tws_init_reqs so cancelling
never-handed-out requests is safe.  git apply --check passes.  Baseline
vs patched cannot be exercised on this guest (no 3ware HW) →
fix_status: not_testable; validated by inspection against tws.c:346-410,
597-636 and tws_cam.c:1217-1256.

## Artifacts

* trigger.sh — exact unload-during-recovery recipe
* run.sh, run.log, guest_probe.log, env.txt — guest hardware-absence proof
* fix.diff — callout cancellation (git-apply clean)
* manifest.json / verdict.json

## 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.
