# DF-3551 — VERDICT: REPRODUCED (v1 twin of DF-0690)

**Impact:** dos (unbounded kernel mbuf leak; realistic ceiling gated by
root-only node creation) **Confidence:** certain

## What was run

`v1demo leakdbg N` / `v1demo leakctl N` on the stock v1 netgraph stack
(build/graph setup identical to DF-3550; `NGM_CONNECT` attaches a second hook
`socket(dbg) <-> sample(debug)`). `netstat -m` snapshots bracket every run.

## Observed (run.log)

| phase | mbufs in use | delta |
|---|---|---|
| baseline | 7 | — |
| leakctl 1200 (downstream, reflections drained) | 8→1507¹ | **0** |
| leakdbg 300 (debug hook) | 1507→1807 | **+300** |
| leakdbg 1200 (debug hook) | 1807→3007 | **+1200** |
| leakdbg 500 (stability re-check) | 3007→3507 | **+500** |

¹ the 1507 plateau is residue from the earlier exploratory leak runs — it
never decreases: the leaked mbufs are permanently lost (still counted in the
final re-check).

Every packet sent into `sample:debug` leaks exactly one mbuf, linearly and
permanently; the control run through `sample:downstream` (whose data path
*does* consume the mbuf via `ng_send_data`) leaks nothing.

## Why (kernel path, line-by-line)

1. `ng_xxx_newhook()` gives the debug hook `hook->private = NULL`
   (sys/netgraph/ng_sample.c:238-242). The downstream hook *always* has
   private set (`:237`), and DLCI hooks get `channel + chan` (`:230`).
2. `ng_xxx_rcvdata()` with `private == NULL` falls straight through to
   `ng_xxx_rcvdataq()` (`:327-338`).
3. In `ng_xxx_rcvdataq()`'s else-branch (`:390-394`), the only free is
   `if (hook == xxxp->downstream_hook.hook) NG_FREE_DATA(m, meta);` — but a
   hook with `private == NULL` can **never** be the downstream hook (see 1),
   so the condition is dead code, the free is unreachable, and the function
   `return 0`s with the caller-owned `m`/`meta` still allocated
   (v1 rcvdata contract: "the caller will never free m or meta … if we use
   up this data or abort we must free BOTH", `:316-317`).
4. Result: one leaked mbuf (+meta) per packet on the debug hook — the v1
   twin of DF-0690 (ng7), here demonstrated live for the first time.

Reachability gate: `ng_sample.ko` does not ship (no SUBDIR/conf entry),
v1 control sockets are `caps_priv_check(SYSCAP_RESTRICTEDROOT)`-gated
(sys/netgraph/socket/ng_socket.c:172) — an admin must kldload the module and
instantiate the node before any data can be pushed; once a `sample` node
exists, any peer can feed its debug hook. Severity Low (matches DF-0690).

## Exploit chain

Resource-exhaustion only: sustained feeding of `sample:debug` grows the
kernel mbuf pool without bound (`mb_map` exhaustion → system-wide networking
denial of service). No memory-safety primitive (the mbufs are freed-never,
not use-after-free).

## Fix validation

`fix.diff` makes the free unconditional in the (debug-only) else branch —
with the sentinel fix from DF-3550's pack the downstream hook always has
private set, so the branch is provably debug-hook-only. Kernel-rebuild
validation skipped per contract (Low severity, not memory corruption);
the patch is a two-line dead-condition removal.

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