# DF-4222 — pvscsi XPT_ABORT handler dereferences a NULL hcb back-pointer (panic on abort of a completed CCB)

File: `sys/dev/virtual/vmware/pvscsi/pvscsi.c` (pass 2; DF-1454/DF-1455 excluded)

## What

```c
sys/dev/virtual/vmware/pvscsi/pvscsi.c:1394-1397
	abort_ccb = ccb->cab.abort_ccb;
	abort_hcb = abort_ccb->ccb_h.ccb_pvscsi_hcb;

	if (abort_hcb->ccb != NULL && abort_hcb->ccb == abort_ccb) {   /* <-- deref */
```

`ccb_pvscsi_hcb` is the driver's per-CCB back-pointer. It is **cleared to
NULL on every normal completion**:

```c
sys/dev/virtual/vmware/pvscsi/pvscsi.c:1018-1019
	ccb->ccb_h.ccb_pvscsi_hcb = NULL;
	ccb->ccb_h.ccb_pvscsi_sc = NULL;
```

and is NULL for any CCB that was never issued through this SIM. The XPT_ABORT
handler dereferences it unconditionally (`abort_hcb->ccb` on the first line
of the condition) — `NULL->ccb` read at a small fixed offset ⇒ **kernel page
fault / panic** whenever CAM asks pvscsi to abort a CCB that is not currently
in flight on this SIM.

The correct pattern is the standard one used by every other checked SIM in
this tree — NULL-check the back-pointer before dereferencing:

```c
	if (abort_hcb != NULL && abort_hcb->ccb == abort_ccb) {
```

(the original first clause `abort_hcb->ccb != NULL` short-circuits the same
way once the pointer itself is checked; the semantics for a live hcb are
identical).

## Reachability

`XPT_ABORT` reaches `pvscsi_action()` from:

* **targ(4) kernel paths** — `abort_all_pending()`
  (`sys/bus/cam/scsi/scsi_target.c:1074-1116`) walks the pending queue under
  `crit_enter()` only (scsi_target.c:498-500) and issues `xpt_action(XPT_ABORT)`
  per CCB; a completion landing on another CPU between queue inspection and
  the abort running clears `ccb_pvscsi_hcb` (pvscsi.c:1018) ⇒ NULL deref.
  Same for `scsi_targ_bh.c:351,359`.
* **targ(4) user paths** — `targwrite` accepts user XPT_ABORT CCBs
  (scsi_target.c:719-745 translates the user abort_ccb pointer to a kernel
  CCB found on the pending queue; the same completion race applies).
* Any kernel or userland (`/dev/xpt0` CAMIOCOMMAND, root) issuer that aborts
  a CCB after it completed.

targ device nodes are privileged (root/operator), and the completion race
window is narrow, so this is a privileged/racy local DoS — but it is a plain
missing NULL check, trivially fixed. Contrast: the sibling virtio-scsi driver
never dereferences a back-pointer for abort (`vtscsi_cam_abort`,
virtio_scsi.c:941-974 uses its own request queue).

## Reproduce / validation

No pvscsi device on the audit guest (QEMU; no 15ad PCI ids) — no live
trigger. Source trace in `VERDICT.md`; `fix.diff` (one-line NULL check)
applies cleanly to the pristine guest kernel source and the module rebuilds
(`build.log`).

## Files

`VERDICT.md`, `fix.diff`, `patch_apply.log`, `build.log`, `env.txt`,
`manifest.json`, `verdict.json`
