# DF-5244 — pmap_unwire() PTE teardown race vs backing-scan removal (double vm_page_unwire / PHYS_TO_VM_PAGE(0) OOB)

## What
`pmap_unwire()` (user branch, sys/platform/pc64/x86_64/pmap.c:5627-5635) clears
PG_W with `atomic_clear_long()` and then **re-reads** `*ptep & PG_FRAME` as two
separate operations. Concurrent cmpset-based PTE teardown
(`pmap_remove_all()`, pmap.c:4816 — reached cross-process from
`msync(MS_INVALIDATE)` → `vm_object_page_remove()` → wired-page branch
(vm_object.c:1543) → `vm_page_protect(p, VM_PROT_NONE)`) takes neither the
victim pmap's vm_map lock, nor the pt_pv, nor the PTE placemarker, so nothing
serializes the two sides.

Consequences of the interleaving:
1. B's cmpset lands between A's W-clear and A's frame re-read → A computes
   `m = PHYS_TO_VM_PAGE(0) = &vm_page_array[atop(0) - first_page]`
   (index −1 when first_page > 0) → out-of-bounds vm_page_t handed to
   `vm_page_busy_wait()`/`vm_page_unwire()` (kernel heap corruption).
2. A returns the correct page but both sides unwire the single wire →
   `panic("vm_page_unwire: invalid wire count: 0")` (proven, see panic.txt).

## Trigger privilege
**root-only on stock DragonFly**: mlock()/mlockall()/MAP_WIREFUTURE are gated
by `SYSCAP_RESTRICTEDROOT` (sys/vm/vm_mmap.c:1030,1196) because pc64 does not
define `pmap_wired_count`. On any configuration where unprivileged users can
wire memory (the common BSD state — DF is the outlier here), this is an
unprivileged local kernel-memory-corruption/panic.

## Build
    cc -O2 -pthread -o df5244 df5244.c

## Run (as root on the DragonFly guest)
    ./df5244 6

6 pairs of processes: "A" mlock/munlock on a 1 MB MAP_SHARED hammer2 file
mapping; "B" msync(MS_INVALIDATE) on its own mapping of the same file.

## Expected output
Stock kernel: races for >20 minutes in this audit without hitting (the window
is only a few instructions wide). With the audit instrumentation kernel
(instrument.diff — a ~10 µs delay inserted between the W-clear and the frame
re-read in pmap_unwire), the guest panics within ~40 s:

    panic: vm_page_unwire: invalid wire count: 0
    vm_page_unwire() at vm_page_unwire+0xa9
    vm_page_unwire() at vm_page_unwire+0xa9
    vm_fault_unwire() at vm_fault_unwire+0xc6
    vm_map_user_wiring() at vm_map_user_wiring+0x30a
    sys_munlock() at sys_munlock+0x80
    syscall2() at syscall2+0x11e

With fix.diff applied (atomic cmpset clear-and-capture) **plus the same
10 µs delay inside the cmpset loop**, the identical stress runs clean
(8+ minutes verified vs <40 s to panic unfixed).

## Files
    df5244.c          final PoC source (A/B process pairs)
    instrument.diff   kernel instrumentation (widens the race window; NOT a fix)
    fix.diff          the verified fix (git-apply-able, never applied to sys/)
    panic.txt         captured panic from the instrumented baseline kernel
    serial_panic.log  full serial console capture
    run.stock.log     stock-kernel attempt (no hit in ~20 min)
    run.fixed.log     fixed-kernel run log (no panic)
    env.txt           guest uname
    verdict.json      machine verdict
