# DF-5327 evidence pack — `chopen()` sets CH_FLAG_OPEN before `cam_periph_hold(PCATCH)`; interrupted hold burns the open reference (refcount desync → 0-with-open-fd → premature free / unsigned underflow)

## Finding (one line)
`chopen()` (sys/bus/cam/scsi/scsi_ch.c:415-432) performs the
"multi-open fixup" (set `CH_FLAG_OPEN` for the first opener / drop this
open's `cam_periph_acquire()` for later openers) **before**
`cam_periph_hold(periph, PCATCH)`; when that hold is signal-interrupted,
its error path (`cam_periph_unlock` + `cam_periph_release`) drops a
reference that the flag state says someone else owns — every interrupted
open decrements the peripheral refcount by one (the DF-5273 class, in the
open path instead of the ioctl path, in a driver DF-5273 did not cover).

## Consequences (all reproduced on the guest, deterministic)
1. open#1 parked (device stall) + open#2 interrupted → open#1 completes
   with **refcount == 0 while /dev/ch0 is open** → any device removal
   (AC_LOST_DEVICE) runs `camperiphfree()` immediately: softc and periph
   kfree'd under the open descriptor (premature free / UAF-enabling
   state, same window DF-5273 describes).
2. plain close on that fd → `cam_periph_release` at 0 →
   **0xFFFFFFFF** (silent: DF-5329's dead guard).

## Reproduce (guest, root; EVIL kernel = GENERIC+`device ch`, evilcd2.ko)
```
cd /root/df5327
sysctl -n debug.evilcd.watch=2       # refcnt/postmortem watch the ch periph
./run.sh                             # stage A then stage B
```
Stage A (`poc_chopen`): interrupt-burn + refcount 0-with-open-fd + close → -1.
Stage B (`poc_chopen2`): same burn, then invalidate → `chcleanup` frees
periph+softc while the fd is open, then `debug.evilcd.sprayfake`
re-allocates the **exact freed chunk** with fully chosen contents.

## Expected (vulnerable kernel) — stage A
```
[child2] open#2: fd=-1 errno=4(Interrupted system call) refcount=1
[parent] refcount while open#1 still parked: 1
[child1] open#1 returned: refcount=0 (*** expect 0 -- undercounted with ch0 OPEN ***)
[child1] after close: refcount=-1 (*** expect -1: unmatched release ***)
```

## Rig
`evilcd2.c` (derived from the DF-5271/DF-5273 evilcd rig) adds:
- target 1 = fake T_CHANGER (`/dev/ch0`), MODE SENSE pages 0x1d/0x1f
- `debug.evilcd.defer` = park the next SCSI CCB (parks chgetparams)
- `debug.evilcd.watch` = 2 → refcnt/periphaddr/postmortem/sprayfake
  watch the ch periph
- `debug.evilcd.invalidate` → xpt_async(AC_LOST_DEVICE) on the target
- `debug.evilcd.sprayfake` → spray sizeof(cam_periph) chunks until one
  lands at the freed periph address; plant a fake (refcount=0x41414141)
```
root@dfbsd:~ # uname -a
DragonFly dfbsd 6.5-DEVELOPMENT #0 ... /usr/obj/usr/src/sys/EVIL  x86_64
```
