# DF-2834 evidence pack — unbounded vmspace_create kernel-memory exhaustion

File: `sys/vm/vm_vmspace.c:146-201` (`sys_vmspace_create`)
Severity: Medium (CWE-770) — unprivileged local DoS, gated on `vm.vkernel_enable=1`

## What was verified

1. **No cap exists on the path** (static + dynamic):
   - `sys_vmspace_create` (sys/vm/vm_vmspace.c:146-201) consults no rlimit,
     no `priv_check`, no counter. The only gate is `vkernel_enable`
     (sys/vm/vm_vmspace.c:155). Contrast: mmap entries ARE capped by
     `vm.max_proc_mmap` (1000000 on this guest).
   - Empirical: 120,000 live vmspaces created by ONE unprivileged
     (uid 65534) process in 6.9 seconds, zero failures.

2. **Per-iteration cost** (line-accurate):
   - `kmalloc(sizeof(struct vmspace_entry))` — sys/vm/vm_vmspace.c:182
   - `vmspace_alloc()` — sys/vm/vm_vmspace.c:183 → sys/vm/vm_map.c:309-340,
     objcache `vmspace_cache` (sys/vm/vm_map.c:233)
   - `pmap_pinit()` — sys/platform/pc64/x86_64/pmap.c:2347
     * 8192 bytes pageable kernel KVA:
       `kmem_alloc_pageable(kernel_map, PAGE_SIZE * 2, VM_SUBSYS_PML4)`
       (pmap.c:2368-2370)
     * 1 WIRED PML4 vm_page: `pmap_allocpte(pmap_pml4_pindex())`
       (pmap.c:2376-2380) → `vm_page_wire(m)` (pmap.c:2757) — the page is
       wired and therefore unswappable/unreclaimable
   - `pmap_pinit2()` (sys/vm/vm_vmspace.c:187) is a NO-OP on x86_64
     (pmap.c:2509-2512) — refinement of the finding text, which listed it
     as a cost contributor
   - Measured totals: **2.374-2.376 wired pages per create** (9.7 KB) and
     **9752 bytes KVA per create** (cold), perfectly linear.

3. **Growth is unswappable while the vmspaces are live** — wired pages are
   not reclaimable by the page daemon; the only releases are
   `vmspace_destroy` or process exit (`vkernel_exit` → `rb_vmspace_delete`).

4. **Exhaustion extrapolation (NOT executed — single-tenant guest):**
   - guest physmem 4,257,873,920 B = 1,039,519 pages
   - measured rate 17,350 creates/s (120k in 6.915 s)
   - quiescent wirable pool (wire+free) ≈ 992,529 pages → ≈ 418,000 creates
     → **≈ 24 seconds for one unprivileged process to wire all 4 GB**
   - failure mode at exhaustion: `vm_page_alloc(VM_ALLOC_NORMAL)` inside
     `pmap_allocpte` (pmap.c:2751-2756) fails → `vm_wait(0)` loop; wired
     pages can never satisfy the wait → every `M_WAITOK` allocator
     system-wide stalls → hard wedge. No clean error return exists on this
     path.

## Files

- `vmsp_grow.c` — main PoC: unpriv create loop with wire/KVA/free sampling
- `vmsp_cap.c` — fix-validation helper: cap hit / destroy-frees-slot check
- `run.log` — stock-kernel runs (20k cold, 20k warm-reuse, 120k capped)
- `run.fixed.log` — patched-kernel A/B (cap, per-proc, raised-cap legit use)
- `fix.diff` — the verified fix (cap + ABI guards), applied only in the
  guest /usr/src scratch copy, never to the audit tree
- `build.log` — full compiler output (warning-free)
- `env.txt` — guest/kernel/compiler state
- `VERDICT.md` — full narrative and math
- `manifest.json`, `verdict.json` — machine-readable results

## Reproduce

```
build.sh                 # cc -O2 -Wall -o /tmp/vmsp_grow vmsp_grow.c
run.sh                   # enable sysctl, capped 120k unpriv run, restore
```

Success criterion (stock kernel): `# DONE created=120000 ... grow_exit=0`
with linear wire growth ~2.37 pages/iteration and zero failures.
