# DF-5377 — mapped CTIO bounce pbuf leaked forever by targdone()'s
# not-enabled free path (and targdtor): vm.pbuf_mem_count drain →
# system-wide raw-I/O wedge

## What
`sys/bus/cam/scsi/scsi_target.c`

- `targsendccb()` maps daemon CTIO data buffers via
  `cam_periph_mapmem()` (:788), which takes a bounce pbuf with
  `getpbuf_mem()` (cam_periph.c:643-767) and records it in
  `descr->mapinfo` (released only by `cam_periph_unmapmem`).
- The correct release is in `targreturnccb()` (:973-974) — but
  `targdone()`'s "no longer enabled, throw away CCB" path (:828-832)
  and `targdtor()`'s user_ccb_queue drain (:541-548) both call
  `targfreeccb()` directly, dropping `mapinfo->bp[0]` on the floor.
- A pbuf is never returned to the pool: `vm.pbuf_mem_count` (guest:
  394) drops by one per leaked CTIO. Once drained, every
  `getpbuf_mem()` caller — raw physio I/O (`dd if=/dev/daX`),
  disk-label/GPT probes — blocks forever in `tsleep(..., "wswbuf5")`
  (vm/vm_pager.c:505-528).

## Choreography (no race, no close; DF-1045 not involved)
per cycle on ONE fd:
TARGIOCENABLE → write() CTIO (CAM_DIR_IN, dxfer 512) → targsim holds it
→ TARGIOCDISABLE (targsim fails the XPT_ABORT with CAM_UA_ABORT so the
CTIO survives; EN_LUN disable succeeds → LUN_ENABLED cleared) →
sysctl hw.targsim.complete=1 (rig xpt_done()s the held CTIO) →
targdone() sees state not enabled → targfreeccb() → pbuf leaked.
Repeat (re-enable works on the same softc).

## Reproduce (guest, root)
```
cd /root/targpwn
make KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC
cc -O0 -g -o poc5377 poc5377.c
kldload ./targsim.ko; sleep 2
sysctl vm.pbuf_mem_count          # baseline (394 on this guest)
./poc5377 8                       # expect count -8 exactly
./poc5377 386 drain               # drain pool, then dd hangs forever
```

## Expected
`vm.pbuf_mem_count` decreases by exactly the cycle count and never
recovers; in drain mode `dd if=/dev/da1` stays in the process table
stuck on wswbuf5.

## Files
- poc5377.c — leak loop + drain mode
- build.sh / run.sh
- build.log / run.log / run.drain.log
- fix.diff — cam_periph_unmapmem() before targfreeccb() in targdone()
  and targdtor

## Fix-validation mode
`./poc5377 8 x readback` — on a kernel carrying fix.diff, the late CTIO
completions are delivered via user_ccb_queue (not discarded in SWI
context) and targdtor() releases the mapping in the daemon's context:
the pool returns to baseline (394) and stays there. Note the fix went
through three designs — v1 (unmapmem in targdone) and v2 (relpbuf in
targdone) were REFUTED live (copyout is illegal in SWI context; the
pbuf is BUF_LOCKed by the mapping thread) — see run.patched.log; the
validated v3 routes the release into daemon context per the API
contract in scsi_targetio.h.
