# DF-3426 — netmap generic TX pool mbuf UAF / double-free — VERIFIED

**status: reproduced | reproduced: 1 | impact: panic (kernel memory corruption) | confidence: certain**

The finding was previously closed `build_failed` (the stock netmap KLD does
not compile on this tree).  This run made the production module buildable in
a copied tree with two *infrastructure-only* port shims (pointer-sized WNA
side table + `dev_open_args.a_fpp`), loaded it into the stock
6.5-DEVELOPMENT INVARIANTS guest, and drove the real `/dev/netmap` ABI
(NIOCREGIF/NIOCTXSYNC on a tap(4) port with no reader, so
`tapifstart()->ifsq_purge()` "completes" TX — i.e. frees the mbuf —
synchronously inside `ifq_dispatch()`).

## What was demonstrated (unfixed production code)

1. **Double free, counter-exact, on a live guest** (`unreg 8`): 8 packets
   dispatched (each mbuf freed by tap purge inside `ifq_dispatch`), then
   close() → `generic_netmap_register(0)` `m_freem()`s every pool entry
   (`netmap_generic.c:275-280`) → the 8 dispatched slots freed a second
   time.  `netstat -m`: mbuf clusters in use 256 → **248 (exactly −8)**.
   No legitimate path decrements without an allocation.
   (unreg_accounting.txt)

2. **Ring reuse UAF + kernel page fault panic** (`uaf 512 4096 16384`):
   lap 1 dispatches 512 pool mbufs (all freed synchronously); a 4096-datagram
   UDP spray to 127.0.0.1 reallocates the freed combined mbuf+cluster objects
   as *live* network mbufs through the objcache magazine fast path; lap 2
   rewinds `ring->cur` so `generic_netmap_txsync()` walks over the dangling
   `tx_pool[0..510]` (`netmap_generic.c:504`) and `generic_xmit_frame()`
   (`netmap_freebsd.c:124-138`) writes `m_len=0`, 64 attacker-supplied bytes
   via `m_copyback2`, `rcvif`/`hash`, then hands the stolen live mbuf to
   `ifq_dispatch()` a second time.  At ring exhaustion
   `generic_set_tx_event` (`netmap_generic.c:421-445`) arms
   `SET_MBUF_DESTRUCTOR` on a freed pool entry and `m_freem()`s it.
   Result: **Fatal trap 12, page fault in kernel mode**, guest dead in DDB
   (run.log / panic_uaf_run2_serial.txt; reproduced twice).

3. **Mass double free at unregister** (`evt`): full lap + close →
   `netstat -m` in-use counter underflows to **18446744073709550849
   (= 2^64 − 767; net drift −1023)** with 128289406846% of mb_map "in use"
   — the mbuf accounting system itself testifies to ~1024 double frees.
   (run_evt.log)

## Gating (unchanged from the filing)

`/dev/netmap` is 0660 root:wheel (`netmap.c:2237`); the guest's unprivileged
user `maxx` is *not* in wheel.  The corruption is therefore reachable by
root/wheel (or any service granted the fd), not by an unprivileged user;
severity stays High for kernel memory corruption, consistent with the
filing.  From the permitted context the primitive is: (a) a double
objcache_put (two-owner mbuf) and (b) a repeatable attacker-sized write into
recycled mbuf headers/clusters — standard mbuf-forgery escalation material;
a full uid=0 chain from uid!=0 is not applicable through this device node.

## Fix validation (fix.diff = clear tx_pool[j] after dispatch)

Baseline (above) vs patched module, identical PoC matrix
(fix_validation.log):

| PoC                     | unfixed                       | fixed                    |
|-------------------------|-------------------------------|--------------------------|
| `unreg 8`               | clusters 256→248 (−8)         | **256→256 flat**         |
| `uaf 512 4096 16384`    | Fatal trap 12, guest dead     | rc=0, alive, flat        |
| `evt`                   | counter = 2^64−767            | flat; event arms on NULL |

Trace under the fix: `generic_set_tx_event e=… mbuf=0` → NULL-check early
return (no destructor install, no `m_freem` of driver-owned memory);
replenish paths (`:361`, `:505-512`) refill the NULL slots.  fix.diff
applies cleanly to the pristine `sys/net/netmap/netmap_generic.c`
(`patch -p4 --dry-run` OK).  fix_status: **fixed**.

## Rig faithfulness

The module is the verbatim production source; only two port shims (WNA
storage location; cdevpriv arg) and RIG_TRACE kprintfs were added — none
touch the TX-pool lifetime logic under audit (see build.sh, rig_trace.patch,
rig_port2.patch, wna_shim.[ch]).  Guest /usr/src and host sys/ tree md5
match on netmap_generic.c (5c1169dfc687831d5ceb0c973d78d53a).

## Environment

DragonFly dfbsd 6.5-DEVELOPMENT #0 X86_64_GENERIC (INVARIANTS), cc 8.3
[DragonFly], see env.txt.

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