# DF-4482 — vinum revive waitlist drain use-after-free (kernel panic)

## What this proves

`revive_block()` drains `sd->waitlist` by launching each request **while it is
still linked into the waitlist**, then reading `sd->waitlist->next` **after**
the launch (`sys/dev/raid/vinum/vinumrevive.c:216-217`). A launched request is
freed by `complete_rqe() -> freerq()` as soon as its I/O completes, and nothing
ever unlinks a request from `sd->waitlist` at completion time. With an md-backed
(malloc disk) vinum drive the completion happens **synchronously inside
`dev_dstrategy()`** (see `sys/dev/disk/md/md.c` `mdstrategy_malloc()` ->
`biodone(bio)`), so the request is *already freed* when the drain loop reads
`rq->next` and hands the (freed, possibly reallocated) chain to the next
`launch_requests()` — which then fires request/buf structures that have already
been completed and unlocked:

```
panic: bpdone: bp 0xfffff80116ae1eb0 not busy
bpdone() at bpdone+0x326
bpdone() at bpdone+0x326
mdstrategy() at mdstrategy+0x3b6
dev_dstrategy() at dev_dstrategy+0xb7
diskstrategy() at diskstrategy+0x3b      <- vinum DRIVE[].dev = /dev/md0s0
dev_dstrategy() at dev_dstrategy+0xb7    <- vinum launch_requests()
Debugger("panic")
```

Reproduced 2/2 runs. Controls: revive alone (clean) and 150,000 plain
volume writes without revive (clean) — the panic requires the
revive-conflict waitlist machinery.

## Setup (root, one time)

`sh ./setup_vinum.sh` — loads vinum, GPT-partitions two md memory disks with
`vinum`-typed partitions, creates a mirrored 3 MB volume `vol`
(concat plex `vol.p0` on md0, concat plex `vol.p1` on md1), marks
`vol.p1.s0` stale.

## Trigger

`sh ./run_race.sh` — starts 6 `writer.sh` sweepers writing 4 KB blocks all
over `/dev/vinum/vol/vol` and runs `vinum start vol.p1.s0` (revive).
Writes that straddle the moving revive cursor are waitlisted
(`checksdstate()` sets `XFR_REVIVECONFLICT`), and every completed revive
block drains the waitlist → UAF.

## Expected

Kernel panic `bpdone: bp ... not busy` within seconds (~5 s run 1, <30 s
run 2); guest drops to `db>`, ssh dies.

## Fix validation

`fix.diff` unlinks each request **before** launching it (2-line semantic
change). Applied to the guest's `/usr/src`, kernel rebuilt
(`make nativekernel`), rebooted, and the exact same PoC re-run: no panic
(writers complete, revive completes). See VERDICT.md.
