# DF-3660 VERDICT — teardown UAF: FC kthread + ldt/gdt callouts outlive the softc

## Status: not_reproduced (no QLogic hardware on the guest; defect source-certain)

## The lifetime chain, line by line

| Step | Where | What happens |
|------|-------|--------------|
| 1 | isp_freebsd.c:159 | `THREAD_CREATE(isp_kthread, fc, &fc->kthread, ...)` per FC channel |
| 2 | isp_freebsd.c:4187-4279 | `for (;;) { ...; lksleep(fc, &isp->isp_osinfo.lock, 0, "ispf", slp*hz); }` — no exit flag, no termination path anywhere in the driver |
| 3 | isp_freebsd.c:265-323 | `isp_detach()`: frees paths, `xpt_bus_deregister`, `cam_sim_free` per channel; stops ONLY `isp_osinfo.tmo` (286); never touches kthreads or `fc->ldt`/`fc->gdt` |
| 4 | isp_pci.c:1012-1029 | `isp_uninit`; `bus_teardown_intr`; **`lockuninit(&isp->isp_osinfo.lock)` (1017)**; `bus_release_resource` (1022); **`kfree(isp->isp_param)` (1024)**; `kfree(pc.ptr)` (1028) |
| 5 | newbus | softc (`struct isp_pcisoftc`, containing the ispsoftc) freed |
| 6 | still-scheduled code | kthread wakes on the dead lock; `isp_fc_runstate` → `FCPARAM()` on freed `isp_param`; `isp_prt` → freed `isp_dev`; mailbox path → `ISP_READ/ISP_WRITE` on released bus resources; `isp_ldt`/`isp_gdt` → tasks on freed `fc` |

Every element of step 6 is a use-after-free; the mailbox path is a wild-MMIO
write primitive into whatever the released resource region became.

## Reachability / trigger

* Requires an isp(4) HBA that attached, then a detach event: `kldunload isp`
  (modular kernel), `devctl detach`, reprobe. Root-initiated on this hardware
  class, hence Medium, not High.
* `sim->refcount > 2 → EBUSY` (isp_freebsd.c:281) only guards active CCBs —
  the kthread holds no sim reference, so it does not block detach.

## Why not reproduced here

* `pciconf -l`: 0 devices with vendor 0x1077 → isp(4) never attaches → no
  `/dev/isp*`, no isp bus, nothing to detach (run.log).
* isp is compiled into X86_64_GENERIC (sys/config/X86_64_GENERIC:89), not
  loadable as a module on this guest, so even the kldunload route is closed.
* The defect does not depend on input data — it is pure teardown ordering,
  provable by inspection and by contrast with upstream FreeBSD, whose
  `isp_detach_chan()` reaps threads (`num_threads` + `wakeup(fc)` +
  `mtx_sleep` loop) and whose `isp_detach()` sets `is_exiting` and
  `callout_drain`s the timer (verified against FreeBSD HEAD 2026-09-15).

## Exploitability assessment (primary-objective analysis)

The primitive would be: after `kfree(isp_param)` (a `M_DEVBUF` allocation of
`sizeof(fcparam)*nchan` — for FC that is a large, well-known-sized heap
block, `fcparam` is ~500 KB with `portdb[512]`), a resident kernel thread
continues read-modify-writing it on a timer cadence, plus wild MMIO through a
stale bus_space handle. Practical weaponization requires controlling heap
reuse of the freed softc/isp_param and surviving the lock corruption — on the
audit guest's no-SMEP/no-KASLR profile that would be plausible with hardware;
without hardware it cannot be pursued. Classified Medium (root-triggered
teardown UAF), memcorrupt bucket.

## Fix

`fix.diff`: upstream-style `is_exiting` flag + `num_threads` reaping +
`callout_stop(ldt/gdt)` + `fc->ready = 0` before any frees, and
`kthread_exit()` in the worker. Not runtime-validated on this guest
(`fix_status: not_testable`) because neither baseline nor patched kernel can
attach the absent HBA; the change mirrors FreeBSD's shipped solution.

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