# DF-3540 — VERDICT

**Status: reproduced. Impact: panic (kernel DoS, root-gated). Confidence: certain.**

## How it was reproduced

Guest: DragonFly 6.5-DEVELOPMENT #0 (stock INVARIANTS kernel), ng7 module
stack (netgraph.ko, ng_socket.ko, ng_bpf.ko) built from the guest's clean
/usr/src — build.sh / run.sh. Trigger `bpf_cycle.c` uses the AF_NETGRAPH
control+data socket API (NG_VERSION 8; sockaddr_ng data at offset 2 —
DFly `sa_family_t` is `__uint8_t`):

1. control socket node named "bpcyc" (NGM_NAME), data socket connected to
   it ("bpcyc:" absolute-node addressing).
2. mkpeer bpf (B1), mkpeer bpf from B1 (B2), three NGM_BPF_SET_PROGRAMs:
   - self mode: `B1.x2: ifMatch=x1`, `B1.x1: ifMatch=x1` (self),
     `B2.y1: ifMatch=y1` (self). Steady loop B1.x1 ↔ B2.y1.
   - cross mode: B1 hooks e/p/r, B2 hooks q/s (NGM_CONNECT wires B1.r↔B2.s);
     programs `B1.e→p`, `B1.r→p`, `B2.q→s`. Loop B1.r → B2.q → B1.r.
     **No ifMatch equals its own thisHook anywhere.**
3. one 1-byte packet injected via the data socket.

Both modes: kernel dies instantly (ssh hangs, guest down). Serial console:

    panic: assertion "ref < &td->td_toks_end" failed in lwkt_gettoken_shared
           at /usr/src/sys/kern/lwkt_token.c:732
    lwkt_gettoken_shared() x2, ng_apply_item() x3+ recursive
    Debugger("panic")

(panic.txt, console_full.txt, console_cross.txt)

## Why it happens (source trace)

- ng_bpf.c:584-585 — `hip->match = ng_findhook(node, prog->ifMatch)` —
  any hook name accepted, including thisHook itself; no cycle check.
- ng_bpf.c:500 — `NG_FWD_ITEM_HOOK(error, item, dest)`.
- netgraph.h:861-872 → ng_base.c:1908 `ng_snd_item()`: items with
  refs>1 are applied **synchronously** (line 1961) — each forward is a
  stack frame chain, no hop limit.
- ng_base.c:1998 `ng_acquire_read()` → `lwkt_gettoken_shared()` pushes a
  tokref into `td_toks_array[LWKT_MAXTOKENS=32]` (sys/sys/thread.h:331);
  recursive acquisition per hop; 32 acquisitions → KKASSERT at
  lwkt_token.c:732 (always compiled — KKASSERT, not KASSERT).
- netgraph7 dispatches everything on the cpu0 ngthread
  (ng_base.c:1948-1956), so the recursion is on a kernel thread stack.

The framework property (no cycle guard on synchronous forwarding) is the
root enabler; ng_bpf's SET_PROGRAM is the stock node type that makes a
cycle expressible with two/three control messages. The same framework
property is reachable through other forwarding node types (e.g. an
ng_echo pair), which is why fix.diff fixes both layers.

## Exploit chain

None beyond denial of service: root-gated configuration, deterministic
panic. No memory-safety violation (the overflow tripwire is an
always-on KKASSERT; no controlled overwrite occurs before it).

## Fix validation (fix.diff)

Applied as module rebuilds on the same stock kernel:

| build | self mode | cross mode |
|---|---|---|
| stock | panic (run 1) | panic (run 2) |
| ng_bpf guard only | SET_PROGRAM → EINVAL (sendto errno=22, apply-error propagation), no panic (fix1_self.log) | still panics (fix1_cross.log) — proves the framework part is required |
| full fix (ng_bpf + ng_base hop bound 16) | EINVAL, survives (fix2_self.log) | bounded: cycle unwinds with ELOOP, packet dropped, survives 30s+ (fix2_cross.log) |

fix_status: fixed.

## Residual notes

- NG_HOPS_MAX=16 chosen so worst-case recursion stays well inside a
  2-page kernel thread stack; legitimate netgraph paths are far
  shallower. Upstream may prefer queueing instead of recursion
  (FreeBSD-style), which removes the need for the bound.
- v1 netgraph (sys/netgraph/bpf/ng_bpf.c) forwards synchronously too
  (v1 ng_send_data calls rcvdata directly, ng_base.c:1687) — the v1 twin
  of this bug exists but v1 ng_bpf is a separate file with its own
  audit record (DF-2595 et al.); not re-filed here.

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