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],