# DF-3661 VERDICT — per-channel cam_simq_release on a once-referenced shared devq

## Status: not_reproduced (no QLogic hardware on guest; arithmetic is source-certain)

## Analysis

The devq reference protocol in DragonFly CAM:

* `cam_devq_alloc` → `refcount = 1` (cam_queue.c:228).
* `cam_sim_alloc(action, poll, name, softc, unit, lock, maxdev, maxtagged, queue)`
  **takes** one reference per sim when a non-NULL queue is supplied
  (`cam_sim.c:182-185`: `else cam_devq_reference(queue)`).
* `cam_sim_free` → `cam_sim_release` drops that sim's reference when the sim
  dies (`cam_sim.c:246-252`).
* `cam_devq_release` is `if (--refcount == 0) { camq_fini x2; kfree(devq) }`
  (cam_queue.c:248-256). No underflow protection.

The driver:

* `isp_attach` allocates the devq once (isp_freebsd.c:213) and hands the same
  pointer to every `isp_attach_chan` (isp_freebsd.c:219-223).
* Each `isp_attach_chan` — right after its `cam_sim_alloc` — executes
  `cam_simq_release(devq)` (isp_freebsd.c:95). This is patterned on
  single-channel drivers where the caller's reference is dropped exactly once
  after handing it to the (sole) sim. Applied per channel, every channel
  after the first decrements a reference owned by a live sim.

Net: for nchan = N the steady-state refcount is 1 instead of N.

## Failure sequence at teardown (isp_detach, isp_freebsd.c:290-309)

1. chan N−1: `cam_sim_free(sim)` → `--refcount` → 0 → **devq kfree'd**.
2. Between step 1 and the last `cam_sim_free`, the remaining N−1 sims are
   still registered; their buses' CCB flow (`xpt_run_devq`, allocation
   openings) dereferences `sim->devq` → **use-after-free** reads/writes of
   `alloc_queue`/`send_queue` state.
3. Each later `cam_sim_free` calls `cam_simq_release(sim->devq)` →
   `--freed->refcount` → **write-after-free**; if the decremented value is 0
   the devq is kfree'd a second time.

Identical over-release executes in `isp_attach`'s unwind (:235-251) when a
later channel fails to attach.

## Affected hardware (this tree)

* PCI_QLOGIC_ISP1240, ISP12160 (dual SCSI, `isp_nchan = 2`; isp_pci.c:732/749).
* ISP2422/2432/2532 with NPIV virtual ports (`isp_nchan += isp_nvports`;
  isp_pci.c:798/805 — nvports hint, up to ~126 channels).
* Single-channel cards (the vast majority) balance exactly — N=1 is safe,
  which is why this survived: the common configuration never trips it.

## Why not reproduced

Guest has zero QLogic PCI devices (vendor 0x1077) — isp never attaches, so
neither the over-release nor the premature free can execute (run.log /
env.txt). The refcount walk is fully determined by source; upstream FreeBSD
(fetched 2026-09-15) releases the devq only once, after all sims are gone,
in both unwind and detach — confirming the intended protocol.

## Exploitability

Write-after-free of a fixed-size `cam_devq` (M_CAMDEVQ) triggered by root
teardown on specific multi-channel hardware; heap-reuse steering would be
required to convert to more than a panic. Not pursuable without the HBA.

## Fix

`fix.diff`: delete `cam_simq_release(devq)` from `isp_attach_chan`; drop the
caller's single reference in `isp_attach` unwind and `isp_detach` after all
sims are freed (upstream pattern). Not runtime-validatable on this guest
(no hardware) → `fix_status: not_testable`.

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