# DF-2865 — VERDICT

**Status: reproduced (stock kernel) / fixed (patched kernel).**
**Impact: dos (reservation-counter integrity + transient I/O throttling
corruption). Not memory corruption.**

## What was run

KLD harness `pbufres.c` (6 kernel threads, one per CPU, reservation
counter cap = 1, `getpbuf_kva()`/`relpbuf()` hammered for 10 s) on the
single-tenant QEMU guest, stock INVARIANTS kernel
`DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026` (env.txt).

## Baseline (stock kernel) — run.2.log (and run.log, independent boot)

```
vm.pbres_neg: 2            # counter observed < 0 twice in 10 s
vm.pbres_min: -1           # reservation counter driven below zero
vm.pbres_maxinflight: 2    # TWO pbufs held simultaneously, cap was 1
PBUFRES: counter NEGATIVE (-1), cap=1, inflight=1     (console)
```

`maxinflight = 2` with `cap = 1` is the direct, race-proven statement
of the finding: two CPUs passed the `*pfreecnt <= 0` gate
(vm_pager.c:463) with the counter at 1 and both decremented
(vm_pager.c:489-490), exactly as read from the source.  The negative
counter is the observable residue of the same race.

Incidentally, `run.log` (first run, before the rescue sysctl existed)
also captured the *known* DF-0953 behaviour end-to-end: 5 threads
asleep on the reservation gate while `vm.pbuf_kva_count: 512` — pool
100 % idle — because `relpbuf()` only wakes `pfreecnt` sleepers on the
1→2 transition.  That stall is DF-0953's defect (do-not-re-report);
this finding is the allocator-side atomicity hole that *additionally*
drives the counter negative and past its cap.

## Patched kernel (fix.diff: cmpxchg reservation in all five allocators)

Combined kernel build `make -j6 nativekernel KERNCONF=X86_64_GENERIC`
(+ the DF-2866 pagertab hunk), see run.patched.log / fix_build.log:

```
vm.pbres_neg: 0
vm.pbres_min: 1            # never below the floor
vm.pbres_maxinflight: 1    # cap respected
```

The race window is closed: the reservation is taken with
`atomic_cmpset_int(pfreecnt, c, c-1)` re-validating `c > 0`; on
failure the buffer is put back and the outer gate re-entered.

## Why not higher severity

The counter is self-correcting (each over-decrement is matched by its
release), so the effect is transient over-consumption and extra
blocking latency on subsystems whose throttles exist to keep the
shared pools fair (swap-in, clustered VFS I/O).  No memory-safety
consequence; impact ceiling is local I/O-latency DoS — the same family
DF-0953 occupies, with a distinct root cause and fix.

## Kernel references

- sys/vm/vm_pager.c:417-422 (unlocked gate, getpbuf), :442-447 (raw
  decrement under per-bucket lock)
- sys/vm/vm_pager.c:462-467, :487-491 (getpbuf_kva)
- sys/vm/vm_pager.c:511-516, :536-540 (getpbuf_mem)
- sys/vm/vm_pager.c:564-578 (trypbuf), :596-610 (trypbuf_kva)
- Real-world counters: sys/vm/swap_pager.c:1447,163-166; sys/kern/
  vfs_cluster.c:924-927,1525-1527; sys/sys/mount.h:221 (u_int
  mnt_pbuf_count — also type-punned through the `int *` API);
  sys/sys/vnode.h:153.
