# DF-5162 — bktr_mem registry never invalidated when METEORSETGEO grow frees the frame buffer → freed KVA reinstalled and bzero()ed/DMA-written after driver reload

## Verdict (short)

**Not reproduced on the audit guest — the guest has no Bt848/Bt878 capture card
(`NO_BT8XX_PCI_DEVICE`, no `/dev/bktr*`), so the trigger cannot run.** The
defect itself is **proven at source level (certain)**: the grow path frees the
attach-time frame buffer without updating the bktr_mem registry, and the
registry has *no invalidation mechanism at all*. Every line of the chain is
cited below; on real hardware the reload deterministically writes ~3.5 MB of
kernel KVA that was returned to the kernel map by `kmem_free()`.

## File under audit

`sys/dev/video/bktr/bktr_mem.c` — the module whose *entire purpose* is to keep
DMA buffer addresses valid across `bktr.ko` unload/reload. The lifetime defect
lives in its (missing) invalidation contract and in its only consumer,
`bktr_core.c`, violating it.

## The chain, line by line

1. **Attach stores all five buffers** — `common_bktr_attach()`,
   `sys/dev/video/bktr/bktr_core.c:415-419` (under `#if defined(KLD_MODULE)`):
   `bktr_store_address(unit, BKTR_MEM_BUF, buf)` etc. `buf` here is the
   `contigmalloc`'d `BROOKTREE_ALLOC` (217*4 pages = 868 pages ≈ 3.5 MB)
   buffer from `bktr_core.c:404`.

2. **Registry API can only set, never clear** — `sys/dev/video/bktr/bktr_mem.c:103-138`:
   `bktr_store_address()` sets `addresses_stored = 1` (lines 115/119/123/127/131);
   no function in the file ever clears it or invalidates a stored pointer.
   (`bktr_has_stored_addresses()` bktr_mem.c:89-98, `bktr_retrieve_address()`
   bktr_mem.c:142-167 return the raw stored values.)

3. **Unprivileged grow frees the stored buffer** — METEORSETGEO handler,
   `sys/dev/video/bktr/bktr_core.c:1655-1675`:
   ```c
   buf = get_bktr_mem(unit, temp*PAGE_SIZE);          /* 1667 */
   if (buf != 0) {
       kmem_free(kernel_map, bktr->bigbuf,            /* 1669 — OLD buffer freed */
           (bktr->alloc_pages * PAGE_SIZE));
       bktr->bigbuf = buf;                            /* 1672 */
       bktr->alloc_pages = temp;                      /* 1673 */
   ```
   **No `bktr_store_address(unit, BKTR_MEM_BUF, buf)` follows.** The registry
   entry for `BKTR_MEM_BUF` now holds freed KVA with `addresses_stored == 1`.
   Device node is `make_dev(&bktr_ops, unit, 0, 0, 0444, "bktr%d", unit)`
   (`sys/dev/video/bktr/bktr_os.c:325`) — world-readable; ioctls are permitted
   on an `O_RDONLY` fd, so the grow is unprivileged (consistent with pass-1
   findings DF-3789/DF-4671 which treat METEORSETGEO as unprivileged).

4. **Detach relies on the registry** — `sys/dev/video/bktr/bktr_os.c:362-364`:
   "We do not free memory for RISC programs, grab buffer, vbi buffers. The
   memory is retained by the bktr_mem module so we can unload and then reload
   the main bktr driver module." The invariant this comment depends on was
   broken in step 3.

5. **Reload reinstalls the freed pointer** — `common_bktr_attach()` again,
   `sys/dev/video/bktr/bktr_core.c:382-390`:
   `bktr_has_stored_addresses(unit) == 1` → retrieves all five addresses,
   `need_to_allocate_memory = 0` → `buf` = the freed KVA.

6. **Immediate deterministic write-after-free** — `sys/dev/video/bktr/bktr_core.c:429-432`:
   ```c
   if ( buf != 0 ) {
       bktr->bigbuf = buf;                            /* freed pointer */
       bktr->alloc_pages = BROOKTREE_ALLOC_PAGES;
       bzero((caddr_t) bktr->bigbuf, BROOKTREE_ALLOC); /* ≈3.5 MB zeroing of
                                                           possibly-reallocated
                                                           kernel KVA */
   ```
   Subsequently, `read()`, `mmap()` (bktr_os.c:669-690) and the RISC DMA
   program builders target the same freed range as soon as capture starts:
   the DMA writes attacker-shaped video data into whatever now occupies that
   KVA.

## Threat model

- Step 3 (plant the stale entry): **unprivileged** local user with access to
  `/dev/bktr0` (0444).
- Steps 4-6 (harvest): requires **root** to `kldunload bktr && kldload bktr`
  — a mundane admin/driver-recycling operation, not an unusual privilege.
  There is no unprivileged-only path to the reload (attach only runs at
  module load / bus probe).
- Requires a Bt848/878-family capture card (PCI).

Because the unprivileged user cannot complete the chain alone, this is
**Low severity**, not a privesc: the payoff for the root actor is silent
kernel-memory corruption of its own kernel. It is nonetheless a genuine
use-after-free lifetime defect in the module whose sole job is lifetime
management, with a deterministic multi-megabyte write sink.

## Why not reproduced on the guest (and what was verified instead)

- `build.log`/`run.log`: trigger compiles cleanly on the guest
  (DragonFly 6.5-DEVELOPMENT X86_64_GENERIC) and fails exactly at
  `open("/dev/bktr0") → ENOENT`.
- `env.txt`: `pciconf -lv` shows no Brooktree/Bt87x PCI device; `devinfo`
  has no bktr; `/boot/kernel/bktr.ko` and `bktr_mem.ko` exist (the modules
  load, but no probe ever attaches, so `common_bktr_attach` never runs).
- Fix validation is therefore **not_testable** on this guest (no baseline
  possible). The fix is a two-line source correction whose effect is provable
  by inspection: after it, the registry always holds either 0 or a live
  allocation.

## Fix

`fix.diff` (against the read-only `sys/` tree, never applied there):
re-store the new buffer in the grow path, and clear the stored BUF address
when the new allocation is bigger but the realloc fails/doesn't happen —
keeping `memory_list[unit].buf` always pointing at the live `bktr->bigbuf`.
(Related bookkeeping wart kept out of scope: on reload `alloc_pages` is
reset to `BROOKTREE_ALLOC_PAGES` regardless of a prior grow — harmless
over-allocation at worst, since grow only triggers when `temp > alloc_pages`.)

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