# DF-3540 — netgraph7 ng_bpf: cyclic ifMatch/ifNotMatch forwarding → unbounded synchronous recursion → kernel panic

## What

`ng_bpf_setprog()` (sys/netgraph7/bpf/ng_bpf.c:584-585) resolves
`ifMatch`/`ifNotMatch` with `ng_findhook()` and stores the result as a
direct forwarding target, with **no validation that the targets differ
from `thisHook`** and no cycle detection. netgraph7 forwards items whose
refcount is > 1 **synchronously** — `NG_FWD_ITEM_HOOK` →
`ng_snd_item()` → `ng_apply_item()` → the peer's `rcvdata()` →
`NG_FWD_ITEM_HOOK` → … — with no hop bound (sys/netgraph7/netgraph/ng_base.c:1908-1965).
Each hop also takes the destination node's LWKT token
(`ng_acquire_read`, ng_base.c:1998) **recursively**.

A cyclic configuration — two ng_bpf nodes cross-connected, each with
`ifMatch == thisHook` ("self" mode), or a pure multi-hook ifMatch cycle
with no self-reference at all ("cross" mode) — makes one injected packet
recurse until the kernel thread's LWKT token reference array
(`td_toks_array[LWKT_MAXTOKENS == 32]`, sys/sys/thread.h:331) overflows:

    panic: assertion "ref < &td->td_toks_end" failed in lwkt_gettoken_shared
           at /usr/src/sys/kern/lwkt_token.c:732     (KKASSERT: all builds)
    lwkt_gettoken_shared() at lwkt_gettoken_shared+0x34c
    lwkt_gettoken_shared() at lwkt_gettoken_shared+0x34c
    ng_apply_item() at ng_apply_item+0xa9
    ng_apply_item() at ng_apply_item+0x105     (recursive)
    ...

The KKASSERT is compiled into production (non-INVARIANTS) kernels too,
so the panic is deterministic everywhere. Without the token-array
tripwire the recursion would instead exhaust the ngthread kernel stack.

## Trigger privilege

ROOT ONLY (verified live): ng7 control sockets require
`caps_priv_check(SYSCAP_RESTRICTEDROOT)` (sys/netgraph7/socket/ng_socket.c:182-185);
uid 1001 gets EPERM (unpriv_check.txt). Root must build the cyclic graph;
one packet (sent by root, or by any traffic source once configured)
panics the machine. Severity Low (privileged DoS) — same class as
DF-3486/DF-0679.

## Build / Run (in-guest, root, clean /usr/src)

    sh build.sh          # ng7 netgraph.ko + ng_socket.ko + ng_bpf.ko + trigger
    sh run.sh self       # expect panic (console: token-array assertion)
    # after reboot:
    sh run.sh cross      # expect same panic (cycle class, no self-reference)

## Fix validation (fix.diff)

  stock            self → panic        cross → panic
  ng_bpf guard only  self → EINVAL       cross → panic   (fix1_*.log)
  full fix          self → EINVAL       cross → survives (fix2_*.log)

`fix.diff` = (1) ng_bpf.c rejects `ifMatch`/`ifNotMatch == thisHook`
(kills the trivially-configurable self cycle); (2) ng_base.c bounds
synchronous forwards per item (`el_hops`, `NG_HOPS_MAX = 16`, ELOOP) —
the general guard covering cycles built from any node types.
