# DF-4106 — PoC evidence pack (verify run, 2026-10-14)

Finding: `sys/dev/netif/vge/if_vge.c` — `vge_rxeof()` SOF branch ignores
the `vge_newbuf()` failure (if_vge.c:1294): the just-linked head-chain
mbuf remains in `vge_rx_mbuf[]` and the descriptor still points at its
cluster → mbuf double-reference → double free / UAF / double-queued
(cyclic) chain.

## How it was verified

The audit guest has **no VIA VT612x hardware**, but the driver is
**compiled into the running kernel** (`device vge` in X86_64_GENERIC),
so the production static handlers exist in the kernel image:

```
ffffffff808d2d60 t vge_dma_map_addr
ffffffff808d2480 t vge_newbuf
ffffffff808d2680 t vge_rxeof
```

The KLD rig (`df4106_vge_poc.c`) contains **zero driver code**: it

1. fabricates the exact post-`vge_attach`/`vge_init` softc state —
   verbatim replay of `vge_dma_alloc()` (if_vge.c:738-889, including
   the production RX-tag-sized-VGE_TX_LIST_SZ quirks) using the
   kernel's own `vge_dma_map_addr()`; a real `lwkt_serializer` as
   `ifp->if_serializer` (production: `vge_intr` runs `vge_rxeof` under
   it, and the INVARIANTS build asserts it); a scratch page standing in
   for the MMIO BAR (the only chip access in the RX path is the
   residue-count CSR write at if_vge.c:1408);
2. fills the ring with 256 **real** `m_getcl()` mbufs via a verbatim
   replay of `vge_rx_list_init()` (if_vge.c:1195-1219) that calls the
   **kernel's own `vge_newbuf()`**;
3. simulates the NIC exactly as the driver observes it: descriptor
   OWN-bit/status writes (a 3-fragment 6132-byte jumbo — multi-fragment
   RX is always reachable because `vge_init` unconditionally enables
   `VGE_RXCTL_RX_GIANT`, if_vge.c:1837);
4. imposes the **production failure condition**: drains the mbuf
   cluster pool on the dispatch CPU until `m_getcl(M_NOWAIT)` fails —
   the kernel itself logs `Warning: objcache(mbuf pkthdr+cl)
   exhausted on cpu1!` — i.e. the `ENOBUFS` path of `vge_newbuf()`
   (if_vge.c:1111-1113);
5. **dispatches the running kernel's own `vge_rxeof()`** (address
   injected via sysctl from `nm /boot/kernel/kernel`).

## Scenarios

**A — incomplete multi-fragment frame** (SOF fragments delivered,
frame not yet complete): both SOF-branch `vge_newbuf()` calls fail and
are ignored → `sc->vge_head` chain holds m0,m1 **and** `vge_rx_mbuf[0..1]`
still reference them (`DOUBLE-REFERENCE`), `vge_rx_consumed` stuck at 0,
slots 0/1 never re-OWNed. Ring wrap + one more dispatch links m0 into
its own chain again (`CYCLE DETECTED`: m0→m1→m0→m1…). The verbatim
`vge_stop()` tail (if_vge.c:2102-2125) then frees the head chain (m0,m1)
and frees `vge_rx_mbuf[0]/[1]` (== m0,m1) again (`DOUBLE FREE`).
Post-scan: **one mbuf returned to two owners** (`ALIAS CONFIRMED`).

**B — completed jumbo frame**: the EOF branch (if_vge.c:1328-1337)
`m_freem()`s the head chain {m0,m1} while the failed SOF newbufs left
`vge_rx_mbuf[0..1]` **dangling** (==m0/==m1: YES). After a
protocol-faithful slot-2 re-arm (3 completed frames push the 4-window
OWN onto slots 2..5 per if_vge.c:1161-1168) and ring wrap, the next
`vge_rxeof()` walk **writes through the dangling entries** (m->m_len,
m_flags) and **delivers the freed mbufs to `ifp->if_input`**
(if_vge.c:1395): `if_input chain: [m0 len=2044][m1 len=2040]` — both
already freed. Owner-side frees then turn the theft into pool aliasing
(`ALIAS CONFIRMED` again).

**PANIC variant** (`run.sh panic`): after the proofs, the aliased-mbuf
primitive is steered — a write through one owner's mbuf header
(stand-in for attacker influence over the twin object) sets
`m_next = 0x4141414141414141`; the other owner's completely normal
`m_freem()` follows it → **Fatal trap 9 (general protection fault) at
`m_free+0x10: movswq 0x20(%rdi),%rax`** — kernel deref of an
attacker-chosen pointer.

## Build / run (in guest, as root)

```
sh build.sh
kldload ./df4106_vge_poc.ko
sh run.sh baseline     # corruption proofs, guest SURVIVES
# fresh boot for this one (it traps the kernel):
kldload ./df4106_vge_poc.ko && sh run.sh panic
```

`run.sh` resolves the live symbol addresses itself
(`nm /boot/kernel/kernel`). The rig leaves the mbuf pool balanced
(phantom cache entries from the demonstrated double frees are consumed
exactly once), so the guest remains usable after the baseline run.

## Fix validation

`fix.diff` (authored after verification): the SOF branch replenishes
the descriptor FIRST and, on failure, keeps the current mbuf in the
descriptor exactly like the EOF branch. Applied to the guest's
`/usr/src`; kernel rebuilt (`make nativekernel KERNCONF=X86_64_GENERIC`)
and rebooted; the same rig re-run against the **patched kernel's own
`vge_rxeof`** must show: no DOUBLE-REFERENCE (head stays NULL), no
cycle, no dangling entries, **NO ALIAS FOUND** in either scenario.
See `fix_run.log`.

## Files

- `df4106_vge_poc.c` — the KLD rig (no driver code; dispatches the
  running kernel's own handlers)
- `Makefile`, `build.sh`, `run.sh`
- `build.log` — full untrimmed build output
- `run.log`, `run.2.log`, `run.3.log` — baseline runs (guest survives)
- `panic.txt` — serial console of the panic variant (Fatal trap 9 in
  `m_free` following the steered `m_next`)
- `fix.diff` — the verified fix (git-apply-able)
- `fix_build.log`, `fix_run.log` — patched-kernel build + re-run
- `env.txt` — guest environment
- `VERDICT.md`, `manifest.json`, `verdict.json`
