# DF-3474 — VERDICT

**status: reproduced / impact: dos / confidence: certain — fix validated (fixed)**

## What was run

Guest: DragonFly 6.5-DEVELOPMENT #0 (X86_64_GENERIC, INVARIANTS), stock
kernel. The stock /boot/kernel netgraph modules are built from the OLD
`sys/netgraph` tree (verified: `strings /boot/kernel/ng_socket.ko` contains
`ng_send_msg`, and `netgraph.ko` lacks `netgraph_msg`) — so the netgraph7
stack under audit was built from the identical in-guest source
(md5-verified against the workspace tree) and loaded:

    cd /usr/src/sys/netgraph7/netgraph && make obj && make
    cd /usr/src/sys/netgraph7/socket   && make obj && make
    kldload netgraph7/netgraph.ko ; kldload netgraph7/ng_socket.ko

(the old stack must not have been loaded since boot — the AF_NETGRAPH domain
registration makes ng_socket.ko permanently unloadable, so module-swap
requires a reboot; a fresh boot loads nothing netgraph-related by default).

One quirk worth recording: netgraph7's kernel expects `NG_VERSION 8` while
the stock userland `<netgraph/ng_message.h>` (old tree) says `2` — the
trigger sets version 8 explicitly or every sendto fails early with EINVAL.

## Baseline (stock netgraph7 ng_base.c)

`trigger.c` does an A/B on ONE control socket:

- GOOD: 250,000 × 4148-byte sends to path "." (own node; responses drained).
  free pages 843954 → 843906 (Δ 48 pages — noise). No leak. (run.log)
- BAD: sends to "df3474_no_such_node_xyz:" — each sendto returns ENOENT
  (the exact `ng_path2noderef` failure that reaches
  `ng_address_path()`'s `NG_FREE_ITEM(item)` at ng_base.c:3096).

Two independent runs (run.log = 250k target, run.2.log = 50k target):

- 3k-send smoke: free 847737 → 844942 (Δ 2795 pages ≈ 11 MB ≈ 3000 × ~4 KB
  msg — matches per-send leak exactly, while the interleaved GOOD phase cost
  nothing).
- 50k-send run and 250k-send run both ended with the guest **wedge-killed**:
  serial console shows `Warning: objcache(netgraph) exhausted on cpu0!`
  (wedge_serial.log), ssh stopped answering, `vm.sh status` → down, no panic.
  Mechanism: ~4096 leaked items exhaust the `ng_oc` objcache permanently;
  `objcache_get(ng_oc, M_WAITOK)` then blocks in a non-interruptible
  `ssleep("objcache_get")` (kern_objcache.c:547-552) forever; the loop's
  messages leak kmem on top.

So: unbounded kernel memory leak + permanent non-interruptible wedge of
netgraph-consuming threads = local DoS with root on a netgraph7 system
(race-dependent variants exist for unprivileged data sockets, see README).

## Fix validation

`fix.diff` (3 hunks, ng_address_hook/ng_address_path/ng_address_ID):
when `item->refs == 0` (fresh, never-sent item — the only case whose
reference is never dropped by `ng_apply_item()`), call `ng_free_item(item)`
which frees the item plus attached msg/mbuf and drops embedded node/hook
references; otherwise keep the mark-only `NG_FREE_ITEM()` for in-flight
items (their final unref happens at ng_base.c:2109).

Applied in-guest to /usr/src (`patch -p1`, clean), netgraph.ko rebuilt:

- BAD 100,000 sends on patched module: all ENOENT, completed in seconds,
  free pages 841147 → 840580 → 840571 after settle (Δ 567 pages ≈ 2.2 MB
  steady-state objcache/magazine fill — versus ~420 MB expected on stock);
  `dmesg | grep -c exhausted` → 0; guest healthy (fix_run.log).
- GOOD 50,000 sends on patched module: ok=50000, no regression, guest
  healthy (uptime continues, load 0.04).

Baseline wedged twice; patched survived 2× the bad-send volume plus the good
phase. fix_status: fixed.

## Why this is not a false positive

- md5 of in-guest /usr/src/sys/netgraph7/netgraph/ng_base.c ==
  workspace file (44667ee8cb1e5724235aa8ce9400abfd).
- Source proof: NG_FREE_ITEM sets only a flag (netgraph.h:816-820);
  NGQF_FREE is never tested anywhere (rg over sys/);
  ng_address_* return without dropping the item reference; fresh items are
  born with refs==0 (bzero ctor + ng_alloc_item), and the only unconditional
  `ng_unref_item()` sits at the end of ng_apply_item() which never runs for
  items that failed addressing before ng_snd_item().
- The kernel itself flagged the exhausted item cache on the console.

## Exploit chain

Not a memory-corruption primitive; ceiling is the demonstrated permanent
kernel wedge + unbounded kmem growth (dos). No uid=0 route exists for this
class.

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