# DF-4672 — VERDICT (verify run, 2026-10-15)

## Verdict

**REPRODUCED (impact: dos — permanent unprivileged wired-memory leak).**
Fix behaviorally VALIDATED on a rebuilt kernel (`fixed`).

The METEORSETGEO grow block of the **production, unmodified** `bktr.ko`
(`video_ioctl`, resolved at `0xffffffff82606190` from the stock module)
was driven by an **unprivileged user** (uid 1001) through the DF-1215-style
KLD rig (`bktrig2.ko` → `/dev/bktrig0`, 0666 — real `/dev/bktr0` is 0444,
both world-openable; no `priv_check` exists anywhere in the driver).

### Baseline (stock kernel #0, unfixed bktr.ko), `run.log`:

- grow storm: 14 strictly-larger `METEORSETGEO` ioctls (128-page steps)
  → buffers churn (KVA visible changing), all rc=0 as uid 1001.
- `vm.stats.vm.v_wire_count`: **155,684 → 207,846 = +52,137 pages
  (203.7 MiB) permanently wired**, never returned (unchanged after
  15 s settle; still 207,846 at the end of the run).
- The wire-count trace shows the two phases of the same leak on
  DragonFly: `contigmalloc` serves from the 256 MiB low-phys **DMA
  reserve** first (vm_page.c:2765 `vm_page_alloc_contig` →
  `vm_contig_alist`); reserve pages are pre-wired so the drain is silent
  (wire flat, grows #1-#32); once the reserve's contiguous blocks are
  exhausted the 3-pass PQ_FREE/PQ_CACHE scan wires normal pages
  (vm_contig.c:447 `vm_page_wire`) and **the wire count climbs per grow
  and never comes back** (grow #40 onward: +1511, +28234, +22392...).
- Re-confirmed on a fresh guest after `vm.sh reset with-src`:
  +107,297 pages (419.1 MiB) permanent wire growth from 15 unpriv grows
  (`wire climbed from grow #10`), exhaustion (`Cannot allocate memory`
  from `get_bktr_mem`) reached during the DF-4673 runs of the same boot.

### Root cause re-confirmed in source, line by line

- grow block: bktr_core.c:1667-1673 — `get_bktr_mem()` (contigmalloc,
  bktr_os.c:399-407) then bare `kmem_free(kernel_map, bigbuf, size)`.
- contigmalloc: vm_contig.c:560-582 → `vm_contig_pg_alloc` (wires the
  pages, vm_contig.c:447; for reserve service `vm_page_alloc_contig`,
  vm_page.c:2765) + `vm_contig_pg_kmap` (object-less
  `kmem_alloc_pageable` entry + `pmap_kenter_noinval`, vm_contig.c:527-531).
- the ONLY correct release: `contigfree()` vm_contig.c:588-603
  (`pmap_qremove` + `kmem_free` + `vm_page_free_contig`).
- bare `kmem_free`: vm_map.c:3300-3370 — for an object-less
  VM_MAPTYPE_NORMAL entry with `wired_count == 0` NONE of the
  pmap_remove branches fire and no page release happens; the
  vm_kern.c:311-313 warning describes this misuse verbatim.
- Reserve variant: `vm_page_free_contig` (vm_page.c:2858-2877) is what
  returns blocks to `vm_contig_alist` — bare `kmem_free` never does, so
  every grow permanently drains the DMA reserve (256 MiB,
  vm_page.c:426-428) as well.

### Rig fidelity (what is production, what is synthetic)

| Element | Status |
|---|---|
| METEORSETGEO handler incl. grow block | **production, unmodified** (stock bktr.ko `video_ioctl`) |
| get_bktr_mem / contigmalloc / kmem_free | **production** (stock bktr.ko + kernel) |
| initial buffer installed by RIG_INIT | production `get_bktr_mem` + `bzero` — mirrors `common_bktr_attach` (bktr_core.c:393-446); size 32 pages instead of BROOKTREE_ALLOC_PAGES=868 to keep the leak measurable on the 4 GB guest (grow path is size-agnostic) |
| softc | synthetic zeroed `struct bktr_softc` + fake PCI register page for the SETGEO `OUTW`/`OUTL` (x86_64 bus_space = direct deref, bus_dma.h:139-141) |
| device node | rig 0666 vs real 0444 — both unpriv-openable |
| bt848 hardware / DMA | absent in QEMU (irrelevant for this finding — no DMA involved) |

## Fix validation (behavioral A/B)

`fix.diff` (in this pack): grow block uses `contigfree()` for the old
buffer, installs new buffer before freeing, zeroes it. Built as
`DF467XFIX` = X86_64_GENERIC + `device bktr` + fix (`make -j6
nativekernel`, rc=0; installkernel; reboot). With the same rig (nodep
variant, production symbols now kernel globals) and the same unpriv
attack: **wire delta ≈ 0 pages** across the same grow storm (see
`fix_run.log`), buffer contents zero after every grow. Baseline bad
behavior GONE ⇒ `fix_status: fixed`.

## Files

| File | Purpose |
|---|---|
| `poc_wireleak.c` | phase-aware unpriv PoC (reserve drain → wire climb → exhaustion) |
| `bktrig2.c` | KLD rig: production `video_ioctl` + production `get_bktr_mem` init |
| `run.log` | decisive baseline run (full untrimmed output) |
| `fix_run.log` | same attack on the DF467XFIX kernel |
| `fix.diff` | verified fix (never applied to the read-only sys/ tree) |
| `env.txt`, `manifest.json`, `verdict.json` | environment + machine records |

## 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.
