Module Name:    src
Committed By:   riastradh
Date:           Wed Oct 30 11:19:38 UTC 2024

Modified Files:
        src/tests/net/npf: t_npf.sh
        src/usr.sbin/npf/npfctl: npf_bpf_comp.c

Log Message:
npfctl(8): Fix compiling multiword comparisons, i.e., IPv6 addrs.

PR bin/55403: npfctl miscompiles IPv6 rules


To generate a diff of this commit:
cvs rdiff -u -r1.6 -r1.7 src/tests/net/npf/t_npf.sh
cvs rdiff -u -r1.16 -r1.17 src/usr.sbin/npf/npfctl/npf_bpf_comp.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/tests/net/npf/t_npf.sh
diff -u src/tests/net/npf/t_npf.sh:1.6 src/tests/net/npf/t_npf.sh:1.7
--- src/tests/net/npf/t_npf.sh:1.6	Wed Oct 30 10:12:31 2024
+++ src/tests/net/npf/t_npf.sh	Wed Oct 30 11:19:38 2024
@@ -1,4 +1,4 @@
-# $NetBSD: t_npf.sh,v 1.6 2024/10/30 10:12:31 riastradh Exp $
+# $NetBSD: t_npf.sh,v 1.7 2024/10/30 11:19:38 riastradh Exp $
 #
 # Copyright (c) 2008, 2010 The NetBSD Foundation, Inc.
 # All rights reserved.
@@ -29,12 +29,6 @@ run_test()
 {
 	local name="${1}"
 
-	case $name in
-	rule)
-		atf_expect_fail "PR bin/55403: npfctl miscompiles IPv6 rules"
-		;;
-	esac
-
 	atf_check -o ignore -e ignore npfctl debug -c "$(atf_get_srcdir)/npftest.conf" -o ./npf.plist
 	atf_check -o ignore npftest -c npf.plist -T "${name}"
 }

Index: src/usr.sbin/npf/npfctl/npf_bpf_comp.c
diff -u src/usr.sbin/npf/npfctl/npf_bpf_comp.c:1.16 src/usr.sbin/npf/npfctl/npf_bpf_comp.c:1.17
--- src/usr.sbin/npf/npfctl/npf_bpf_comp.c:1.16	Sat May 30 14:16:56 2020
+++ src/usr.sbin/npf/npfctl/npf_bpf_comp.c	Wed Oct 30 11:19:38 2024
@@ -79,10 +79,27 @@
  *	out of the group; if false, then jump "next".  At the end of the
  *	group, an addition failure path is appended and the JUMP_MAGIC
  *	uses within the group are patched to jump past the said path.
+ *
+ *	For multi-word comparisons (IPv6 addresses), there is another
+ *	layer of grouping:
+ *
+ *		A and B and ((C and D) or (E and F))
+ *
+ *	This strains the simple-minded JUMP_MAGIC logic, so for now,
+ *	when generating the jump-if-false targets for (C and D), we
+ *	simply count the number of instructions left to skip over.
+ *
+ *	A better architecture might be to create asm-type labels for
+ *	the jt and jf continuations in the first pass, and then, once
+ *	their offsets are determined, go back and fill them in in the
+ *	second pass.  This would simplify the logic (no need to compute
+ *	exactly how many instructions we're about to generate in a
+ *	chain of conditionals) and eliminate redundant RET #0
+ *	instructions which are currently generated after some groups.
  */
 
 #include <sys/cdefs.h>
-__RCSID("$NetBSD: npf_bpf_comp.c,v 1.16 2020/05/30 14:16:56 rmind Exp $");
+__RCSID("$NetBSD: npf_bpf_comp.c,v 1.17 2024/10/30 11:19:38 riastradh Exp $");
 
 #include <stdlib.h>
 #include <stdbool.h>
@@ -134,6 +151,7 @@ struct npf_bpf {
 	 */
 	unsigned		ingroup;
 	bool			invert;
+	bool			multiword;
 	unsigned		goff;
 	unsigned		gblock;
 
@@ -322,6 +340,7 @@ npfctl_bpf_group_enter(npf_bpf_t *ctx, b
 	ctx->goff = bp->bf_len;
 	ctx->gblock = ctx->nblocks;
 	ctx->invert = invert;
+	ctx->multiword = false;
 	ctx->ingroup++;
 }
 
@@ -334,8 +353,14 @@ npfctl_bpf_group_exit(npf_bpf_t *ctx)
 	assert(ctx->ingroup);
 	ctx->ingroup--;
 
-	/* If there are no blocks or only one - nothing to do. */
-	if (!ctx->invert && (ctx->nblocks - ctx->gblock) <= 1) {
+	/*
+	 * If we're not inverting, there were only zero or one options,
+	 * and the last comparison was not a multi-word comparison
+	 * requiring a fallthrough failure -- nothing to do.
+	 */
+	if (!ctx->invert &&
+	    (ctx->nblocks - ctx->gblock) <= 1 &&
+	    !ctx->multiword) {
 		ctx->goff = ctx->gblock = 0;
 		return;
 	}
@@ -502,7 +527,7 @@ npfctl_bpf_cidr(npf_bpf_t *ctx, unsigned
     const npf_addr_t *addr, const npf_netmask_t mask)
 {
 	const uint32_t *awords = (const uint32_t *)addr;
-	unsigned nwords, length, maxmask, off;
+	unsigned nwords, origlength, length, maxmask, off;
 
 	assert(((opts & MATCH_SRC) != 0) ^ ((opts & MATCH_DST) != 0));
 	assert((mask && mask <= NPF_MAX_NETMASK) || mask == NPF_NO_NETMASK);
@@ -529,7 +554,7 @@ npfctl_bpf_cidr(npf_bpf_t *ctx, unsigned
 	/* Ensure address family. */
 	fetch_l3(ctx, af, 0);
 
-	length = (mask == NPF_NO_NETMASK) ? maxmask : mask;
+	length = origlength = (mask == NPF_NO_NETMASK) ? maxmask : mask;
 
 	/* CAUTION: BPF operates in host byte-order. */
 	for (unsigned i = 0; i < nwords; i++) {
@@ -563,13 +588,49 @@ npfctl_bpf_cidr(npf_bpf_t *ctx, unsigned
 			add_insns(ctx, insns_mask, __arraycount(insns_mask));
 		}
 
+		/*
+		 * Determine how many instructions we have to jump
+		 * ahead if the match fails.
+		 *
+		 * - If this is the last word, we jump to the final
+                 *   failure, JUMP_MAGIC.
+		 *
+		 * - If this is not the last word, we jump past the
+		 *   remaining instructions to match this sequence.
+		 *   Each 32-bit word in the sequence takes two
+		 *   instructions (BPF_LD and BPF_JMP).  If there is a
+		 *   partial-word mask ahead, there will be one
+		 *   additional instruction (BPF_ALU).
+		 */
+		uint8_t jf;
+		if (i + 1 == (origlength + 31)/32) {
+			jf = JUMP_MAGIC;
+		} else {
+			jf = 2*((origlength + 31)/32 - i - 1);
+			if (origlength % 32 != 0 && wordmask == 0)
+				jf += 1;
+		}
+
 		/* A == expected-IP-word ? */
 		struct bpf_insn insns_cmp[] = {
-			BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, word, 0, JUMP_MAGIC),
+			BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, word, 0, jf),
 		};
 		add_insns(ctx, insns_cmp, __arraycount(insns_cmp));
 	}
 
+	/*
+	 * If we checked a chain of words in sequence, mark this as a
+	 * multi-word comparison so if this is in a group there will be
+	 * a fallthrough case.
+	 *
+	 * XXX This is a little silly; the compiler should really just
+	 * record holes where conditional jumps need success/failure
+	 * continuations, and go back to fill in the holes when the
+	 * locations of the continuations are determined later.  But
+	 * that requires restructuring this code a little more.
+	 */
+	ctx->multiword = (origlength + 31)/32 > 1;
+
 	uint32_t mwords[] = {
 		(opts & MATCH_SRC) ? BM_SRC_CIDR: BM_DST_CIDR, 6,
 		af, mask, awords[0], awords[1], awords[2], awords[3],

Reply via email to