{
 "finding_id": "DF-3474",
 "status": "reproduced",
 "reproduced": 1,
 "impact": "dos",
 "confidence": "certain",
 "verdict": "netgraph7s NG_FREE_ITEM() only sets a vestigial NGQF_FREE flag (netgraph.h:816-820, flag never tested anywhere); the item is only really freed when the reference ng_snd_item()/ng_apply_item() manages is dropped at ng_base.c:2109. Items that fail addressing BEFORE being sent (fresh items, refs==0) are therefore leaked together with their entire control message at ng_base.c:3066/3096/3122. A root loop of sendto() on an AF_NETGRAPH control socket to a bogus path leaks ~4.2KB per call and, after ~4096 items, permanently exhausts the netgraph objcache (maxalloc=4096): objcache_get(M_WAITOK) then blocks forever in non-interruptible ssleep, wedging the machine (kernel prints Warning: objcache(netgraph) exhausted on cpu0!). Reproduced twice (guest wedge-killed, vm.sh status down); control experiment on the same socket with a valid path shows zero growth. Fixed by freeing fresh (refs==0) items via ng_free_item() at the three ng_address_* error sites; patched module survived 100k failing sends with flat memory and no exhaustion warning. Deterministic trigger requires root (control socket is SYSCAP_RESTRICTEDROOT-gated, ng_socket.c:182); unprivileged race-dependent variants via data sockets exist (ngd_send -> NG_SEND_DATA_FLAGS -> ng_address_hook).",
 "exploit_chain": "root (or unpriv+graph-owner race) -> repeated failing AF_NETGRAPH control sendto (ENOENT from ng_address_path) -> each send leaks struct ng_item + kmallocd ng_mesg payload -> after ~4096 sends the netgraph objcache is permanently exhausted -> any thread allocating a netgraph item with M_WAITOK sleeps forever non-interruptibly (kern_objcache.c:547-552) -> system-wide wedge until reboot (demonstrated); continued sends additionally drain kmem unboundedly. Ceiling: permanent local DoS; no memory-corruption/uid=0 route.",
 "evidence": [
  "findings/poc/DF-3474/run.log \u2014 baseline A/B: GOOD 250k sends free 843954->843906 (no leak); BAD phase then wedged guest",
  "findings/poc/DF-3474/run.2.log \u2014 second baseline: 3k smoke shows 2795-page (~11MB) drop matching 3000 x ~4KB leaked msgs; 50k bad-run wedged guest again",
  "findings/poc/DF-3474/wedge_serial.log \u2014 kernel console: Warning: objcache(netgraph) exhausted on cpu0! then silence, ssh dead",
  "findings/poc/DF-3474/fix_run.log \u2014 patched ng_base.c: 100k BAD sends complete, free 841147->840580 (~steady-state), dmesg exhaustion count 0; GOOD 50k sends ok",
  "findings/poc/DF-3474/trigger.c \u2014 A/B trigger (NG_VERSION 8 for netgraph7)",
  "findings/poc/DF-3474/fix.diff \u2014 3-hunk fix, applied and validated in-guest"
 ],
 "kernel_refs": [
  "sys/netgraph7/netgraph/ng_base.c:3062",
  "sys/netgraph7/netgraph/ng_base.c:3094",
  "sys/netgraph7/netgraph/ng_base.c:3117",
  "sys/netgraph7/netgraph/netgraph.h:816",
  "sys/netgraph7/netgraph2.h:77",
  "sys/netgraph7/netgraph/ng_base.c:2109",
  "sys/netgraph7/netgraph/ng_base.c:2772",
  "sys/netgraph7/socket/ng_socket.c:310",
  "sys/kern/kern_objcache.c:547"
 ],
 "poc_changes": "Trigger initially used stock userland NG_VERSION (2, old sys/netgraph stack) which netgraph7 rejects with EINVAL; set header.version=8 explicitly. Also enlarged the sockaddr to a variable-length buffer for the 24-char bogus path, added per-phase errno histogram, per-iteration response draining (so the GOOD control phase never hits ENOBUFS backpressure), and argv-selectable phases/iteration counts. Guest needed the netgraph7 modules built from /usr/src (stock /boot/kernel modules are the OLD sys/netgraph build - verified via strings) and a fresh boot so the old stacks AF_NETGRAPH domain registration was absent.",
 "attempts": 6,
 "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
 "runtime_sec": 3600,
 "guest_dirty": 0,
 "build_cmd": "cd /usr/src/sys/netgraph7/netgraph && make obj && make; cd /usr/src/sys/netgraph7/socket && make obj && make; cc -O2 -Wall -o /root/trigger /root/df3474/trigger.c",
 "run_cmd": "kldload /root/ng7/netgraph.ko; kldload /root/ng7/ng_socket.ko; sysctl -n vm.stats.vm.v_free_count; /root/trigger good 250000; sysctl -n vm.stats.vm.v_free_count; /root/trigger bad 50000",
 "code_hash": "d71a97d1cead49886dc4758c98ccb09406495e3afc9624f0647bd16b89c30c30",
 "notes": "Guest left clean (vm.sh reset with-src after fix validation; /usr/src patch reverted by reset, fix.diff preserved in pack). vmstat -m is unreliable for kld-loaded malloc types on this guest (kvm_read Bad address) - free-page deltas + console objcache warning used instead. Stock module set on this guest ships the OLD sys/netgraph stack; netgraph7 must be the live stack for this bug (e.g. systems running netgraph7 bluetooth). The sibling ng_connect_data() at ng_socket.c:776 already worked around this exact flaw with a manual ng_free_item(), corroborating the design defect.",
 "recommended_fix": "In ng_address_hook/ng_address_path/ng_address_ID error paths, free never-sent items via ng_free_item() when item->refs == 0 (keep mark-only NG_FREE_ITEM for in-flight items) - see fix.diff.",
 "fix_status": "fixed",
 "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 (netgraph7 ng_base.c patched module rebuilt in-guest)",
 "fix_baseline_reproduced": 1,
 "fix_patched_reproduced": 0,
 "fix_verdict": "Baseline (stock netgraph7): 50k failing sends -> objcache(netgraph) exhausted + permanent guest wedge (2 occurrences). Patched (fix.diff applied to in-guest /usr/src, module rebuilt): 100k failing sends complete in seconds, free-page delta ~567 pages (steady-state, vs ~420MB expected on stock), zero exhaustion warnings, good-path regression check 50k sends OK. The leak and the wedge are gone.",
 "fix_evidence": [
  "findings/poc/DF-3474/fix_run.log",
  "findings/poc/DF-3474/wedge_serial.log",
  "findings/poc/DF-3474/fix.diff"
 ]
}