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);