Fix for DF-3546: v1 ng_bpf cyclic-forwarding recursion -> kernel stack exhaustion. Part 1: ng_bpf_setprog() rejects ifMatch/ifNotMatch == thisHook (non-empty); forwarding out the arrival hook is always a cycle. Default (empty) program attached by ng_bpf_newhook() still passes. Part 2: ng_bpf_rcvdata() bounds nested synchronous forwarding at 16 hops (atomic depth counter, decremented on every exit AFTER the forward), turning any cycle (cross-node, mixed node types) into ELOOP drops instead of 'DOUBLE FAULT - KERNEL STACK GUARD HIT!'. Validated in-guest: module rebuild + self/cross triggers survive (see VERDICT.md). --- a/sys/netgraph/bpf/ng_bpf.c +++ b/sys/netgraph/bpf/ng_bpf.c @@ -69,6 +69,10 @@ #define ERROUT(x) do { error = (x); goto done; } while (0) +/* DF-3546: max nested synchronous forwards through ng_bpf nodes */ +#define NG_BPF_MAX_FWD_DEPTH 16 +static volatile int ng_bpf_fwd_depth; + /* Per hook private info */ struct ng_bpf_hookinfo { node_p node; @@ -380,6 +384,18 @@ hook_p dest; u_int len; + /* + * DF-3546: v1 netgraph forwards synchronously (ng_send_data calls + * the peer's rcvdata directly) with no hop limit, so a cyclic + * configuration recurses until the kernel stack is exhausted. + * Bound the nesting and drop (ELOOP) instead. + */ + if (atomic_fetchadd_int(&ng_bpf_fwd_depth, 1) >= NG_BPF_MAX_FWD_DEPTH) { + atomic_subtract_int(&ng_bpf_fwd_depth, 1); + NG_FREE_DATA(m, meta); + return (ELOOP); + } + /* Update stats on incoming hook */ hip->stats.recvFrames++; hip->stats.recvOctets += totlen; @@ -390,7 +406,8 @@ data = kmalloc(totlen, M_NETGRAPH, M_NOWAIT); if (data == NULL) { NG_FREE_DATA(m, meta); - return (ENOMEM); + error = ENOMEM; + goto out; } needfree = 1; } else @@ -421,7 +438,8 @@ dest = ng_findhook(hip->node, hip->prog->ifNotMatch); if (dest == NULL) { NG_FREE_DATA(m, meta); - return (0); + error = 0; + goto out; } /* Deliver frame out destination hook */ @@ -429,6 +447,8 @@ dhip->stats.xmitOctets += totlen; dhip->stats.xmitFrames++; NG_SEND_DATA(error, dest, m, meta); +out: + atomic_subtract_int(&ng_bpf_fwd_depth, 1); return (error); } @@ -475,7 +495,20 @@ { const hinfo_p hip = hook->private; struct ng_bpf_hookprog *hp; - int size; + int size, i; + + /* + * DF-3546: forwarding a packet out the hook it arrived on is + * always a cycle; there is no legitimate such configuration. + * (The default program has empty ifMatch/ifNotMatch and passes.) + */ + for (i = 0; i < 2; i++) { + const char * const dest = + (i == 0) ? hp0->ifMatch : hp0->ifNotMatch; + + if (dest[0] != '\0' && strcmp(dest, hp0->thisHook) == 0) + return (EINVAL); + } /* Check program for validity */ if (!bpf_validate(hp0->bpf_prog, hp0->bpf_prog_len))