# DF-4801 — the revive waitlist is never drained when a revive is abandoned: writers queue on `sd->waitlist` forever (uninterruptible, `kill -9`-proof, survives revive abort/detach)

* File: `sys/dev/raid/vinum/vinumrevive.c:198-218` (the only drain in
  the tree), `vinumrequest.c:327-355` (the append)
* Severity: Medium (permanent unkillable I/O hang + request leak; root
  starts the revive, unprivileged writers via a mounted filesystem are
  the victims; only a reboot clears it)
* Bucket: base:raid / dos
* Status: **reproduced** (4/4 writers hung, deterministic), fix
  validated on the guest

## Root cause

A write that straddles the revive cursor is queued on `sd->waitlist`
(`launch_requests()`, `XFR_REVIVECONFLICT`, `vinumrequest.c:327-355`)
and its writer sleeps in `biowait()` — which has **no PCATCH**, so the
sleep is uninterruptible. The ONLY code in the whole tree that drains
`sd->waitlist` is the tail of a *successful* `revive_block()` call
(`vinumrevive.c:198-218`). The revive itself is driven **one block per
ioctl** by the userland `vinum start` process; when that process stops
issuing ioctls — killed, SIGSTOPped, crashed, or simply not scheduled —
the waitlist is orphaned:

* no `set_sd_state()` transition away from `sd_reviving`
  (down/stale/initializing/…) looks at `sd->waitlist`
  (`vinumstate.c:104-296`);
* subdisk detach / `vinum stop` / module unload never drain it
  (`rg waitlist` over `sys/dev/raid/vinum/` shows exactly one drain
  site);
* the queued `struct request`s and their buffers leak permanently.

Result: every writer whose request was queued hangs **forever**, is
immune to `kill -9`, and stays hung through an explicit revive abort
(`vinum setstate -f down`).

## Reproduction (guest, stock kernel, deterministic — no UAF race needed)

`prime.c` sends exactly ONE `VINUM_SETSTATE(object_up, blocksize=64k)`
ioctl: one clean `revive_block()` runs (empty waitlist), the sd is now
`sd_reviving` with the cursor at byte 64k, and nothing will ever drive
it again. Four writers then hammer the straddle window `[64k, 96k)`
(every write conflicts → waitlisted). `hang.sh`:

```
prime: error=35 msg=                       <- EAGAIN, sd "R 2%"
writer1..4: (frozen immediately, counters empty)
 918 D0     physstr  dd ... seek=18 ...    <- all four writers D-state
kill -9 -> all four STILL D-state          (">>> UNKILLABLE HUNG WRITERS <<<")
vinum setstate -f down vol.p1.s0
 -> all four STILL D-state                 (">>> STILL HUNG AFTER ABORT <<<")
S vol.p1.s0   State: down                  (revive aborted; nobody drained)
```

Writers target only the straddle window so the trigger never touches
the REQUEST_DOWN / trailing-BAD-group launch path (the DF-4482/DF-4721
UAF) — the hang is shown in isolation, deterministically (`run.log`).

## Fix (validated)

`fix.diff` adds `drain_sd_waitlist(sdno)` (steal the list atomically;
relaunch each request if the sd just came up — exactly what
`revive_block()`'s own drain would have done — otherwise fail it with
EIO and free it), called from `set_sd_state()` on every actual
transition out of `sd_reviving` (`vinumstate.c` hook). Aborting the
revive now completes the queued writes:

```
-- after revive abort --
writers gone (drained: fix working)        (fix_run.log)
```

vs. the stock kernel's `>>> STILL HUNG AFTER ABORT <<<`.

## Files

* `hang.sh` — the exact trigger (setup + prime + 4 straddle writers +
  kill -9 tests + abort test)
* `prime.c` — single-ioctl revive primer
* `run.log` — stock reproduction
* `fix.diff` — drain-on-abort (validated), stacks on the DF-4482 v2 fix
* `fix_run.log` — patched-module validation
* `env.txt`
