Module Name:    src
Committed By:   martin
Date:           Sun Nov 17 13:58:11 UTC 2024

Modified Files:
        src/usr.sbin/npf/npfctl [netbsd-9]: npf_bpf_comp.c
        src/usr.sbin/npf/npftest [netbsd-9]: npftest.conf
        src/usr.sbin/npf/npftest/libnpftest [netbsd-9]: npf_rule_test.c

Log Message:
Pull up following revision(s) (requested by riastradh in ticket #1918):

        usr.sbin/npf/npftest/npftest.conf: revision 1.10
        usr.sbin/npf/npftest/npftest.conf: revision 1.11
        usr.sbin/npf/npftest/npftest.conf: revision 1.12
        usr.sbin/npf/npfctl/npf_bpf_comp.c: revision 1.17
        usr.sbin/npf/npftest/libnpftest/npf_rule_test.c: revision 1.20
        usr.sbin/npf/npftest/libnpftest/npf_rule_test.c: revision 1.21
        usr.sbin/npf/npftest/libnpftest/npf_rule_test.c: revision 1.22
        usr.sbin/npf/npftest/libnpftest/npf_rule_test.c: revision 1.23
        tests/net/npf/t_npf.sh: revision 1.5
        tests/net/npf/t_npf.sh: revision 1.6
        tests/net/npf/t_npf.sh: revision 1.7

npftest: Add AF_* parameter to test cases.
No functional change intended.
Preparation to add test cases for:
PR bin/55403: npfctl miscompiles IPv6 rules

npftest: Add a test to match groups of IPv6 addresses.
The npf_rule test group is now an xfail.  (npftest doesn't have a way
to mark individual cases in a test group as xfail, so this will have
to do for now.)
PR bin/55403: npfctl miscompiles IPv6 rules

npftest: Fix newly added test.
- Adapt new test to actually exercise new rules.
- Mark the right test xfail.
PR bin/55403: npfctl miscompiles IPv6 rules

npftest: Expand test cases to cover more compiler paths.
Cover masked ranges with full- and partial-word sizes.
PR bin/55403: npfctl miscompiles IPv6 rules

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.13.2.3 -r1.13.2.4 src/usr.sbin/npf/npfctl/npf_bpf_comp.c
cvs rdiff -u -r1.7.2.2 -r1.7.2.3 src/usr.sbin/npf/npftest/npftest.conf
cvs rdiff -u -r1.17.2.2 -r1.17.2.3 \
    src/usr.sbin/npf/npftest/libnpftest/npf_rule_test.c

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

Modified files:

Index: src/usr.sbin/npf/npfctl/npf_bpf_comp.c
diff -u src/usr.sbin/npf/npfctl/npf_bpf_comp.c:1.13.2.3 src/usr.sbin/npf/npfctl/npf_bpf_comp.c:1.13.2.4
--- src/usr.sbin/npf/npfctl/npf_bpf_comp.c:1.13.2.3	Sat Jun 20 15:46:48 2020
+++ src/usr.sbin/npf/npfctl/npf_bpf_comp.c	Sun Nov 17 13:58:11 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.13.2.3 2020/06/20 15:46:48 martin Exp $");
+__RCSID("$NetBSD: npf_bpf_comp.c,v 1.13.2.4 2024/11/17 13:58:11 martin 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],

Index: src/usr.sbin/npf/npftest/npftest.conf
diff -u src/usr.sbin/npf/npftest/npftest.conf:1.7.2.2 src/usr.sbin/npf/npftest/npftest.conf:1.7.2.3
--- src/usr.sbin/npf/npftest/npftest.conf:1.7.2.2	Sat Jun 20 15:46:48 2020
+++ src/usr.sbin/npf/npftest/npftest.conf	Sun Nov 17 13:58:11 2024
@@ -1,4 +1,4 @@
-# $NetBSD: npftest.conf,v 1.7.2.2 2020/06/20 15:46:48 martin Exp $
+# $NetBSD: npftest.conf,v 1.7.2.3 2024/11/17 13:58:11 martin Exp $
 
 $ext_if = "npftest0"
 $int_if = "npftest1"
@@ -30,6 +30,10 @@ map $ext_if dynamic $local_ip1 port 6000
 $net6_inner = fd01:203:405::/48
 $net6_outer = 2001:db8:1::/48
 
+# Example of multiple addresses with a common 32-bit word, taken from
+# PR bin/55403: npfctl miscompiles IPv6 rules.
+$net6_pr55403 = { fe80::1, fe80::1000:0:0/95, fe80::2, fe80::2000:0:0/96, fe80::3, fe80::3000:0:0/97 }
+
 $net_a = 10.100.0.0/16
 $net_b = 10.255.0.0/16
 
@@ -51,6 +55,7 @@ group "ext" on $ext_if {
 	pass stateful out final from $local_net
 	pass stateful in final to any port $ports
 	pass stateful in final proto icmp all
+
 	block all
 }
 
@@ -59,6 +64,9 @@ group "int" on $int_if {
 	pass stateful out final to $local_ip2
 	pass out final to $local_ip3
 	block final to $local_ip4
+
+	pass in final family inet6 proto udp from $net6_pr55403
+	pass in final family inet6 proto udp from ! $net6_pr55403 to $net6_pr55403
 }
 
 group default {

Index: src/usr.sbin/npf/npftest/libnpftest/npf_rule_test.c
diff -u src/usr.sbin/npf/npftest/libnpftest/npf_rule_test.c:1.17.2.2 src/usr.sbin/npf/npftest/libnpftest/npf_rule_test.c:1.17.2.3
--- src/usr.sbin/npf/npftest/libnpftest/npf_rule_test.c:1.17.2.2	Sun Sep  1 13:21:39 2019
+++ src/usr.sbin/npf/npftest/libnpftest/npf_rule_test.c	Sun Nov 17 13:58:11 2024
@@ -15,6 +15,7 @@
 #define	RESULT_BLOCK	ENETUNREACH
 
 static const struct test_case {
+	int		af;
 	const char *	src;
 	const char *	dst;
 	const char *	ifname;
@@ -25,11 +26,13 @@ static const struct test_case {
 
 	/* Stateful pass. */
 	{
+		.af = AF_INET,
 		.src = "10.1.1.1",		.dst = "10.1.1.2",
 		.ifname = IFNAME_INT,		.di = PFIL_OUT,
 		.stateful_ret = RESULT_PASS,	.ret = RESULT_PASS
 	},
 	{
+		.af = AF_INET,
 		.src = "10.1.1.2",		.dst = "10.1.1.1",
 		.ifname = IFNAME_INT,		.di = PFIL_IN,
 		.stateful_ret = RESULT_PASS,	.ret = RESULT_BLOCK
@@ -37,18 +40,153 @@ static const struct test_case {
 
 	/* Pass forwards stream only. */
 	{
+		.af = AF_INET,
 		.src = "10.1.1.1",		.dst = "10.1.1.3",
 		.ifname = IFNAME_INT,		.di = PFIL_OUT,
 		.stateful_ret = RESULT_PASS,	.ret = RESULT_PASS
 	},
 	{
+		.af = AF_INET,
 		.src = "10.1.1.3",		.dst = "10.1.1.1",
 		.ifname = IFNAME_INT,		.di = PFIL_IN,
 		.stateful_ret = RESULT_BLOCK,	.ret = RESULT_BLOCK
 	},
 
+	/*
+	 * Pass in from any of the { fe80::1, fe80:1000:0:0/95,
+	 * fe80::2, fe80::2000:0:0/96, fe80::3, fe80::3000:0:0/97 }
+	 * group.
+	 */
+	{			/* fe80::1 */
+		.af = AF_INET6,
+		.src = "fe80::1", .dst = "fe80::adec:c91c:d116:7592",
+		.ifname = IFNAME_INT,		.di = PFIL_IN,
+		.stateful_ret = RESULT_PASS,	.ret = RESULT_PASS
+	},
+	{			/* fe80::1000:0:0/95 */
+		.af = AF_INET6,
+		.src = "fe80::1001:0:0", .dst = "fe80::adec:c91c:d116:7592",
+		.ifname = IFNAME_INT,		.di = PFIL_IN,
+		.stateful_ret = RESULT_PASS,	.ret = RESULT_PASS
+	},
+	{			/* fe80::1000:0:0/95, one bit off */
+		.af = AF_INET6,
+		.src = "fe80::1003:0:0", .dst = "fe80::adec:c91c:d116:7592",
+		.ifname = IFNAME_INT,		.di = PFIL_IN,
+		.stateful_ret = RESULT_BLOCK,	.ret = RESULT_BLOCK
+	},
+	{			/* fe80::2 */
+		.af = AF_INET6,
+		.src = "fe80::2", .dst = "fe80::adec:c91c:d116:7592",
+		.ifname = IFNAME_INT,		.di = PFIL_IN,
+		.stateful_ret = RESULT_PASS,	.ret = RESULT_PASS
+	},
+	{			/* fe80::2000:0:0/96 */
+		.af = AF_INET6,
+		.src = "fe80::2000:8000:0", .dst = "fe80::adec:c91c:d116:7592",
+		.ifname = IFNAME_INT,		.di = PFIL_IN,
+		.stateful_ret = RESULT_PASS,	.ret = RESULT_PASS
+	},
+	{			/* fe80::2000:0:0/96, one bit off */
+		.af = AF_INET6,
+		.src = "fe80::2001:8000:0", .dst = "fe80::adec:c91c:d116:7592",
+		.ifname = IFNAME_INT,		.di = PFIL_IN,
+		.stateful_ret = RESULT_BLOCK,	.ret = RESULT_BLOCK
+	},
+	{			/* fe80::3 */
+		.af = AF_INET6,
+		.src = "fe80::3", .dst = "fe80::adec:c91c:d116:7592",
+		.ifname = IFNAME_INT,		.di = PFIL_IN,
+		.stateful_ret = RESULT_PASS,	.ret = RESULT_PASS
+	},
+	{			/* fe80::3000:0:0/97 */
+		.af = AF_INET6,
+		.src = "fe80::3000:7fff:0", .dst = "fe80::adec:c91c:d116:7592",
+		.ifname = IFNAME_INT,		.di = PFIL_IN,
+		.stateful_ret = RESULT_PASS,	.ret = RESULT_PASS
+	},
+	{			/* fe80::3000:0:0/97, one bit off */
+		.af = AF_INET6,
+		.src = "fe80::3000:ffff:0", .dst = "fe80::adec:c91c:d116:7592",
+		.ifname = IFNAME_INT,		.di = PFIL_IN,
+		.stateful_ret = RESULT_BLOCK,	.ret = RESULT_BLOCK
+	},
+	{
+		.af = AF_INET6,
+		.src = "fe80::4", .dst = "fe80::adec:c91c:d116:7592",
+		.ifname = IFNAME_INT,		.di = PFIL_IN,
+		.stateful_ret = RESULT_BLOCK,	.ret = RESULT_BLOCK
+	},
+
+	/*
+	 * Pass in from anywhere _not_ in that group, as long as it is
+	 * to that group.
+	 */
+	{			/* fe80::1 */
+		.af = AF_INET6,
+		.src = "fe80::adec:c91c:d116:7592", .dst = "fe80::1",
+		.ifname = IFNAME_INT,		.di = PFIL_IN,
+		.stateful_ret = RESULT_PASS,	.ret = RESULT_PASS
+	},
+	{			/* fe80::1000:0:0/95 */
+		.af = AF_INET6,
+		.src = "fe80::adec:c91c:d116:7592", .dst = "fe80::1001:0:0",
+		.ifname = IFNAME_INT,		.di = PFIL_IN,
+		.stateful_ret = RESULT_PASS,	.ret = RESULT_PASS
+	},
+	{			/* fe80::1000:0:0/95, one bit off */
+		.af = AF_INET6,
+		.src = "fe80::adec:c91c:d116:7592", .dst = "fe80::1003:0:0",
+		.ifname = IFNAME_INT,		.di = PFIL_IN,
+		.stateful_ret = RESULT_BLOCK,	.ret = RESULT_BLOCK
+	},
+	{			/* fe80::2 */
+		.af = AF_INET6,
+		.src = "fe80::adec:c91c:d116:7592", .dst = "fe80::2",
+		.ifname = IFNAME_INT,		.di = PFIL_IN,
+		.stateful_ret = RESULT_PASS,	.ret = RESULT_PASS
+	},
+	{			/* fe80::2000:0:0/96 */
+		.af = AF_INET6,
+		.src = "fe80::adec:c91c:d116:7592", .dst = "fe80::2000:8000:0",
+		.ifname = IFNAME_INT,		.di = PFIL_IN,
+		.stateful_ret = RESULT_PASS,	.ret = RESULT_PASS
+	},
+	{			/* fe80::2000:0:0/96, one bit off */
+		.af = AF_INET6,
+		.src = "fe80::adec:c91c:d116:7592", .dst = "fe80::2001:8000:0",
+		.ifname = IFNAME_INT,		.di = PFIL_IN,
+		.stateful_ret = RESULT_BLOCK,	.ret = RESULT_BLOCK
+	},
+	{			/* fe80::3 */
+		.af = AF_INET6,
+		.src = "fe80::adec:c91c:d116:7592", .dst = "fe80::3",
+		.ifname = IFNAME_INT,		.di = PFIL_IN,
+		.stateful_ret = RESULT_PASS,	.ret = RESULT_PASS
+	},
+	{			/* fe80::3000:0:0/97 */
+		.af = AF_INET6,
+		.src = "fe80::adec:c91c:d116:7592", .dst = "fe80::3000:7fff:0",
+		.ifname = IFNAME_INT,		.di = PFIL_IN,
+		.stateful_ret = RESULT_PASS,	.ret = RESULT_PASS
+	},
+	{			/* fe80::3000:0:0/97, one bit off */
+		.af = AF_INET6,
+		.src = "fe80::adec:c91c:d116:7592", .dst = "fe80::3000:ffff:0",
+		.ifname = IFNAME_INT,		.di = PFIL_IN,
+		.stateful_ret = RESULT_BLOCK,	.ret = RESULT_BLOCK
+	},
+	{
+		.af = AF_INET6,
+		.src = "fe80::adec:c91c:d116:7592", .dst = "fe80::4",
+		.ifname = IFNAME_INT,		.di = PFIL_IN,
+		.stateful_ret = RESULT_BLOCK,	.ret = RESULT_BLOCK
+	},
+
 	/* Block. */
-	{	.src = "10.1.1.1",		.dst = "10.1.1.4",
+	{
+		.af = AF_INET,
+		.src = "10.1.1.1",		.dst = "10.1.1.4",
 		.ifname = IFNAME_INT,		.di = PFIL_OUT,
 		.stateful_ret = RESULT_BLOCK,	.ret = RESULT_BLOCK
 	},
@@ -65,7 +203,7 @@ run_raw_testcase(unsigned i)
 	npf_rule_t *rl;
 	int slock, error;
 
-	m = mbuf_get_pkt(AF_INET, IPPROTO_UDP, t->src, t->dst, 9000, 9000);
+	m = mbuf_get_pkt(t->af, IPPROTO_UDP, t->src, t->dst, 9000, 9000);
 	npc = get_cached_pkt(m, t->ifname);
 
 	slock = npf_config_read_enter(npf);
@@ -91,7 +229,7 @@ run_handler_testcase(unsigned i)
 	struct mbuf *m;
 	int error;
 
-	m = mbuf_get_pkt(AF_INET, IPPROTO_UDP, t->src, t->dst, 9000, 9000);
+	m = mbuf_get_pkt(t->af, IPPROTO_UDP, t->src, t->dst, 9000, 9000);
 	error = npfk_packet_handler(npf, &m, ifp, t->di);
 	if (m) {
 		m_freem(m);

Reply via email to