# DF-2685 VERDICT — mlock()/wiring faults zero-fill non-resident pages

## Status: REPRODUCED (stock) / FIXED (validated by kernel rebuild + rerun)

## How it was reproduced

Guest: DragonFly 6.5-DEVELOPMENT #0 (X86_64_GENERIC, INVARIANTS), 4GB RAM,
4GB swap, QEMU/KVM.

1. `mlockswap.c`: mmap 4.6GB MAP_PRIVATE|MAP_ANON, dirty with a
   page-index-derived pattern, sleep 8s (page daemon moves ~1GB to swap —
   `swapinfo` shows 1026M used).
2. `mlock()` the first 64MB (the earliest-swapped region).
3. Verify the pattern.

Stock kernel (`baseline_stock_run.log`):

```
/dev/vbd0s1b        4096M     1026M    3070M    25%    Interleaved
mismatch off=0 i=1 got=00000000 want=00000001
mismatch off=0 i=2 got=00000000 want=00000002
mlock-verify: bad=16253937 of 16777216 words
BUG REPRODUCED: mlock zero-filled swapped pages (silent data destruction)
rc=2
```

16.25M of 16.77M words are zeros; mlock() returned success. (First
reproduction on the previous boot: 16340977 bad words — see notes in
baseline log.) The kernel never reports anything: the corruption is silent.

## Why (line-level trace)

* `sys/vm/vm_mmap.c:1036` mlock → `vm_map_user_wiring()`
* `sys/vm/vm_map.c:2654` → `vm_fault_wire(map, entry, TRUE, 0)`
* `sys/vm/vm_fault.c:2632` → `vm_fault(map, va, VM_PROT_READ, VM_FAULT_USER_WIRE)`
* `sys/vm/vm_fault.c:383-385` `TRYPAGER(fs)` is false because
  `fs->fault_flags & VM_FAULT_WIRE_MASK` == VM_FAULT_USER_WIRE ≠ 0 → the
  pager is never consulted in `vm_fault_object()`
* `sys/vm/vm_fault.c:2327` `vm_page_zero_fill(fs->mary[0])` — a fresh page
  is zero-filled in the top object and
* `sys/vm/vm_fault.c:793` `pmap_enter()` installs it (wired per FW_WIRED).

The user's swapped data is never read back. POSIX (mlock(2): "pages ...
become resident") is violated with silent data destruction instead.

Cross-checks performed:
* File-backed variant: freshly-written file pages survive mlock (pages still
  resident in the vnode object — lookup succeeds, no pager needed), which is
  why this bug is normally masked; it fires exactly when pages are
  non-resident (swapped-out anon, reclaimed clean file pages).
* Unprivileged reachability on this x86-64 stock config: plain user gets
  EPERM (`sys/vm/vm_mmap.c:1029-1035`, SYSCAP_RESTRICTEDROOT — pc64 has no
  `pmap_wired_count`). The direct caller must be privileged; any
  memory-hungry local user can create the swap-out precondition for a
  privileged mlocker.

## Exploit chain

Not a memory-corruption primitive: this is silent user-memory destruction
(integrity/availability) in the process that performs the wiring. Attack
shape: local unprivileged user drives the box into swap pressure; a
privileged daemon that (re)locks its memory (ssh key material, database
buffers, crypto keys) silently loses it — keys become zeros with no error
anywhere (dmesg clean). Impact ceiling: authentication/DoS/data-loss in
root-owned security software; no kernel-memory disclosure or overwrite.

## Fix validation

`fix.diff` (TRYPAGER additionally allows `VM_FAULT_USER_WIRE` so wiring
faults page in from swap/vnode pagers like normal faults):

* baseline (stock): `bad=16253937`, rc=2 — see `baseline_stock_run.log`
* patched (same guest, kernel rebuilt with `make nativekernel`, rebooted):
  `OK: data intact after mlock`, rc=0 — see `patched_run.log`

Kernel-wiring (`VM_FAULT_CHANGE_WIRING`) still skips the pager (historical
deadlock avoidance for kernel wiring paths); the POSIX-defined user-wire
paths (mlock/mlockall/brk-under-MAP_WIREFUTURE) are the ones fixed. NVMM's
`vm_map_kernel_wiring()` of guest RAM remains exposed — noted in the finding
as residual hardening.
