# DF-4423 — vtblk_finish_bio() drops its error argument: detach mid-I/O completes unperformed I/O as SUCCESS (silent data fabrication + stale kernel memory disclosure)

## Build (on the DragonFly guest)

```
cc -O2 -pthread -o df4423_tester df4423_tester.c      # userspace tester
cd /root && make                                      # dfdetach.ko (helper KLD)
```

## Run (exact protocol that reproduced)

Requires a vtblk device that is NOT the root disk (so it can be detached
while I/O is in flight). We used a private QEMU instance with the system
disk on IDE (`ad0s1d` root) and a 256MB raw test disk as `vtblk0`.

```
sh /root/killshot2.sh
```

killshot2.sh does, in order:
1. `cat /boot/kernel/kernel /boot/kernel/*.ko > /dev/null` — touch buffer pool
2. `./df4423_tester readvolley 96 &` — 96 threads, each ONE 2MB pread
   (16×128K bios/thread = 192MB total) from a random 2MB-aligned offset
   into a buffer pre-filled with 0xFF; classifies each 128K chunk.
3. `sleep 5; kldload dfdetach.ko` — helper invokes `device_detach(vtblk0)`
   synchronously (same vtblk_detach() path as kldunload / virtio unplug).
4. waits, prints RESULT per-thread + summary.

Control (same volley, no detach): all 1536 chunks verify as real disk
pattern, `errors=0`, wall time 95.81s when host cache is cold.

## Expected output (reproduced)

```
RESULT readvolley threads=96 ok_chunks=1344 STALE_chunks=0 foreign=192 errors=0
```

with every thread's `read_ms ≈ 4900–5050ms` — i.e. every 2MB pread
returned exactly at the 5.0s detach instant — and `errors=0`.

## The proof (QEMU `info blockstats` delta across the run)

- before: `virtio0: rd_bytes=611639296`
- after:  `virtio0: rd_bytes=629596160`
- **device actually transferred Δ = 17,956,864 bytes (17.1MB)**

while the guest's 96 threads received
**96 × 2MB = 201,326,592 bytes of read() SUCCESS (0 errors)**.

**185MB (91%) of "successfully read" data was never transferred by the
device** — it was fabricated by `vtblk_drain()`/strategy-ENXIO completing
pending bios with `biodone()` WITHOUT setting `B_ERROR`/`b_error`
(virtio_blk.c:1202-1207 drops the error argument). The buffer contents
handed to userspace come from recycled kernel buffers/pages holding stale
physical memory (previous boots' disk patterns: 0xAA fill data and 0x5A
marker data — see leak_sample_0.bin = 0xAA…, leak_sample_1.bin = 0x5A…,
matching *no* byte the device transferred in this window).

## Files

- `df4423_tester.c` — userspace tester (fill/marker/readvolley/writevolley/verify)
- `dfdetach.c` — helper KLD calling `device_detach(devclass_get_device(devclass_find("vtblk"),0))`
- `killshot2.sh` — the reproducing run script
- `run_ks2.out` — full per-thread timing + classification (decisive run)
- `blockstats_s0.txt` / `blockstats_s1.txt` — QEMU device byte counters before/after
- `leak_sample_0.bin` / `leak_sample_1.bin` — 4KB samples of fabricated "read" data
- `env_dmesg.txt` — guest uname + detach trace
- `VERDICT.md`, `verdict.json`, `manifest.json`, `fix.diff`
