{
  "finding_id": "DF-3426",
  "status": "reproduced",
  "reproduced": true,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "REPRODUCED at runtime after making the production netmap KLD buildable (verbatim sources; two infrastructure-only port shims: pointer-sized WNA side table because this trees struct ifnet has no if_unused7, and dev_open_args.a_fp->a_fpp). Driven through the real /dev/netmap ABI on an unopened tap(4) port whose tapifstart()->ifsq_purge() frees every dispatched mbuf synchronously inside ifq_dispatch() (deterministic TX completion). Three independent manifestations on the stock 6.5-DEVELOPMENT INVARIANTS guest: (1) unreg mode: 8 dispatches then close() -> generic_netmap_register(0) m_freem()s every pool entry -> netstat -m mbuf clusters in use drops 256->248 = exactly -8 double frees on a LIVE guest; (2) uaf mode (512-packet lap, 4096-datagram UDP spray, rewound second lap): generic_netmap_txsync reuses the dangling tx_pool[] entries, writes attacker bytes into recycled LIVE mbufs, double-dispatches them, and the run ends in Fatal trap 12: page fault while in kernel mode with the guest dead in DDB (reproduced twice); (3) evt mode: full lap + close underflows the mbuf in-use counter to 2^64-767 (~1024 double frees). Gating unchanged from the filing: /dev/netmap is 0660 root:wheel, so the attacker is root/wheel or a service granted the fd; unpriv->uid0 is not reachable through this node, severity stays High for kernel memory corruption.",
  "exploit_chain": "root/wheel (device gating, netmap.c:2237) -> open /dev/netmap -> NIOCREGIF tap0 (generic adapter, 1024-slot TX ring, tx_pool[] of m_getcl mbufs) -> NIOCTXSYNC lap 1: generic_xmit_frame() (netmap_freebsd.c:119-140) copies attacker bytes and hands each pool mbuf to ifq_dispatch() with the designs refcount retention #if 0d (netmap_freebsd.c:131-135); the tap purge frees them all synchronously; tx_pool[j] is never cleared (netmap_generic.c:504-548) -> UDP spray reallocates the freed mbuf+cluster objects as live network mbufs via the objcache magazine fast path (no ctor re-run, kern_objcache.c:493-517) -> lap 2 rewinds ring->cur over the dangling slots: m_len=0 write + m_copyback2 of attacker-controlled bytes + rcvif/hash into stolen live mbufs, second ifq_dispatch = double free -> ring exhaustion arms generic_set_tx_event on a freed entry (SET_MBUF_DESTRUCTOR write + m_freem = more double frees; detonates DF-3427s destructor in the recycled mbuf) -> kernel page fault, DDB. Primitive characterization: repeatable attacker-sized write into recycled mbuf headers/clusters + double objcache_put (two-owner mbuf) - classic mbuf-forgery escalation material from the permitted root/wheel context; a full ROP chain was not pursued since unprivileged->uid0 is not applicable through this device node.",
  "evidence": "run.log + panic_uaf_run2_serial.txt (decisive: RIG traces of both laps, ring-exhaustion arming, destructor entry, then Fatal trap 12 / Stopped at generic_mbuf_destructor+0x3f and DDB); panic_uaf_run1_serial.txt (first reproduction, fault 0x76); unreg_accounting.txt (counter-exact -8 double frees on a live guest); run_evt.log (netstat in-use = 18446744073709550849 = 2^64-767 after evt+close); fix_validation.log (patched module: identical PoC matrix -> rc=0 everywhere, counters exactly flat, set_tx_event arms on mbuf=0 and early-returns); build.log (full untrimmed module build); VERDICT.md (narrative)",
  "kernel_refs": [
    "sys/net/netmap/netmap_generic.c:504",
    "sys/net/netmap/netmap_generic.c:520",
    "sys/net/netmap/netmap_generic.c:538",
    "sys/net/netmap/netmap_generic.c:543",
    "sys/net/netmap/netmap_generic.c:559",
    "sys/net/netmap/netmap_generic.c:567",
    "sys/net/netmap/netmap_generic.c:573",
    "sys/net/netmap/netmap_generic.c:275",
    "sys/net/netmap/netmap_generic.c:361",
    "sys/net/netmap/netmap_freebsd.c:119",
    "sys/net/netmap/netmap_freebsd.c:124",
    "sys/net/netmap/netmap_freebsd.c:127",
    "sys/net/netmap/netmap_freebsd.c:131",
    "sys/net/netmap/netmap_freebsd.c:138",
    "sys/net/if.c:3344",
    "sys/kern/uipc_mbuf.c:1316",
    "sys/kern/kern_objcache.c:493"
  ],
  "poc_changes": "Previous attempt ended build_failed (module unbuildable: if_unused7). Repaired rig: (1) rebuilt the full production module from a copied tree with wna_shim.[ch] (pointer-sized WNA side table; netmap_kern.hs own WNA is honored via the #ifndef guard) and rig_port2.patch (a_fp -> *a_fpp); no logic under audit changed; (2) replaced the documentation-stub trigger with a real userspace rig driving NIOCREGIF/NIOCTXSYNC over mmapd rings (probe/evt/uaf/unreg modes, tunable laps + UDP spray); (3) added RIG_TRACE kprintfs (entry state prints + print_backtrace in the destructor) for mechanism capture.",
  "attempts": 6,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
  "runtime_sec": 3060,
  "guest_dirty": 0,
  "build_cmd": "scp rig files to guest:/root/df3426 && ./build.sh  # builds netmap-src/netmap.ko (verbatim production sources + 2 port shims + RIG_TRACE) and ./trigger; full log: build.log",
  "run_cmd": "./run.sh uaf 512 4096 16384   # panic; ./run.sh unreg 8 (live-guest -8 double frees); ./run.sh evt (2^64-767 underflow); ./run.sh probe (clean)",
  "code_hash": "c7fa707cf9661b7ef8de1b077cf35bb6e6fda102a11a7ef5f2c7dcce1aec7406",
  "notes": "Module-level fix validated without a kernel rebuild because the defect (and the fix) live entirely in the KLD; fix.diff additionally verified to apply cleanly (patch -p4 --dry-run) against the pristine sys tree and the guest /usr/src copy (md5 netmap_generic.c matches host: 5c1169dfc687831d5ceb0c973d78d53a). build.log and fix_build.log contain identical cc command lines (same flags; the fixed source differs only in the added line). Incidental observation, one line: the objcache magazine fast path not re-running cache ctors (kern_objcache.c hot path) is a systemic amplifier for ANY stale destructor/constructor state and deserves its own hardening review. DF-3427 was exercised with the same rig; its pack carries the destructor-ABI evidence (udp_input backtrace).",
  "recommended_fix": "Clear kring->tx_pool[j] = NULL immediately after generic_xmit_frame() (ownership has been transferred to ifq_dispatch()/the driver on both success and failure); the replenish paths already handle NULL entries. See fix.diff (validated).",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff (clear tx_pool[j] after dispatch) applied to the rig module: the identical PoC matrix that panicked the baseline (uaf 512 4096 16384) and underflowed its counters (unreg 8: -8; evt: 2^64-767) now runs rc=0 on all modes with netstat -m exactly flat (256/33552 before and after), both 4096+16384-datagram sprays complete, generic_set_tx_event arms on a NULL slot (RIG trace: mbuf=0) and returns early, and the guest stays up (fix_validation.log; fix2627 combined-fix rerun in fix_validation27.log). The bug is fixed by this one line because every sink (reuse, set_tx_event m_freem, unregister sweep) operates on tx_pool[] entries that now never alias driver-owned or freed memory.",
  "fix_evidence": "fix_run.log (= fix_validation.log: full before/after PoC matrix with netstat deltas and RIG traces); fix_validation27.log (combined fix26+fix27 module, all modes rc=0, 0 destructor calls); fix_build.log (fixed-module build); fix.diff (tree patch, applies clean with patch -p4); fix3426-rig.diff/fix2627-rig.diff (rig variants)"
}