# DF-3518 — ng_pptpgre `priv` UAF: rmnode free vs in-flight rcvdata

**Verdict: NOT REPRODUCED on this guest — code-confirmed UAF whose trigger
vector is blocked by the same CPU-0 netisr serialization that DF-0590
established empirically (millions of frames, no concurrency).**

`status = not_reproduced`, `reproduced = 0`, `impact = none (on this
vector)`, `confidence = certain (code-level)`.

## The defect (code trace)

v1 netgraph delivers data **inline, with no node reference and no lock**
across the call:

* `sys/netgraph/netgraph/ng_base.c:1687` — `ng_send_data()`:
  `error = (*rcvdata)(hook->peer, m, meta);` — direct call, caller keeps
  no ref on the peer node and none exists on `node->private`.
* `sys/netgraph/pptpgre/ng_pptpgre.c:437-440` — `ng_pptpgre_rmnode()`:
  after `ng_pptpgre_reset()` sync-stops both timers,
  `if (priv->timers == 0) { kfree(priv, M_NETGRAPH); node->private = NULL; }`.

Nothing pins `priv` for the duration of an in-flight `rcvdata` /
`rcvmsg`. On an SMP system where a peer node dispatches into the
pptpgre node on CPU A (e.g. `ng_ether` delivering from
`ether_input`'s per-flow netisr, `sys/net/if_ethersubr.c:1189`), a
concurrent teardown on CPU B (last-hook disconnect → `ng_rmnode` →
`ng_pptpgre_rmnode` → `kfree(priv)`) frees the ~208-byte `priv` while
CPU A is still writing `priv->stats` (`:576-577`, `:551-552`),
`priv->recvSeq/xmitSeq` (`:698`, `:516`), `priv->conf` reads, etc.
→ **use-after-free write of controlled-ish data on the M_NETGRAPH heap**.
This is the pptpgre instantiation of the "node private data" half of
DF-0492 (L2TP) — not covered by DF-0596, which is scoped to the
`xmitWin`/`timeSent[]` state race.

Aggravator (post-free, not pre-free): with DF-3521's leak,
`priv->timers` never reaches 0, so `rmnode` *doesn't* free — the UAF
window needs a clean-timers shutdown (e.g. node with no armed timers,
or a kernel with the kern_timeout contract fixed).

## Why it cannot fire from userspace on this guest

Every `ng_socket` send (data **and** control) is processed on **CPU 0's
netisr port**: `sys/kern/uipc_socket.c:254-259` (no `PR_SYNC_PORT`, no
`pr_initport` for ng_socket → `so->so_port = netisr_cpuport(0)`), and the
callouts arm on the CPU that starts them. DF-0590's pack demonstrated
1–3 M injected frames with zero concurrency — the identical blocker.
`uaf_hammer` here reproduces that negative for the pptpgre rmnode path:

```
hammer 30 s: injector floods pptp:upper while killer builds+shuts down nodes
DONE inj_sent=9 (ENOBUFS backpressure) kill_cycles=51962 kill_fail=0
EXIT_OK, guest up, vmstat -m netgraph Count unchanged
```

52k create/config/shutdown lifecycles overlapped with in-flight frames,
zero panics — consistent with full serialization on CPU 0.

## Realistic exposure

A root-configured PPTP/VPN concentrator with `ng_ether`- or
`ng_ksocket`-fed lower hooks receives frames on per-flow CPUs — the
rmnode-vs-rcvdata window is genuinely open there. Fix (same as
DF-0590's): per-node serialization for v1 ng_pptpgre (lockmgr `LK_*`
around rcvdata/rcvmsg/shutdown, or take a node ref + flag across the
inline dispatch in `ng_send_data`).

## Files

* `uaf_hammer.c` — hammer (root): `cc -O2 -lpthread -o uaf_hammer uaf_hammer.c`
* `run.log` — full 30 s run output

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

(No fix.diff for this item: documented recommendation only - see Fix above.)
