# DF-3485 VERDICT — REPRODUCED (panic, deterministic, root-only)

## Bottom line

Creating the first `ng_iface` node on the stock kernel panics instantly and
100% deterministically: `panic: trying to free NULL pointer` from
`ng_iface_constructor+0x3a2` (= the `kfree(ng_iface_units, M_NETGRAPH)` at
sys/netgraph/iface/ng_iface.c:295, reached through the inlined
`ng_iface_get_unit()`). Root cause is a **compiler-visible UB pattern**, not
a stale binary: `bcopy(ng_iface_units, ...)` at ng_iface.c:290 feeds
`__builtin_memmove()` a pointer that is NULL on the first call; per the
in-tree contract in sys/sys/systm.h:257-260 GCC then assumes the pointer is
non-NULL and deletes the `if (ng_iface_units != NULL)` guard at :294, so the
compiled module calls `kfree(NULL)` unconditionally on the array-growth path
— which is exactly the first-creation path. `kfree(NULL)` is an
unconditional `panic()` (sys/kern/kern_slaballoc.c:1406-1407), so production
(non-INVARIANTS) kernels are equally affected.

## How it was proven

1. **Live reproduction** (fresh boot, stock modules):
   `kldload ng_socket ng_iface; ngctl mkpeer . iface inet inet` →
   instant panic, guest dead, trace in panic.txt. Reproduced on every
   attempt (3/3 across independent boots; also independently observed
   during the DF-3482 ng_ppp run).
2. **Machine-code proof**: objdump of the *shipped* module
   (disasm.stock.txt) shows the load of `ng_iface_units` at +0x31d flowing
   into `call _kfree` at +0x39d with **no NULL test in between**
   (relocations identify `_kfree`, `M_NETGRAPH`, and the `.bss` globals).
   The return address `+0x3a2` in the panic trace matches this exact call.
   DWARF maps the call to ng_iface.c:295 via the inlined `ng_iface_get_unit`.
3. **Not a stale binary**: rebuilding the module from the unmodified
   /usr/src (md5-identical to the audit tree) reproduces the identical
   unguarded pattern — this is what the current tree compiles to.
4. **Why the guard vanished**: systm.h:257-260 documents that
   `bcopy()`→`__builtin_memmove()` lets GCC "optimize-out later NULL tests"
   on the operands — precisely what happened to the :294 guard.
5. **Fix validation** (fix.diff, module rebuild only):
   - disasm.fixed.txt: a real `test/je` now dominates both bcopy and kfree.
   - Live: with only this fix, the free-NULL panic is GONE; creation now
     panics later at the if_attach() call (ng_iface.c:605) with the
     distinct DF-3486 signature (panic.after_fix.txt).
   - With DF-3486's fix too, the full ng_iface lifecycle (create, list,
     ifconfig ng0, shutdown incl. deferred if_detach, 22+ rapid
     create/destroy cycles) succeeds without any panic
     (findings/poc/DF-3486/fix_run.log).

## Reachability / severity

- Trigger: root only. The only entry to node creation is a netgraph control
  message; AF_NETGRAPH control sockets require
  `caps_priv_check(SYSCAP_RESTRICTEDROOT)` (ng_socket.c:172-176) — verified
  live: uid-1001 `ngctl` gets EPERM (unpriv_check.log).
- Impact: deterministic kernel panic (DoS) by an already-privileged user;
  no memory corruption occurs (the allocator checks NULL before touching
  anything). Severity Low.

## Relation to DF-3482's lead

The DF-3482 lead said ng_eiface panics with "trying to free NULL pointer at
ng_eiface_constructor+0xf6". That was an extrapolation from a truncated
console window; the actual ng_eiface panic is a different defect (DF-3486).
This pack fixes the *iface-only* NULL-free. ng_eiface has no get_unit bitmap
and is not affected by DF-3485's root cause.

## Residual notes

- `ng_iface_units` bitmap itself remains unsynchronized, but v1 node
  creation is serialized on the netisr control path, so no concurrent
  access exists today.
- The B-only regression observed mid-validation (free-NULL panic returning
  after a crash) was HAMMER2 write-loss reverting /usr/src edits made ~60s
  before a panic — an environment artifact, documented here so nobody
  chases it as a "fix regression".

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