# DF-3271 — netgraph7 `ng_ID_rehash`/`hashdestroy` panic under node churn

## What happens

Repetitive, completely ordinary netgraph7 node creation (socket node + `mkpeer`
deflate nodes, i.e. what any ngctl script or MPD does when graphs are set up and
torn down) panics INVARIANTS kernels:

    panic: hashdestroy: hash not empty
    hashdestroy() at hashdestroy+0x3a
    hashdestroy() at hashdestroy+0x3a            <- inlined ng_ID_rehash
    ng_make_node_common() at ng_make_node_common+0x21f
    ng_make_node() at ng_make_node+0x3b
    ng_apply_item() at ng_apply_item+0x666
    ngthread() at ngthread+0x18

Guest dies in DDB; ssh gone; `vm.sh status` => down. Reproduced 4 times
(panic.txt), including on a FRESH guest with only the netgraph7 module stack
loaded (occurrence 3).

## Where

`sys/netgraph7/netgraph/ng_base.c`:

- `ng_make_node_common()` (:618) increments `ng_nodes` and calls
  `ng_ID_rehash()` when `ng_nodes * 2 > ng_ID_hmask` (:656-658).
- `ng_ID_rehash()` (:973-999) moves every node from the old `ng_ID_hash` into a
  doubled table (under `#ifdef INVARIANTS` it `LIST_REMOVE`s each node first,
  :986-994) and then calls `hashdestroy(ng_ID_hash, ...)` (:996).
- `hashdestroy()` (`sys/kern/kern_subr.c:348`) KASSERTs every bucket in
  `0..hashmask` is empty before `kfree`.

The KASSERT trips, i.e. at `hashdestroy` time the old table still looks
non-empty although the rehash loop just `LIST_REMOVE`d everything — consistent
with a corrupted/double-linked `nd_idnodes` list state that makes
`LIST_FOREACH_MUTABLE` + `LIST_REMOVE` leave a stale bucket head.

## What is NOT the (sole) cause — proven experimentally

`ng_base.c:996` passes the **wrong mask** to `hashdestroy`:

    hashdestroy(ng_ID_hash, M_NETGRAPH_NODE, ng_name_hmask);   /* should be ng_ID_hmask */

That is objectively a defect: when `ng_name_hmask > ng_ID_hmask` (old),
`hashdestroy` reads LIST heads past the end of the old ID-hash allocation
(kernel OOB read, up to `(name_hmask - ID_hmask)+1` buckets = e.g. 256+ bytes)
and KASSERTs on garbage; the sibling `ng_name_rehash()` (:964) passes the
correct mask, confirming the copy-paste origin. The one-line fix
(`ng_name_hmask` -> `ng_ID_hmask`, see fix.diff) was built and tested under the
identical repro loop — **the panic persisted** (occurrence 4 in panic.txt, with
the patched module proven active by the shifted `ng_make_node_common+0x21b`
frame offset). So the panic has a deeper cause in the rehash/list bookkeeping
that requires ddb-level debugging of the live hash state; the wrong mask at
:996 remains a real latent OOB-read defect that should be fixed regardless.

## Impact

- INVARIANTS/debug kernels: reliable kernel panic (local DoS) from privileged
  netgraph node churn (AF_NETGRAPH socket creation is root-only on this
  system). All 4 occurrences deterministic within a 12-iteration loop.
- Production (non-INVARIANTS) kernels: the KASSERT is compiled out; the
  observable symptom disappears, but if the underlying list-state corruption is
  real it may silently corrupt the ID hash (unresolved; needs upstream
  debugging). The :996 wrong-mask direction only reads/KASSERTs under
  INVARIANTS, so by itself it is debug-kernel-only.

## Reproduce

See repro.sh (needs the DF-3270 client binary `df3270_exploit`, whose
`-g 6` mode performs the churn loop). Guest must have the netgraph7 modules
built (see DF-3270 README). Expected on a stock INVARIANTS guest: panic within
<= 12 iterations (observed: iteration 2-3 once prior netgraph activity exists,
fresh guest also panics within the loop).

## Fix attempt (fix.diff) — INSUFFICIENT

    - hashdestroy(ng_ID_hash, M_NETGRAPH_NODE, ng_name_hmask);
    + hashdestroy(ng_ID_hash, M_NETGRAPH_NODE, ng_ID_hmask);

Built, loaded, re-tested: panic persists (occurrence 4). Recorded as
fix_status=fix_failed. Upstream needs to instrument `ng_ID_rehash` (dump old
bucket heads at destroy) to pin the list corruption — the double-linked-list
hypothesis (node linked into two buckets at rehash time) is the most consistent
with the KASSERT firing with the correct mask.

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