# VERDICT — DF-5358

**Status: reproduced** (impact: panic — root-gated local DoS via /dev/kmem read;
confidence: certain)
**Fix: validated** (fix_status=fixed — panic eliminated, clean EFAULT, no
regression).

## What was claimed

Pass-2 audit of `sys/platform/pc64/x86_64/vm_machdep.c` flagged
`kvm_access_check()` (vm_machdep.c:1276-1291) as the pc64 consumer of a
`pmap_kextract()` (pmap.c:1865-1888) that performs no paging-level validity
check — the pc64 twin of DF-5245 (`pmap_extract`, "no live in-tree KVA caller")
and DF-5290 (vkernel `pmap_extract`/`pmap_kextract`, whose live caller is the
*vkernel* `kvm_access_check`). Two predicted failure modes:

1. ungrown KVA → `*vtopt(va)` recursive-map deref → kernel page fault → panic;
2. PT-present/PTE-0 → non-zero garbage → "pages resident" gate passes
   vacuously.

## How it was proven (guest: DragonFly 6.5-DEVELOPMENT #0 Jul 2 2026, X86_64_GENERIC)

### Baseline (stock kernel)

`pmxtest.ko` (build.sh, root):

```
pmxtest: KvaStart=fffff80040000000 KvaEnd=ffffffffff1ff000 KvaSize=000007ffbf1ff000
pmxtest[1]: CONTROL pmap_kextract(&dummy=0xffffffff826002d8) = 0000000135ab1278 (mapped, sane)
pmxtest[2]: va=ffffffff82601000 while-mapped pa=0000000134641000
pmxtest[2]: PT-present/PTE-0: aligned   pmap_kextract(va)     = 0000000000000000
pmxtest[2]: PT-present/PTE-0: unaligned pmap_kextract(va+0x30) = 0000000000000030  <-- garbage
pmxtest[2]: restored + freed
pmxtest[3]: kernel_vm_end=fffff80118800000
pmxtest[3]: PML4[496] present, PDPT[5] ABSENT (0000000000000000) -> /dev/kmem test KVA fffff80140000000
```

* The PTE-0 hole is manufactured deterministically: `kmem_alloc3()` one page,
  `pmap_kremove()` its PTE (kernel PT pages are never freed, so the level above
  stays present), extract, restore, free.
* **Honest correction to the initial analysis:** for the *aligned* addresses
  `kvm_access_check()` walks, `(pa & PG_FRAME)|(va & PAGE_MASK)` evaluates to 0
  (offset-in-page is 0), so the aligned PTE-0 case actually fails *closed* on
  pc64. The non-zero garbage appears only for unaligned callers (`0x30`
  observed) — exactly the DF-5245 defect class, but those in-tree callers
  (kern_memio.c:784/797/821 `user_kernel_mapping`) only query always-mapped
  kernel objects. So failure mode 2 is a *latent landmine* on pc64 today, not a
  live gate bypass.
* Failure mode 1 is live and lethal. `pmx_user` (pread /dev/kmem, 8 bytes) at
  the probed absent-PDPT KVA `0xfffff80140000000` (inside [KvaStart,KvaEnd),
  above `kernel_vm_end`, covered by the `VM_SUBSYS_RESERVED` VM_PROT_ALL entry
  `kmem_init()` (vm_kern.c:494-537) installs over all unallocated KVA — so
  `kernacc()` would NOT have rejected it either):

```
Fatal user address access from kernel mode from pmx_user at ffffffff80c0cdc6
Fatal trap 12: page fault while in kernel mode
fault virtual address = 0xffff80403e005000     <- recursive-map PDE-slot address (unmapped)
fault code           = supervisor read data, page not present
current process      = 1016
Stopped at pmap_kextract.part.23+0x16: movq (%rax,%rdx,1),%rdx
```

Call chain: `pread` → `mmrw` (kern_memio.c:257-290, minor 1) →
`kvm_access_check` (vm_machdep.c:1284 loop) → `pmap_kextract` (pmap.c:1872
`pt = *vtopt(va)`) → page fault, `pcb_onfault == NULL` → `db>` panic. Guest
down. **REPRODUCED.**

Reachability gating: `/dev/kmem` open requires
`caps_priv_check(SYSCAP_RESTRICTEDROOT|__SYSCAP_NOROOTTEST)` (kern_memio.c:152);
reads are allowed at any securelevel/kernel_mem_readonly (only FWRITE is
blocked, kern_memio.c:155-158). So the panic is root-gated, not
securelevel-gated. Impact ceiling: privileged local DoS (Low per the audit
rubric). No memory-corruption primitive: the faulting access is a read of an
*unmapped* address inside the validator itself; the garbage-PA variant returns
a bogus PA that pc64 `kvm_access_check` only compares against 0 (it is not used
as a DMA target), and the aligned walk fails closed. No uid=0 chain exists or
was pursued (triggering already requires root).

### Fix validation

`fix.diff` replaces the recursive-map deref with a DMAP walk of the four levels
with `PG_V` required at each (returning 0 when absent). Applied to the guest's
/usr/src (`patch -p1`), `make -j6 nativekernel KERNCONF=X86_64_GENERIC` +
`make installkernel` (rc=0, Werror clean), reboot.

An intermediate fix (v1) skipped the PDPT→PD level (fed the PDPE to
`pmap_pt_to_pte`) and panicked at boot (`vm_page_free_contig: wire_count == 1`
via `ata_dmaalloc`) — my authoring bug, not a kernel issue; recorded here for
honesty. The final fix.diff (v2) walks all four levels.

Patched kernel (build #1 Oct 3 2026):

```
pmxtest[1]: CONTROL pmap_kextract(&dummy=...) = 000000013218e2d8      (sane)
pmxtest[2]: PT-present/PTE-0: unaligned pmap_kextract(va+0x30) = 0000000000000000   (garbage gone)
pmxtest[3]: ... -> /dev/kmem test KVA fffff80140000000
$ ./pmx_user 0xfffff80140000000 8
pmx_user: pread returned -1 errno=14 (Bad address)
pmx_user: kernel survived (no panic)
$ ./pmx_user 0xffffffff811449e0 24        # control: kernel `version` symbol
pmx_user: pread returned 24 errno=0        # /dev/kmem still reads mapped KVA
```

Baseline panic eliminated; clean EFAULT; no false negatives on mapped KVA; the
whole system boots and runs (vtophys() == pmap_kextract(), so every busdma/
contigfree path exercised the new walk during boot + build).

## Why the other cited paths in the file are clean (pass-2 negative results)

* cpu_fork LDT paths (vm_machdep.c:118-123, 221-227) — dead on pc64:
  `pcb_ldt` has no writer; `user_ldt_alloc/free/set_user_ldt` are panic stubs
  (userldt.c:42-58); sysarch.c has no LDT ops on x86_64.
* cpu_fork PCB copy — `union savefpu pcb_save` is embedded in struct pcb
  (pcb.h:80) and `td_savefpu = &td_pcb->pcb_save` (pmap.c:2250), so `*pcb2 =
  *pcb1` copies FPU state by value with no aliasing; pcb_ext is NULLed
  (vm_machdep.c:218) and never set on pc64 (cpu_lwp_exit's KKASSERT at :311
  can't fire); pcb_onfault is NULL in fork context.
* cpu_prepare_lwp's user-controlled copyout (vm_machdep.c:262-265) —
  std_copyout (support.s:228-279) rejects dst/len outside user range (carry +
  VM_MAX_USER_ADDRESS), so a kernel-range lwp_stack returns EFAULT, not a write.
* PCB_DBREGS/dbregs — set_dbregs (machdep.c:3133) re-validates addresses vs
  VM_MAX_USER_ADDRESS for unprivileged users; fork inheritance copies within
  identical credentials (no privilege crossing); swtch.s:210-236/592-618
  save/restore protocol is flag-consistent with cpu_lwp_exit's reset_dbregs
  (vm_machdep.c:316-319).
* spectre/mds sysctl parsers (vm_machdep.c:654-752, 1045-1146) — root-only
  (kern_sysctl.c:1447 SYSCAP_NOSYSCTL_WR), 128-byte buffer with `len >=
  sizeof(buf)` rejection and NUL termination; strsep loop safe.
* cpu_vmspace_alloc/free — empty stubs on pc64 (vm_machdep.c:1266-1273); the
  VMSPACE_CTL_* intercept lives in vm_vmspace.c (already pass-2 audited).
* kernacc int-truncation at vm_machdep.c:1288 (`eaddr - saddr` → int param of
  kernacc) — real type defect, but unreachable in isolation: any /dev/kmem
  transfer ≥ 2^31 spans unmapped KVA, so the (correct or panicking) page walk
  decides first; with a correct walk it returns EFAULT before kernacc's
  truncated length matters. Killed (negative note), consistent with DF-0081's
  INVALID resolution of the uiomove side.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
