# DF-4223 — pvscsi attach/detach error-path asymmetry: double bus_dmamap_destroy (double kfree) and wild callout_drain through a NULL hcb array

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

## What

`pvscsi_free_all()` (pvscsi.c:1566-1608) assumes `sc->hcb_cnt` per-hcb DMA
resources exist and that `sc->hcbs` is valid. Both assumptions break on
attach-failure paths, producing two distinct memory-safety defects:

### (a) Double `bus_dmamap_destroy` ⇒ double kfree

On partial failure of `pvscsi_dma_alloc_per_hcb()` (pvscsi.c:558-621):

1. the fail path at pvscsi.c:615-618 runs
   `pvscsi_dma_free_per_hcb(sc, i)` — destroying the `bus_dmamap_t`s it
   created for hcbs `[0..i)` (pvscsi.c:549-552);
2. `pvscsi_attach()` then calls `pvscsi_free_all()` (pvscsi.c:1729), which
   runs `pvscsi_dma_free_per_hcb(sc, sc->hcb_cnt)` **again**
   (pvscsi.c:1586) — destroying the same `[0..i)` maps a second time.

DragonFly's `bus_dmamap_destroy()`
(`sys/platform/pc64/x86_64/busdma_machdep.c:439-449`) tolerates a NULL map
but performs `kfree(map, M_DEVBUF)` without clearing the caller's pointer
and without any freed-flag — the second destroy is a **double free into the
M_DEVBUF/kmalloc zone** (heap corruption). The `[i..hcb_cnt)` maps that were
never created are NULL and harmless, which is exactly why this asymmetry
survives casual review.

### (b) `sc->hcbs == NULL` ⇒ wild `callout_drain`/`bus_dmamap_destroy`

If the hcb array allocation fails (pvscsi.c:1718-1724), attach still calls
`pvscsi_free_all()`, which unconditionally runs
`pvscsi_dma_free_per_hcb(sc, sc->hcb_cnt)` with
`sc->hcb_cnt = adapter_queue_size` (set at pvscsi.c:1718 *before* the
failing kmalloc) and `sc->hcbs == NULL`. The helper then computes
`hcb = sc->hcbs + i` (pvscsi.c:542, 550) — a NULL/wild pointer for every i —
and executes `callout_drain(&hcb->callout)` (pvscsi.c:543-544) on it ⇒
NULL/low-address kernel access ⇒ **panic during OOM recovery** (the exact
moment the system is already fragile).

Both are classic error-path asymmetry bugs: the partial-cleanup function and
the global-cleanup function both free the same resources, and the global
cleanup trusts counts that were set before the failed allocation.

## Reachability

Requires a pvscsi controller present (driver probes) and then (a) a
`bus_dmamap_create` failure mid-loop or (b) an `M_NOWAIT` kmalloc failure
(~26 KB for the default 256-hcb array) — i.e. severe memory pressure or
resource exhaustion at attach time. Severity Low: attach-time, OOM-gated,
DoS/heap-corruption only, but the driver ships in GENERIC
(`sys/config/X86_64_GENERIC:147`).

## Reproduce / validation

No pvscsi device on the audit guest — the faults cannot be scheduled.
Source trace in `VERDICT.md`. `fix.diff` (track how many hcbs actually have
per-hcb resources in `sc->hcbs_setup`; use it in `pvscsi_free_all`;
zero it after the partial free) 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`
