--- a/sys/netgraph7/bpf/ng_bpf.c 2026-09-14 18:15:36.484957045 +0000 +++ b/sys/netgraph7/bpf/ng_bpf.c 2026-09-14 18:15:42.832877239 +0000 @@ -556,6 +556,20 @@ #endif int size; + /* + * Disallow programs that would forward packets back out the same + * hook they arrived on (ifMatch/ifNotMatch == thisHook). Such a + * "self-bounce", combined with any peer that re-sends (e.g. a + * second ng_bpf configured the same way), forms a forwarding + * cycle; netgraph7 forwards items synchronously and would recurse + * until the kernel stack / token array is exhausted. (DF-3540) + * The default program (empty thisHook) is unaffected. + */ + if (hp0->thisHook[0] != '\0' && + (strcmp(hp0->ifMatch, hp0->thisHook) == 0 || + strcmp(hp0->ifNotMatch, hp0->thisHook) == 0)) + return (EINVAL); + /* Check program for validity */ if (!bpf_validate(hp0->bpf_prog, hp0->bpf_prog_len)) return (EINVAL); --- a/sys/netgraph7/netgraph.h 2026-09-14 18:15:36.488956995 +0000 +++ b/sys/netgraph7/netgraph.h 2026-09-14 18:15:42.832877239 +0000 @@ -624,6 +624,7 @@ */ struct ng_apply_info *apply; u_int refs; + u_int el_hops; /* synchronous forwards so far (cycle guard) */ #ifdef NETGRAPH_DEBUG /*----------------------------------------------*/ char *lastfile; int lastline; --- a/sys/netgraph7/netgraph/ng_base.c 2026-09-14 18:15:36.484957045 +0000 +++ b/sys/netgraph7/netgraph/ng_base.c 2026-09-14 18:15:51.768764894 +0000 @@ -1832,6 +1832,14 @@ * are distributed based on the ingress node, to keep item ordering. \***************************************************************/ +/* + * Maximum number of synchronous forwards (hops) per item. Bounds the + * ng_snd_item -> ng_apply_item recursion for cyclic graphs so it can + * never exhaust the kernel stack or the per-thread token array + * (LWKT_MAXTOKENS). (DF-3540) + */ +#define NG_HOPS_MAX 16 + static __inline void ng_acquire_read(node_p node); static __inline void ng_acquire_write(node_p node); static __inline void ng_leave_readwrite(node_p node); @@ -1957,7 +1965,15 @@ /* * The item wasn't queued. Process it synchronously. + * + * Bound the number of synchronous forwards: each hop is a stack + * frame chain and a token reference; without a bound a cyclic + * graph recurses until the stack or td_toks[] overflows. */ + if (item->el_hops++ >= NG_HOPS_MAX) { + ng_unref_item(item, ELOOP); + return (ELOOP); + } error = ng_apply_item(item); done: