⬢ DragonFlyBSD Kernel Audit
DF-3546 / fix.diff
← back to finding ↓ download raw
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))