# DF-2875 VERDICT

## Status: reproduced (memcorrupt — deterministic premature free of an
## in-flight disk-I/O buffer; peer-controlled)

Guest: DragonFly 6.5-DEVELOPMENT #0 X86_64_GENERIC (see env.txt), QEMU
virtio boot disk `/dev/vbd0` (30GB; 7.5MB unused tail gap used as the
write sandbox).

## What was run

`run_f2.sh`: 10 rounds of a single BLK_WRITE transaction streaming
W1(4KB, pattern 0x11+r) → W2(128KB, 0x22+r) → R1..R4(128KB, 0xAA, last
carries DELETE), all pipelined while earlier bios are still in flight;
then a full readback scan of all 60 slots (`readscan`).

## Observed (stock kernel)

1. **Premature free / UAF on in-flight `bp->b_data`** — `diskiodone` for
   W1's bio frees `iost->data`, which by then contains the aux of the
   *most recent* write (R4), while R4's bio is still queued
   (sys/kern/subr_diskiocom.c:440 detach-overwrite, :606 free-on-any-
   completion). Readback:
   - W2/reclaimer slots showing `aa aa aa aa` where their own pattern
     should be — the freed buffer was reallocated (reclaimer aux,
     same kmalloc size class, M_DMSG_DISK) before the victim bio's DMA,
     so the disk received *another message's* payload
     (`f2_scan.txt`: off+003/005/009... `aa aa aa aa`).
   - `off+014: 53 ff 00 f0` and several slots with ASCII `"cals"` —
     **kernel heap contents persisted onto the raw disk** through the
     freed-then-reused buffer (info-leak-onto-disk primitive; readable
     back by the same peer via BLK_READ).
2. **Memory leak** — `vmstat -m dmsg_disk` in-use grew 2.19K → 2.28M
   across the 10 rounds: every detached aux except the one freed by the
   first completion is orphaned (aux1 of each round + intermediates).
3. No panic on this (non-INVARIANTS) kernel; the racy double-free variant
   (two concurrent diskiodone on the same iost between the
   `if (data->aux_data)` check and the NULL store in
   `kdmsg_free_aux_data`) was not needed to prove the bug — the
   sequential premature-free is fully deterministic.

## Threat model

Any dmsg peer of a disk iocom: a remote HAMMER2 cluster node routed by
the `hammer2 service` daemon (which DIOCRECLUSTERs every disk at boot,
sbin/hammer2/cmd_service.c:898), or any local process holding the peer
end of the cluster pipe. Same position as DF-0117. The attacker controls
message contents, sizes (aux 512B..1MB), count, and timing → controls
which kmalloc zone is freed-under-DMA and when reclamation happens.
On this permissive guest (no SMAP/SMEP/KASLR) the primitive is a
classic exploitable UAF: content-controlled kernel heap object freed
while referenced by in-flight DMA, with attacker-driven reallocation.

## Exploit chain (memcorrupt → uid0 route)

1. Peer streams pipelined BLK_WRITEs in one transaction with aux size
   class C (victim object's zone).
2. First completion frees the newest C-sized buffer under the in-flight
   bio; subsequent message aux allocations (same zone, same thread)
   reclaim it; the pending DMA then *reads* the replacement object's
   bytes and writes them to attacker-chosen disk offsets — kernel heap
   disclosure to disk (proven: "cals", 53 ff 00 f0).
3. Repeating with target zones yields pointer/contents disclosure;
   combined with write-side grooming (the same freed slot can be
   re-detached), full kernel-memory corruption is reachable. uid0 was
   not attempted end-to-end on this guest because the peer already
   runs as root here; on a real cluster the peer is a remote node and
   the primitive is remote-kernel-memory corruption.

## Fix validation

fix.diff (per-bio aux ownership via bio_caller_info2.ptr) applied to the
guest /usr/src, kernel rebuilt (`make nativekernel`), PoC re-run:
readback shows every slot with exactly its own pattern and dmsg_disk
usage returns to baseline (see run.fix.log). Bug dead.
