DF-3540 / verdict.json
{ "finding_id": "DF-3540", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "Reproduced on the stock INVARIANTS guest with ng7 modules built from clean /usr/src: a cyclic ng_bpf configuration (two bpf nodes cross-connected, each with ifMatch == thisHook \u2014 or a pure multi-hook ifMatch cycle with no self-reference) makes ONE injected packet recurse synchronously through NG_FWD_ITEM_HOOK -> ng_snd_item -> ng_apply_item -> rcvdata with no hop bound, and each hop takes the destination node token recursively, overflowing the LWKT token array: panic: assertion \"ref < &td->td_toks_end\" failed in lwkt_gettoken_shared at sys/kern/lwkt_token.c:732 with the ng_apply_item recursion visible in the trace (panic.txt). The KKASSERT is compiled into production kernels, so the panic is deterministic on all configs; without the token tripwire the recursion would exhaust the ngthread kernel stack instead. Root-only trigger (ngc_attach caps_priv_check, EPERM verified for uid 1001), so impact is a privileged kernel DoS (Low, same class as DF-3486). fix.diff validated in two stages: the ng_bpf-local guard (reject ifMatch/ifNotMatch == thisHook) stops the self mode with EINVAL but the self-reference-free cross cycle still panics (fix1_cross.log), proving the framework part is required; the full fix (plus ng_snd_item hop bound el_hops/NG_HOPS_MAX=16 -> ELOOP) makes both modes survive with the packet dropped (fix2_*.log).", "exploit_chain": "", "evidence": "panic.txt + console_full.txt (stock self-mode panic trace); console_cross.txt (stock cross-mode panic \u2014 cycle without any self-reference); unpriv_check.txt (EPERM); fix1_self.log / fix1_cross.log (partial-fix matrix); fix2_self.log / fix2_cross.log (full fix, both modes survive); run.sh / build.sh", "kernel_refs": [ "sys/netgraph7/bpf/ng_bpf.c:584", "sys/netgraph7/bpf/ng_bpf.c:585", "sys/netgraph7/bpf/ng_bpf.c:500", "sys/netgraph7/netgraph/ng_base.c:1908", "sys/netgraph7/netgraph/ng_base.c:1961", "sys/netgraph7/netgraph/ng_base.c:1998", "sys/netgraph7/netgraph.h:861", "sys/kern/lwkt_token.c:732", "sys/sys/thread.h:331" ], "poc_changes": "Seed sketch rewritten entirely: correct ng7 user ABI (NG_VERSION 8, header field order arglen/cmd/flags/token/typecookie), DFly sockaddr_ng layout (sa_family_t is __uint8_t, sg_data at offset 2, sg_len = 2 + strlen + 1), absolute-node addressing needs the trailing colon (name:), hook paths are DOT-separated (in.x1 not in:x1), cross-cycle needs NGM_CONNECT (each NGM_MKPEER creates a NEW node), data socket attach via connect() to the control nodes [id]:/name.", "attempts": 5, "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": 2100, "guest_dirty": 0, "build_cmd": "cd /usr/src/sys/netgraph7/netgraph && make && (cd ../socket && make) && (cd ../bpf && make) && cc -O2 -o bpf_cycle bpf_cycle.c", "run_cmd": "kldload ng7 netgraph.ko ng_socket.ko ng_bpf.ko; ./bpf_cycle self|cross (root)", "code_hash": "5e9ec23e403385089ecf51ef35720b5e6085e7f4afcbbef70530ed6afd8e69bf", "notes": "Framework-level root cause: ng_snd_item applies forwarded items synchronously with no cycle/hop guard; ng_bpf SET_PROGRAM is the config surface that makes a two-message cycle possible. The same property is reachable via other forwarding node types (e.g. ng_echo pair) \u2014 the ng_base.c part of fix.diff covers those. v1 twin exists (v1 ng_send_data is a direct synchronous call) but v1 ng_bpf.c is a separate audit unit. NG_HOPS_MAX=16 keeps worst-case recursion well inside a 2-page kernel thread stack.", "recommended_fix": "Reject ifMatch/ifNotMatch == thisHook in ng_bpf_setprog AND bound synchronous forwards per item in ng_snd_item (el_hops, NG_HOPS_MAX=16, ELOOP) \u2014 see fix.diff.", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 (stock kernel; fix applied as ng7 module rebuilds only)", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Both panic modes stopped: self mode now fails SET_PROGRAM with EINVAL at config time (error visible in sendto errno via the ng7 apply mechanism) and cross mode survives 30s+ with the cycle bounded (packet dropped with ELOOP, guest up). Two-stage matrix proves the ng_bpf-local guard alone is insufficient (cross still panicked, fix1_cross.log) and the framework hop bound completes the fix (fix2_cross.log).", "fix_evidence": "fix1_self.log; fix1_cross.log; fix2_self.log; fix2_cross.log; fix.diff; panic.txt vs fix2_*.log" } |