# DF-3523 — VERDICT

**Status: reproduced** (stock kernel).  **Impact: dos** — permanent,
unprivileged, unbounded kernel-memory leak (one `struct ng_item` + one
node reference + one hook reference per cancelled armed pptpgre timer);
at ~30k cycles the `objcache(netgraph)` exhausts and the leaked node
refs drive the known DF-3514 idhash-rehash **kernel panic** (observed
in this run, panic.txt).  **Fix validated** (two layers, kernel #1 +
ng7 module rebuild): identical workload -> leak count flat (3).

## Root cause (certain; TWO stacked defects)

`ng7 ng_callout()` (ng_base.c:3248-3268) parks an ng_item holding
`NG_NODE_REF` + `NG_HOOK_REF` as the callout argument.
`ng_uncallout()` (ng_base.c:3273-3296) frees it only inside
`if (rval > 0)`, and even then only via `NG_FREE_ITEM(item)`.

1. **Layer 1 — DF-3515 contract violation (stock kernel):**
   `callout_stop()` of an ARMED, not-yet-fired callout returns 0
   (kern_timeout.c `_callout_update_spinlocked` CALLOUT_STOP branch
   unloads the callout without setting CALLOUT_PREVENTED;
   `_callout_cancel_or_stop` then early-exits `return 0` at :888-891).
   `callout_cancel()` has the same early-exit (verified experimentally:
   swapping stop->cancel did not fix anything).  So on stock, the
   cleanup is never reached.
2. **Layer 2 — ng7 port defect (any kernel):** `NG_FREE_ITEM` in
   netgraph7 is a flag-only macro (netgraph.h:817-820; the same defect
   DF-3513 found in `NG_BT_ITEMQ_DRAIN`).  The real destructor is
   `ng_free_item()` (reached via `ng_unref_item`, netgraph2.h:77-94);
   the callout-arg item has refs==0 (born zeroed, never sent), so the
   correct release is `ng_free_item(item)` directly.  Measured: with
   ONLY layer 1 fixed (kernel #1, stock ng_base), 30k cycles still
   leaked 5.76K allocs / 830 KB — identical to stock.

Consumer sites in ng_pptpgre.c (all normal-operation): reset via every
`NGM_PPTPGRE_SET_CONFIG` (:396 -> :968-969), ack processing (:770),
piggy-backed ack in xmit (:596), session-hook disconnect (:494).

## Verification narrative

1. Stock kernel (X86_64_GENERIC #0), modules built in-guest, harness
   `pptp_leak.c` wires ng_socket -> pptpgre:upper + session_0000 and
   then runs the whole attack loop as **uid 65534** (ng7 data sockets
   carry no privilege check):
   30k arm/cancel cycles (SET_CONFIG -> 1-byte data write arms
   rackTimer -> SET_CONFIG cancels it) -> `vmstat -m` netgraph zone
   InUse 4 -> **5.76K / 830 KB**, never returns (run.log).
2. Persistence: `kldunload ng_pptpgre` -> **Device busy** (leaked node
   refs pin the module); a later `NGM_NAME pptp0` -> **Address already
   in use** (the dead node's name stays registered — ng_unref_node only
   unlists at last ref).
3. Escalation observed: after the 30k leak, the next netgraph
   control-socket creation panicked the kernel via the DF-3514 sink:
   `Warning: objcache(netgraph) exhausted on cpu0!` ->
   `panic: hashdestroy: hash not empty` (ngc_attach ->
   ng_make_node_common), guest down (panic.txt).
4. CONTROL (fresh boot, identical code path, `enableWindowing=0` so the
   rack timer is never armed): 30k cycles -> Count **3 -> 3 flat**.
5. Fix validation: kern_timeout.c fix applied in-guest (kernel #1,
   `make nativekernel` RC=0) + `ng_free_item(item)` added to
   ng_uncallout (module rebuild).  30k cycles -> Count **flat at 3**
   (vs 5.76K).  Intermediate step (kernel #1 + STOCK ng_base) measured
   5.76K again — proving both layers are necessary.

## Exploit chain / impact ceiling

Pure leak/DoS primitive (no corruption): ~0.5 KB of unreclaimable
kernel memory per cancelled timer from an unprivileged local user, plus
permanent module pinning and node-name squatting; at scale it crosses
into the DF-3514 rehash panic (observed).  No route to uid=0 — the
leaked items are never dereferenced after their flag is set.

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