# DF-2672 VERDICT — REPRODUCED (leak), fix validated

## Question

Does `fork()` of a wired (mlock'd) entry leak a vm_object reference, and
does that translate into permanently unreclaimable kernel memory / swap?

## Root cause (path:line)

* `vmspace_fork_normal_entry()` clones the child entry and calls
  `vm_map_backing_replicated(new_map, new_entry, 0)` at
  **sys/vm/vm_map.c:3882** — with `flags == 0` this executes
  `vm_object_reference_quick(object)` on the base object
  (**sys/vm/vm_map.c:3510-3513**).
* `vm_map_copy_entry()` (**sys/vm/vm_map.c:3600**) then takes the wired
  path because `src_entry->wired_count != 0` (**:3608**) and discards the
  cloned object: `vm_map_backing_detach()` + `ba.map_object = NULL`
  (**:3627-3629**) **without `vm_object_deallocate()`**.  The stale
  comment at :3613-3615 ("its ref-count has not yet been adjusted") is
  wrong since the backing_ba rework — the reference IS taken before the
  call.  One reference is orphaned per fork.
* The orphaned object is never terminated, so `swap_pager_freespace()`
  never runs for it: dirty pages paged out under pressure keep their swap
  allocation forever.

## Reproduction (guest: DragonFly 6.5-DEVELOPMENT #0, INVARIANTS kernel)

`leak_fork.c`: mmap(ANON|PRIVATE) + memset (dirty) + mlock + fork();
child exits; owner exits.  Control omits the inner fork.

Decisive observations (run.log):

| phase | vm_object count | swap used (512B blocks) |
|---|---|---|
| baseline | 1.01K | 0 |
| control 100x4MB + pressure | 1.02K (+~10 noise) | 0 |
| leak 200x4MB | 1.21K (**+~190 for 200 forks**) | 0 (no pressure yet) |
| hog 3.4GB pressure | 1.21K (unchanged) | **1,033,784 (~504MB)** |
| +leak 50x4MB + pressure | 1.25K | 1,727,816 (~844MB) |
| after 30s idle, no anon-owning procs | 1.25K | 1,720,384 (persists) |

* Object growth ≈ 1 per fork-of-wired-entry, exactly as the code path
  predicts; control adds only kernel noise.
* ~844MB of swap permanently consumed with **no processes owning anonymous
  memory** (every leak_fork/hog exited).  This swap can never be freed
  except by reboot: the owning vm_objects are unreachable.

Unprivileged: `mlock()` needs no privilege, only RLIMIT_MEMLOCK
(default = free memory / 3, sys/kern/kern_plimit.c:131-132).  The PoC ran
as root on the guest but uses no privilege; mlock(256KB) succeeds for
normal users within that rlimit.

Impact: permanent, unbounded, aggregation-across-processes consumption of
kernel memory and swap by an unprivileged local user → system-wide
memory-exhaustion DoS (Medium).

## Fix validation

`fix.diff` adds `vm_object_deallocate(dst_entry->ba.object)` before
NULLing (mirrors vm_map_entry_dispose(), sys/vm/vm_map.c:1132-1135).
Kernel rebuilt in-guest (`make nativekernel`), rebooted, PoC re-run:
vm_object count flat across leak iterations, no residual swap.  See
fix section in run.log / verdict.json fix_* fields.

## Not a UAF

The bug only *leaks* references (object never freed early); there is no
premature-free / type-confusion angle.  Severity Medium (availability).
