* [PATCH bpf-next 00/13] BPF register bounds range vs range support
@ 2023-11-03 0:08 Andrii Nakryiko
2023-11-03 0:08 ` [PATCH bpf-next 01/13] bpf: generalize reg_set_min_max() to handle non-const register comparisons Andrii Nakryiko
` (12 more replies)
0 siblings, 13 replies; 39+ messages in thread
From: Andrii Nakryiko @ 2023-11-03 0:08 UTC (permalink / raw)
To: bpf, ast, daniel, martin.lau; +Cc: andrii, kernel-team
This patch set is a continuation of work started in [0]. It adds a big set of
manual, auto-generated, and now also random test cases validating BPF
verifier's register bounds tracking and deduction logic.
First few patches generalize verifier's logic to handle conditional jumps and
corresponding range adjustments in case when two non-const registers are
compared to each other. Patch #1 generalizes reg_set_min_max() portion, while
patch #2 does the same for is_branch_taken() part of the overall solution.
Patch #3 improves equality and inequality for cases when BPF program code
mixes 64-bit and 32-bit uses of the same register. Depending on specific
sequence, it's possible to get to the point where u64/s64 bounds will be very
generic (e.g., after signed 32-bit comparison), while we still keep pretty
tight u32/s32 bounds. If in such state we proceed with 32-bit equality or
inequality comparison, reg_set_min_max() might have to deal with adjusting s32
bounds for two registers that don't overlap, which breaks reg_set_min_max().
This doesn't manifest in <range> vs <const> cases, because if that happens
reg_set_min_max() in effect will force s32 bounds to be a new "impossible"
constant (from original smin32/smax32 bounds point of view). Things get tricky
when we have <range> vs <range> adjustments, so instead of trying to somehow
make sense out of such situations, it's best to detect such impossible
situations and prune the branch that can't be taken in is_branch_taken()
logic. This equality/inequality was the only such category of situations with
auto-generated tests added later in the patch set.
But when we start mixing arithmetic operations in different numeric domains
and conditionals, things get even hairier. So, patch #4 adds sanity checking
logic after all ALU/ALU64, JMP/JMP32, and LDX operations. By default, instead
of failing verification, we conservatively reset range bounds to unknown
values, reporting violation in verifier log (if verbose logs are requested).
But to aid development, detection, and debugging, we also introduce a new test
flag, BPF_F_TEST_SANITY_STRICT, which triggers verification failure on range
sanity violation.
Patch #11 sets BPF_F_TEST_SANITY_STRICT by default for test_progs and
test_verifier. Patch #12 adds support for controlling this in veristat for
testing with production BPF object files.
Getting back to BPF verifier, patches #5 and #6 complete verifier's range
tracking logic clean up. See respective patches for details.
With kernel-side taken care of, we move to testing. We start with building
a tester that validates existing <range> vs <scalar> verifier logic for range
bounds. Patch #7 implements an initial version of such a tester. We guard
millions of generated tests behind SLOW_TESTS=1 envvar requirement, but also
have a relatively small number of tricky cases that came up during development
and debugging of this work. Those will be executed as part of a normal
test_progs run.
Patch #8 simulates more nuanced JEQ/JNE logic we added to verifier in patch #3.
Patch #9 adds <range> vs <range> "slow tests".
Patch #10 is a completely new one, it adds a bunch of randomly generated cases
to be run normally, without SLOW_TESTS=1 guard. This should help to get
a bunch of cover, and hopefully find some remaining latent problems if
verifier proactively as part of normal BPF CI runs.
Finally, a tiny test which was, amazingly, an initial motivation for this
whole work, is added in lucky patch #13, demonstrating how verifier is now
smart enough to track actual number of elements in the array and won't require
additional checks on loop iteration variable inside the bpf_for() open-coded
iterator loop.
[0] https://patchwork.kernel.org/project/netdevbpf/list/?series=798308&state=*
Andrii Nakryiko (13):
bpf: generalize reg_set_min_max() to handle non-const register comparisons
bpf: generalize is_scalar_branch_taken() logic
bpf: enhance BPF_JEQ/BPF_JNE is_branch_taken logic
bpf: add register bounds sanity checks and sanitization
bpf: remove redundant s{32,64} -> u{32,64} deduction logic
bpf: make __reg{32,64}_deduce_bounds logic more robust
selftests/bpf: BPF register range bounds tester
selftests/bpf: adjust OP_EQ/OP_NE handling to use subranges for branch taken
selftests/bpf: add range x range test to reg_bounds
selftests/bpf: add randomized reg_bounds tests
selftests/bpf: set BPF_F_TEST_SANITY_SCRIPT by default
veristat: add ability to set BPF_F_TEST_SANITY_STRICT flag with -r flag
selftests/bpf: add iter test requiring range x range logic
include/linux/bpf_verifier.h | 1 +
include/linux/tnum.h | 4 +
include/uapi/linux/bpf.h | 3 +
kernel/bpf/syscall.c | 3 +-
kernel/bpf/tnum.c | 7 +-
kernel/bpf/verifier.c | 619 ++---
tools/include/uapi/linux/bpf.h | 3 +
.../bpf/prog_tests/bpf_verif_scale.c | 2 +-
.../selftests/bpf/prog_tests/reg_bounds.c | 2091 +++++++++++++++++
tools/testing/selftests/bpf/progs/iters.c | 22 +
.../selftests/bpf/progs/verifier_bounds.c | 2 +
tools/testing/selftests/bpf/test_loader.c | 35 +-
tools/testing/selftests/bpf/test_sock_addr.c | 1 +
tools/testing/selftests/bpf/test_verifier.c | 2 +-
tools/testing/selftests/bpf/testing_helpers.c | 4 +-
tools/testing/selftests/bpf/veristat.c | 12 +-
16 files changed, 2511 insertions(+), 300 deletions(-)
create mode 100644 tools/testing/selftests/bpf/prog_tests/reg_bounds.c
--
2.34.1
^ permalink raw reply [flat|nested] 39+ messages in thread* [PATCH bpf-next 01/13] bpf: generalize reg_set_min_max() to handle non-const register comparisons 2023-11-03 0:08 [PATCH bpf-next 00/13] BPF register bounds range vs range support Andrii Nakryiko @ 2023-11-03 0:08 ` Andrii Nakryiko 2023-11-03 7:52 ` Shung-Hsi Yu 2023-11-03 16:20 ` Eduard Zingerman 2023-11-03 0:08 ` [PATCH bpf-next 02/13] bpf: generalize is_scalar_branch_taken() logic Andrii Nakryiko ` (11 subsequent siblings) 12 siblings, 2 replies; 39+ messages in thread From: Andrii Nakryiko @ 2023-11-03 0:08 UTC (permalink / raw) To: bpf, ast, daniel, martin.lau; +Cc: andrii, kernel-team Generalize bounds adjustment logic of reg_set_min_max() to handle not just register vs constant case, but in general any register vs any register cases. For most of the operations it's trivial extension based on range vs range comparison logic, we just need to properly pick min/max of a range to compare against min/max of the other range. For BPF_JSET we keep the original capabilities, just make sure JSET is integrated in the common framework. This is manifested in the internal-only BPF_KSET + BPF_X "opcode" to allow for simpler and more uniform rev_opcode() handling. See the code for details. This allows to reuse the same code exactly both for TRUE and FALSE branches without explicitly handling both conditions with custom code. Note also that now we don't need a special handling of BPF_JEQ/BPF_JNE case none of the registers are constants. This is now just a normal generic case handled by reg_set_min_max(). To make tnum handling cleaner, tnum_with_subreg() helper is added, as that's a common operator when dealing with 32-bit subregister bounds. This keeps the overall logic much less noisy when it comes to tnums. Signed-off-by: Andrii Nakryiko <andrii@kernel.org> --- include/linux/tnum.h | 4 + kernel/bpf/tnum.c | 7 +- kernel/bpf/verifier.c | 327 ++++++++++++++++++++---------------------- 3 files changed, 165 insertions(+), 173 deletions(-) diff --git a/include/linux/tnum.h b/include/linux/tnum.h index 1c3948a1d6ad..3c13240077b8 100644 --- a/include/linux/tnum.h +++ b/include/linux/tnum.h @@ -106,6 +106,10 @@ int tnum_sbin(char *str, size_t size, struct tnum a); struct tnum tnum_subreg(struct tnum a); /* Returns the tnum with the lower 32-bit subreg cleared */ struct tnum tnum_clear_subreg(struct tnum a); +/* Returns the tnum with the lower 32-bit subreg in *reg* set to the lower + * 32-bit subreg in *subreg* + */ +struct tnum tnum_with_subreg(struct tnum reg, struct tnum subreg); /* Returns the tnum with the lower 32-bit subreg set to value */ struct tnum tnum_const_subreg(struct tnum a, u32 value); /* Returns true if 32-bit subreg @a is a known constant*/ diff --git a/kernel/bpf/tnum.c b/kernel/bpf/tnum.c index 3d7127f439a1..f4c91c9b27d7 100644 --- a/kernel/bpf/tnum.c +++ b/kernel/bpf/tnum.c @@ -208,7 +208,12 @@ struct tnum tnum_clear_subreg(struct tnum a) return tnum_lshift(tnum_rshift(a, 32), 32); } +struct tnum tnum_with_subreg(struct tnum reg, struct tnum subreg) +{ + return tnum_or(tnum_clear_subreg(reg), tnum_subreg(subreg)); +} + struct tnum tnum_const_subreg(struct tnum a, u32 value) { - return tnum_or(tnum_clear_subreg(a), tnum_const(value)); + return tnum_with_subreg(a, tnum_const(value)); } diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 2197385d91dc..52934080042c 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -14379,218 +14379,211 @@ static int is_branch_taken(struct bpf_reg_state *reg1, struct bpf_reg_state *reg return is_scalar_branch_taken(reg1, reg2, opcode, is_jmp32); } -/* Adjusts the register min/max values in the case that the dst_reg and - * src_reg are both SCALAR_VALUE registers (or we are simply doing a BPF_K - * check, in which case we havea fake SCALAR_VALUE representing insn->imm). - * Technically we can do similar adjustments for pointers to the same object, - * but we don't support that right now. +/* Opcode that corresponds to a *false* branch condition. + * E.g., if r1 < r2, then reverse (false) condition is r1 >= r2 */ -static void reg_set_min_max(struct bpf_reg_state *true_reg1, - struct bpf_reg_state *true_reg2, - struct bpf_reg_state *false_reg1, - struct bpf_reg_state *false_reg2, - u8 opcode, bool is_jmp32) +static u8 rev_opcode(u8 opcode) { - struct tnum false_32off, false_64off; - struct tnum true_32off, true_64off; - u64 uval; - u32 uval32; - s64 sval; - s32 sval32; - - /* If either register is a pointer, we can't learn anything about its - * variable offset from the compare (unless they were a pointer into - * the same object, but we don't bother with that). + switch (opcode) { + case BPF_JEQ: return BPF_JNE; + case BPF_JNE: return BPF_JEQ; + /* JSET doesn't have it's reverse opcode in BPF, so add + * BPF_X flag to denote the reverse of that operation */ - if (false_reg1->type != SCALAR_VALUE || false_reg2->type != SCALAR_VALUE) - return; - - /* we expect right-hand registers (src ones) to be constants, for now */ - if (!is_reg_const(false_reg2, is_jmp32)) { - opcode = flip_opcode(opcode); - swap(true_reg1, true_reg2); - swap(false_reg1, false_reg2); + case BPF_JSET: return BPF_JSET | BPF_X; + case BPF_JSET | BPF_X: return BPF_JSET; + case BPF_JGE: return BPF_JLT; + case BPF_JGT: return BPF_JLE; + case BPF_JLE: return BPF_JGT; + case BPF_JLT: return BPF_JGE; + case BPF_JSGE: return BPF_JSLT; + case BPF_JSGT: return BPF_JSLE; + case BPF_JSLE: return BPF_JSGT; + case BPF_JSLT: return BPF_JSGE; + default: return 0; } - if (!is_reg_const(false_reg2, is_jmp32)) - return; +} - false_32off = tnum_subreg(false_reg1->var_off); - false_64off = false_reg1->var_off; - true_32off = tnum_subreg(true_reg1->var_off); - true_64off = true_reg1->var_off; - uval = false_reg2->var_off.value; - uval32 = (u32)tnum_subreg(false_reg2->var_off).value; - sval = (s64)uval; - sval32 = (s32)uval32; +/* Refine range knowledge for <reg1> <op> <reg>2 conditional operation. */ +static void regs_refine_cond_op(struct bpf_reg_state *reg1, struct bpf_reg_state *reg2, + u8 opcode, bool is_jmp32) +{ + struct tnum t; switch (opcode) { - /* JEQ/JNE comparison doesn't change the register equivalence. - * - * r1 = r2; - * if (r1 == 42) goto label; - * ... - * label: // here both r1 and r2 are known to be 42. - * - * Hence when marking register as known preserve it's ID. - */ case BPF_JEQ: if (is_jmp32) { - __mark_reg32_known(true_reg1, uval32); - true_32off = tnum_subreg(true_reg1->var_off); + reg1->u32_min_value = max(reg1->u32_min_value, reg2->u32_min_value); + reg1->u32_max_value = min(reg1->u32_max_value, reg2->u32_max_value); + reg1->s32_min_value = max(reg1->s32_min_value, reg2->s32_min_value); + reg1->s32_max_value = min(reg1->s32_max_value, reg2->s32_max_value); + reg2->u32_min_value = reg1->u32_min_value; + reg2->u32_max_value = reg1->u32_max_value; + reg2->s32_min_value = reg1->s32_min_value; + reg2->s32_max_value = reg1->s32_max_value; + + t = tnum_intersect(tnum_subreg(reg1->var_off), tnum_subreg(reg2->var_off)); + reg1->var_off = tnum_with_subreg(reg1->var_off, t); + reg2->var_off = tnum_with_subreg(reg2->var_off, t); } else { - ___mark_reg_known(true_reg1, uval); - true_64off = true_reg1->var_off; + reg1->umin_value = max(reg1->umin_value, reg2->umin_value); + reg1->umax_value = min(reg1->umax_value, reg2->umax_value); + reg1->smin_value = max(reg1->smin_value, reg2->smin_value); + reg1->smax_value = min(reg1->smax_value, reg2->smax_value); + reg2->umin_value = reg1->umin_value; + reg2->umax_value = reg1->umax_value; + reg2->smin_value = reg1->smin_value; + reg2->smax_value = reg1->smax_value; + + reg1->var_off = tnum_intersect(reg1->var_off, reg2->var_off); + reg2->var_off = reg1->var_off; } break; case BPF_JNE: + /* we don't derive any new information for inequality yet */ + break; + case BPF_JSET: + case BPF_JSET | BPF_X: { /* BPF_JSET and its reverse, see rev_opcode() */ + u64 val; + + if (!is_reg_const(reg2, is_jmp32)) + swap(reg1, reg2); + if (!is_reg_const(reg2, is_jmp32)) + break; + + val = reg_const_value(reg2, is_jmp32); + /* BPF_JSET (i.e., TRUE branch, *not* BPF_JSET | BPF_X) + * requires single bit to learn something useful. E.g., if we + * know that `r1 & 0x3` is true, then which bits (0, 1, or both) + * are actually set? We can learn something definite only if + * it's a single-bit value to begin with. + * + * BPF_JSET | BPF_X (i.e., negation of BPF_JSET) doesn't have + * this restriction. I.e., !(r1 & 0x3) means neither bit 0 nor + * bit 1 is set, which we can readily use in adjustments. + */ + if (!(opcode & BPF_X) && !is_power_of_2(val)) + break; + if (is_jmp32) { - __mark_reg32_known(false_reg1, uval32); - false_32off = tnum_subreg(false_reg1->var_off); + if (opcode & BPF_X) + t = tnum_and(tnum_subreg(reg1->var_off), tnum_const(~val)); + else + t = tnum_or(tnum_subreg(reg1->var_off), tnum_const(val)); + reg1->var_off = tnum_with_subreg(reg1->var_off, t); } else { - ___mark_reg_known(false_reg1, uval); - false_64off = false_reg1->var_off; + if (opcode & BPF_X) + reg1->var_off = tnum_and(reg1->var_off, tnum_const(~val)); + else + reg1->var_off = tnum_or(reg1->var_off, tnum_const(val)); } break; - case BPF_JSET: + } + case BPF_JGE: if (is_jmp32) { - false_32off = tnum_and(false_32off, tnum_const(~uval32)); - if (is_power_of_2(uval32)) - true_32off = tnum_or(true_32off, - tnum_const(uval32)); + reg1->u32_min_value = max(reg1->u32_min_value, reg2->u32_min_value); + reg2->u32_max_value = min(reg1->u32_max_value, reg2->u32_max_value); } else { - false_64off = tnum_and(false_64off, tnum_const(~uval)); - if (is_power_of_2(uval)) - true_64off = tnum_or(true_64off, - tnum_const(uval)); + reg1->umin_value = max(reg1->umin_value, reg2->umin_value); + reg2->umax_value = min(reg1->umax_value, reg2->umax_value); } break; - case BPF_JGE: case BPF_JGT: - { if (is_jmp32) { - u32 false_umax = opcode == BPF_JGT ? uval32 : uval32 - 1; - u32 true_umin = opcode == BPF_JGT ? uval32 + 1 : uval32; - - false_reg1->u32_max_value = min(false_reg1->u32_max_value, - false_umax); - true_reg1->u32_min_value = max(true_reg1->u32_min_value, - true_umin); + reg1->u32_min_value = max(reg1->u32_min_value, reg2->u32_min_value + 1); + reg2->u32_max_value = min(reg1->u32_max_value - 1, reg2->u32_max_value); } else { - u64 false_umax = opcode == BPF_JGT ? uval : uval - 1; - u64 true_umin = opcode == BPF_JGT ? uval + 1 : uval; - - false_reg1->umax_value = min(false_reg1->umax_value, false_umax); - true_reg1->umin_value = max(true_reg1->umin_value, true_umin); + reg1->umin_value = max(reg1->umin_value, reg2->umin_value + 1); + reg2->umax_value = min(reg1->umax_value - 1, reg2->umax_value); } break; - } case BPF_JSGE: + if (is_jmp32) { + reg1->s32_min_value = max(reg1->s32_min_value, reg2->s32_min_value); + reg2->s32_max_value = min(reg1->s32_max_value, reg2->s32_max_value); + } else { + reg1->smin_value = max(reg1->smin_value, reg2->smin_value); + reg2->smax_value = min(reg1->smax_value, reg2->smax_value); + } + break; case BPF_JSGT: - { if (is_jmp32) { - s32 false_smax = opcode == BPF_JSGT ? sval32 : sval32 - 1; - s32 true_smin = opcode == BPF_JSGT ? sval32 + 1 : sval32; - - false_reg1->s32_max_value = min(false_reg1->s32_max_value, false_smax); - true_reg1->s32_min_value = max(true_reg1->s32_min_value, true_smin); + reg1->s32_min_value = max(reg1->s32_min_value, reg2->s32_min_value + 1); + reg2->s32_max_value = min(reg1->s32_max_value - 1, reg2->s32_max_value); } else { - s64 false_smax = opcode == BPF_JSGT ? sval : sval - 1; - s64 true_smin = opcode == BPF_JSGT ? sval + 1 : sval; - - false_reg1->smax_value = min(false_reg1->smax_value, false_smax); - true_reg1->smin_value = max(true_reg1->smin_value, true_smin); + reg1->smin_value = max(reg1->smin_value, reg2->smin_value + 1); + reg2->smax_value = min(reg1->smax_value - 1, reg2->smax_value); } break; - } case BPF_JLE: + if (is_jmp32) { + reg1->u32_max_value = min(reg1->u32_max_value, reg2->u32_max_value); + reg2->u32_min_value = max(reg1->u32_min_value, reg2->u32_min_value); + } else { + reg1->umax_value = min(reg1->umax_value, reg2->umax_value); + reg2->umin_value = max(reg1->umin_value, reg2->umin_value); + } + break; case BPF_JLT: - { if (is_jmp32) { - u32 false_umin = opcode == BPF_JLT ? uval32 : uval32 + 1; - u32 true_umax = opcode == BPF_JLT ? uval32 - 1 : uval32; - - false_reg1->u32_min_value = max(false_reg1->u32_min_value, - false_umin); - true_reg1->u32_max_value = min(true_reg1->u32_max_value, - true_umax); + reg1->u32_max_value = min(reg1->u32_max_value, reg2->u32_max_value - 1); + reg2->u32_min_value = max(reg1->u32_min_value + 1, reg2->u32_min_value); } else { - u64 false_umin = opcode == BPF_JLT ? uval : uval + 1; - u64 true_umax = opcode == BPF_JLT ? uval - 1 : uval; - - false_reg1->umin_value = max(false_reg1->umin_value, false_umin); - true_reg1->umax_value = min(true_reg1->umax_value, true_umax); + reg1->umax_value = min(reg1->umax_value, reg2->umax_value - 1); + reg2->umin_value = max(reg1->umin_value + 1, reg2->umin_value); } break; - } case BPF_JSLE: + if (is_jmp32) { + reg1->s32_max_value = min(reg1->s32_max_value, reg2->s32_max_value); + reg2->s32_min_value = max(reg1->s32_min_value, reg2->s32_min_value); + } else { + reg1->smax_value = min(reg1->smax_value, reg2->smax_value); + reg2->smin_value = max(reg1->smin_value, reg2->smin_value); + } + break; case BPF_JSLT: - { if (is_jmp32) { - s32 false_smin = opcode == BPF_JSLT ? sval32 : sval32 + 1; - s32 true_smax = opcode == BPF_JSLT ? sval32 - 1 : sval32; - - false_reg1->s32_min_value = max(false_reg1->s32_min_value, false_smin); - true_reg1->s32_max_value = min(true_reg1->s32_max_value, true_smax); + reg1->s32_max_value = min(reg1->s32_max_value, reg2->s32_max_value - 1); + reg2->s32_min_value = max(reg1->s32_min_value + 1, reg2->s32_min_value); } else { - s64 false_smin = opcode == BPF_JSLT ? sval : sval + 1; - s64 true_smax = opcode == BPF_JSLT ? sval - 1 : sval; - - false_reg1->smin_value = max(false_reg1->smin_value, false_smin); - true_reg1->smax_value = min(true_reg1->smax_value, true_smax); + reg1->smax_value = min(reg1->smax_value, reg2->smax_value - 1); + reg2->smin_value = max(reg1->smin_value + 1, reg2->smin_value); } break; - } default: return; } - - if (is_jmp32) { - false_reg1->var_off = tnum_or(tnum_clear_subreg(false_64off), - tnum_subreg(false_32off)); - true_reg1->var_off = tnum_or(tnum_clear_subreg(true_64off), - tnum_subreg(true_32off)); - reg_bounds_sync(false_reg1); - reg_bounds_sync(true_reg1); - } else { - false_reg1->var_off = false_64off; - true_reg1->var_off = true_64off; - reg_bounds_sync(false_reg1); - reg_bounds_sync(true_reg1); - } -} - -/* Regs are known to be equal, so intersect their min/max/var_off */ -static void __reg_combine_min_max(struct bpf_reg_state *src_reg, - struct bpf_reg_state *dst_reg) -{ - src_reg->umin_value = dst_reg->umin_value = max(src_reg->umin_value, - dst_reg->umin_value); - src_reg->umax_value = dst_reg->umax_value = min(src_reg->umax_value, - dst_reg->umax_value); - src_reg->smin_value = dst_reg->smin_value = max(src_reg->smin_value, - dst_reg->smin_value); - src_reg->smax_value = dst_reg->smax_value = min(src_reg->smax_value, - dst_reg->smax_value); - src_reg->var_off = dst_reg->var_off = tnum_intersect(src_reg->var_off, - dst_reg->var_off); - reg_bounds_sync(src_reg); - reg_bounds_sync(dst_reg); } -static void reg_combine_min_max(struct bpf_reg_state *true_src, - struct bpf_reg_state *true_dst, - struct bpf_reg_state *false_src, - struct bpf_reg_state *false_dst, - u8 opcode) +/* Adjusts the register min/max values in the case that the dst_reg and + * src_reg are both SCALAR_VALUE registers (or we are simply doing a BPF_K + * check, in which case we havea fake SCALAR_VALUE representing insn->imm). + * Technically we can do similar adjustments for pointers to the same object, + * but we don't support that right now. + */ +static void reg_set_min_max(struct bpf_reg_state *true_reg1, + struct bpf_reg_state *true_reg2, + struct bpf_reg_state *false_reg1, + struct bpf_reg_state *false_reg2, + u8 opcode, bool is_jmp32) { - switch (opcode) { - case BPF_JEQ: - __reg_combine_min_max(true_src, true_dst); - break; - case BPF_JNE: - __reg_combine_min_max(false_src, false_dst); - break; - } + /* If either register is a pointer, we can't learn anything about its + * variable offset from the compare (unless they were a pointer into + * the same object, but we don't bother with that). + */ + if (false_reg1->type != SCALAR_VALUE || false_reg2->type != SCALAR_VALUE) + return; + + /* fallthrough (FALSE) branch */ + regs_refine_cond_op(false_reg1, false_reg2, rev_opcode(opcode), is_jmp32); + reg_bounds_sync(false_reg1); + reg_bounds_sync(false_reg2); + + /* jump (TRUE) branch */ + regs_refine_cond_op(true_reg1, true_reg2, opcode, is_jmp32); + reg_bounds_sync(true_reg1); + reg_bounds_sync(true_reg2); } static void mark_ptr_or_null_reg(struct bpf_func_state *state, @@ -14887,22 +14880,12 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env, reg_set_min_max(&other_branch_regs[insn->dst_reg], &other_branch_regs[insn->src_reg], dst_reg, src_reg, opcode, is_jmp32); - - if (dst_reg->type == SCALAR_VALUE && - src_reg->type == SCALAR_VALUE && - !is_jmp32 && (opcode == BPF_JEQ || opcode == BPF_JNE)) { - /* Comparing for equality, we can combine knowledge */ - reg_combine_min_max(&other_branch_regs[insn->src_reg], - &other_branch_regs[insn->dst_reg], - src_reg, dst_reg, opcode); - } } else /* BPF_SRC(insn->code) == BPF_K */ { reg_set_min_max(&other_branch_regs[insn->dst_reg], src_reg /* fake one */, dst_reg, src_reg /* same fake one */, opcode, is_jmp32); } - if (BPF_SRC(insn->code) == BPF_X && src_reg->type == SCALAR_VALUE && src_reg->id && !WARN_ON_ONCE(src_reg->id != other_branch_regs[insn->src_reg].id)) { -- 2.34.1 ^ permalink raw reply related [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 01/13] bpf: generalize reg_set_min_max() to handle non-const register comparisons 2023-11-03 0:08 ` [PATCH bpf-next 01/13] bpf: generalize reg_set_min_max() to handle non-const register comparisons Andrii Nakryiko @ 2023-11-03 7:52 ` Shung-Hsi Yu 2023-11-03 8:33 ` Shung-Hsi Yu 2023-11-03 20:39 ` Andrii Nakryiko 2023-11-03 16:20 ` Eduard Zingerman 1 sibling, 2 replies; 39+ messages in thread From: Shung-Hsi Yu @ 2023-11-03 7:52 UTC (permalink / raw) To: Andrii Nakryiko; +Cc: bpf, ast, daniel, martin.lau, kernel-team On Thu, Nov 02, 2023 at 05:08:10PM -0700, Andrii Nakryiko wrote: > Generalize bounds adjustment logic of reg_set_min_max() to handle not > just register vs constant case, but in general any register vs any > register cases. For most of the operations it's trivial extension based > on range vs range comparison logic, we just need to properly pick > min/max of a range to compare against min/max of the other range. > > For BPF_JSET we keep the original capabilities, just make sure JSET is > integrated in the common framework. This is manifested in the > internal-only BPF_KSET + BPF_X "opcode" to allow for simpler and more ^ typo? Two more comments below > uniform rev_opcode() handling. See the code for details. This allows to > reuse the same code exactly both for TRUE and FALSE branches without > explicitly handling both conditions with custom code. > > Note also that now we don't need a special handling of BPF_JEQ/BPF_JNE > case none of the registers are constants. This is now just a normal > generic case handled by reg_set_min_max(). > > To make tnum handling cleaner, tnum_with_subreg() helper is added, as > that's a common operator when dealing with 32-bit subregister bounds. > This keeps the overall logic much less noisy when it comes to tnums. > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org> > --- > include/linux/tnum.h | 4 + > kernel/bpf/tnum.c | 7 +- > kernel/bpf/verifier.c | 327 ++++++++++++++++++++---------------------- > 3 files changed, 165 insertions(+), 173 deletions(-) > > diff --git a/include/linux/tnum.h b/include/linux/tnum.h > index 1c3948a1d6ad..3c13240077b8 100644 > --- a/include/linux/tnum.h > +++ b/include/linux/tnum.h > @@ -106,6 +106,10 @@ int tnum_sbin(char *str, size_t size, struct tnum a); > struct tnum tnum_subreg(struct tnum a); > /* Returns the tnum with the lower 32-bit subreg cleared */ > struct tnum tnum_clear_subreg(struct tnum a); > +/* Returns the tnum with the lower 32-bit subreg in *reg* set to the lower > + * 32-bit subreg in *subreg* > + */ > +struct tnum tnum_with_subreg(struct tnum reg, struct tnum subreg); > /* Returns the tnum with the lower 32-bit subreg set to value */ > struct tnum tnum_const_subreg(struct tnum a, u32 value); > /* Returns true if 32-bit subreg @a is a known constant*/ > diff --git a/kernel/bpf/tnum.c b/kernel/bpf/tnum.c > index 3d7127f439a1..f4c91c9b27d7 100644 > --- a/kernel/bpf/tnum.c > +++ b/kernel/bpf/tnum.c > @@ -208,7 +208,12 @@ struct tnum tnum_clear_subreg(struct tnum a) > return tnum_lshift(tnum_rshift(a, 32), 32); > } > > +struct tnum tnum_with_subreg(struct tnum reg, struct tnum subreg) > +{ > + return tnum_or(tnum_clear_subreg(reg), tnum_subreg(subreg)); > +} > + > struct tnum tnum_const_subreg(struct tnum a, u32 value) > { > - return tnum_or(tnum_clear_subreg(a), tnum_const(value)); > + return tnum_with_subreg(a, tnum_const(value)); > } > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 2197385d91dc..52934080042c 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -14379,218 +14379,211 @@ static int is_branch_taken(struct bpf_reg_state *reg1, struct bpf_reg_state *reg > return is_scalar_branch_taken(reg1, reg2, opcode, is_jmp32); > } > > -/* Adjusts the register min/max values in the case that the dst_reg and > - * src_reg are both SCALAR_VALUE registers (or we are simply doing a BPF_K > - * check, in which case we havea fake SCALAR_VALUE representing insn->imm). > - * Technically we can do similar adjustments for pointers to the same object, > - * but we don't support that right now. > +/* Opcode that corresponds to a *false* branch condition. > + * E.g., if r1 < r2, then reverse (false) condition is r1 >= r2 > */ > -static void reg_set_min_max(struct bpf_reg_state *true_reg1, > - struct bpf_reg_state *true_reg2, > - struct bpf_reg_state *false_reg1, > - struct bpf_reg_state *false_reg2, > - u8 opcode, bool is_jmp32) > +static u8 rev_opcode(u8 opcode) Nit: rev_opcode and flip_opcode seems like a possible source of confusing down the line. Flip and reverse are often interchangable, i.e. "flip the order" and "reverse the order" is the same thing. Maybe "neg_opcode" or "neg_cond_opcode"? Or do it the otherway around, keep rev_opcode but rename flip_opcode. One more comment about BPF_JSET below > { > - struct tnum false_32off, false_64off; > - struct tnum true_32off, true_64off; > - u64 uval; > - u32 uval32; > - s64 sval; > - s32 sval32; > - > - /* If either register is a pointer, we can't learn anything about its > - * variable offset from the compare (unless they were a pointer into > - * the same object, but we don't bother with that). > + switch (opcode) { > + case BPF_JEQ: return BPF_JNE; > + case BPF_JNE: return BPF_JEQ; > + /* JSET doesn't have it's reverse opcode in BPF, so add > + * BPF_X flag to denote the reverse of that operation > */ > - if (false_reg1->type != SCALAR_VALUE || false_reg2->type != SCALAR_VALUE) > - return; > - > - /* we expect right-hand registers (src ones) to be constants, for now */ > - if (!is_reg_const(false_reg2, is_jmp32)) { > - opcode = flip_opcode(opcode); > - swap(true_reg1, true_reg2); > - swap(false_reg1, false_reg2); > + case BPF_JSET: return BPF_JSET | BPF_X; > + case BPF_JSET | BPF_X: return BPF_JSET; > + case BPF_JGE: return BPF_JLT; > + case BPF_JGT: return BPF_JLE; > + case BPF_JLE: return BPF_JGT; > + case BPF_JLT: return BPF_JGE; > + case BPF_JSGE: return BPF_JSLT; > + case BPF_JSGT: return BPF_JSLE; > + case BPF_JSLE: return BPF_JSGT; > + case BPF_JSLT: return BPF_JSGE; > + default: return 0; > } > - if (!is_reg_const(false_reg2, is_jmp32)) > - return; > +} > > - false_32off = tnum_subreg(false_reg1->var_off); > - false_64off = false_reg1->var_off; > - true_32off = tnum_subreg(true_reg1->var_off); > - true_64off = true_reg1->var_off; > - uval = false_reg2->var_off.value; > - uval32 = (u32)tnum_subreg(false_reg2->var_off).value; > - sval = (s64)uval; > - sval32 = (s32)uval32; > +/* Refine range knowledge for <reg1> <op> <reg>2 conditional operation. */ > +static void regs_refine_cond_op(struct bpf_reg_state *reg1, struct bpf_reg_state *reg2, > + u8 opcode, bool is_jmp32) > +{ > + struct tnum t; > > switch (opcode) { > - /* JEQ/JNE comparison doesn't change the register equivalence. > - * > - * r1 = r2; > - * if (r1 == 42) goto label; > - * ... > - * label: // here both r1 and r2 are known to be 42. > - * > - * Hence when marking register as known preserve it's ID. > - */ > case BPF_JEQ: > if (is_jmp32) { > - __mark_reg32_known(true_reg1, uval32); > - true_32off = tnum_subreg(true_reg1->var_off); > + reg1->u32_min_value = max(reg1->u32_min_value, reg2->u32_min_value); > + reg1->u32_max_value = min(reg1->u32_max_value, reg2->u32_max_value); > + reg1->s32_min_value = max(reg1->s32_min_value, reg2->s32_min_value); > + reg1->s32_max_value = min(reg1->s32_max_value, reg2->s32_max_value); > + reg2->u32_min_value = reg1->u32_min_value; > + reg2->u32_max_value = reg1->u32_max_value; > + reg2->s32_min_value = reg1->s32_min_value; > + reg2->s32_max_value = reg1->s32_max_value; > + > + t = tnum_intersect(tnum_subreg(reg1->var_off), tnum_subreg(reg2->var_off)); > + reg1->var_off = tnum_with_subreg(reg1->var_off, t); > + reg2->var_off = tnum_with_subreg(reg2->var_off, t); > } else { > - ___mark_reg_known(true_reg1, uval); > - true_64off = true_reg1->var_off; > + reg1->umin_value = max(reg1->umin_value, reg2->umin_value); > + reg1->umax_value = min(reg1->umax_value, reg2->umax_value); > + reg1->smin_value = max(reg1->smin_value, reg2->smin_value); > + reg1->smax_value = min(reg1->smax_value, reg2->smax_value); > + reg2->umin_value = reg1->umin_value; > + reg2->umax_value = reg1->umax_value; > + reg2->smin_value = reg1->smin_value; > + reg2->smax_value = reg1->smax_value; > + > + reg1->var_off = tnum_intersect(reg1->var_off, reg2->var_off); > + reg2->var_off = reg1->var_off; > } > break; > case BPF_JNE: > + /* we don't derive any new information for inequality yet */ > + break; > + case BPF_JSET: > + case BPF_JSET | BPF_X: { /* BPF_JSET and its reverse, see rev_opcode() */ > + u64 val; > + > + if (!is_reg_const(reg2, is_jmp32)) > + swap(reg1, reg2); > + if (!is_reg_const(reg2, is_jmp32)) > + break; > + > + val = reg_const_value(reg2, is_jmp32); > + /* BPF_JSET (i.e., TRUE branch, *not* BPF_JSET | BPF_X) > + * requires single bit to learn something useful. E.g., if we > + * know that `r1 & 0x3` is true, then which bits (0, 1, or both) > + * are actually set? We can learn something definite only if > + * it's a single-bit value to begin with. > + * > + * BPF_JSET | BPF_X (i.e., negation of BPF_JSET) doesn't have > + * this restriction. I.e., !(r1 & 0x3) means neither bit 0 nor > + * bit 1 is set, which we can readily use in adjustments. > + */ > + if (!(opcode & BPF_X) && !is_power_of_2(val)) > + break; > + > if (is_jmp32) { > - __mark_reg32_known(false_reg1, uval32); > - false_32off = tnum_subreg(false_reg1->var_off); > + if (opcode & BPF_X) > + t = tnum_and(tnum_subreg(reg1->var_off), tnum_const(~val)); > + else > + t = tnum_or(tnum_subreg(reg1->var_off), tnum_const(val)); > + reg1->var_off = tnum_with_subreg(reg1->var_off, t); > } else { > - ___mark_reg_known(false_reg1, uval); > - false_64off = false_reg1->var_off; > + if (opcode & BPF_X) > + reg1->var_off = tnum_and(reg1->var_off, tnum_const(~val)); > + else > + reg1->var_off = tnum_or(reg1->var_off, tnum_const(val)); > } > break; Since you're already adding a tnum helper, I think we can add one more for BPF_JSET here struct tnum tnum_neg(struct tnum a) { return TNUM(~a.value, a.mask); } So instead of getting a value out of tnum then putting the value back into tnum again u64 val; val = reg_const_value(reg2, is_jmp32); tnum_ops(..., tnum_const(val or ~val); Keep the value in tnum and process it as-is if possible tnum_ops(..., reg2->var_off or tnum_neg(reg2->var_off)); And with that hopefully make this fragment short enough that we don't mind duplicate a bit of code to seperate the BPF_JSET case from the BPF_JSET | BPF_X case. IMO a conditional is_power_of_2 check followed by two level of branching is a bit too much to follow, it is better to have them seperated just like how you're doing it for the others already. I.e. something like the follow case BPF_JSET: { if (!is_reg_const(reg2, is_jmp32)) swap(reg1, reg2); if (!is_reg_const(reg2, is_jmp32)) break; /* comment */ if (!is_power_of_2(reg_const_value(reg2, is_jmp32)) break; if (is_jmp32) { t = tnum_or(tnum_subreg(reg1->var_off), tnum_subreg(reg2->var_off)); reg1->var_off = tnum_with_subreg(reg1->var_off, t); } else { reg1->var_off = tnum_or(reg1->var_off, reg2->var_off); } break; } case BPF_JSET | BPF_X: { if (!is_reg_const(reg2, is_jmp32)) swap(reg1, reg2); if (!is_reg_const(reg2, is_jmp32)) break; if (is_jmp32) { /* a slightly long line ... */ t = tnum_and(tnum_subreg(reg1->var_off), tnum_neg(tnum_subreg(reg2->var_off))); reg1->var_off = tnum_with_subreg(reg1->var_off, t); } else { reg1->var_off = tnum_and(reg1->var_off, tnum_neg(reg2->var_off)); } break; } > ... ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 01/13] bpf: generalize reg_set_min_max() to handle non-const register comparisons 2023-11-03 7:52 ` Shung-Hsi Yu @ 2023-11-03 8:33 ` Shung-Hsi Yu 2023-11-03 20:39 ` Andrii Nakryiko 1 sibling, 0 replies; 39+ messages in thread From: Shung-Hsi Yu @ 2023-11-03 8:33 UTC (permalink / raw) To: Andrii Nakryiko; +Cc: bpf, ast, daniel, martin.lau, kernel-team On Fri, Nov 03, 2023 at 03:52:36PM +0800, Shung-Hsi Yu wrote: > On Thu, Nov 02, 2023 at 05:08:10PM -0700, Andrii Nakryiko wrote: > > Generalize bounds adjustment logic of reg_set_min_max() to handle not > > just register vs constant case, but in general any register vs any > > register cases. For most of the operations it's trivial extension based > > on range vs range comparison logic, we just need to properly pick > > min/max of a range to compare against min/max of the other range. > > > > For BPF_JSET we keep the original capabilities, just make sure JSET is > > integrated in the common framework. This is manifested in the > > internal-only BPF_KSET + BPF_X "opcode" to allow for simpler and more > ^ typo? > > Two more comments below > > > uniform rev_opcode() handling. See the code for details. This allows to > > reuse the same code exactly both for TRUE and FALSE branches without > > explicitly handling both conditions with custom code. > > > > Note also that now we don't need a special handling of BPF_JEQ/BPF_JNE > > case none of the registers are constants. This is now just a normal > > generic case handled by reg_set_min_max(). > > > > To make tnum handling cleaner, tnum_with_subreg() helper is added, as > > that's a common operator when dealing with 32-bit subregister bounds. > > This keeps the overall logic much less noisy when it comes to tnums. > > > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org> > > --- > > include/linux/tnum.h | 4 + > > kernel/bpf/tnum.c | 7 +- > > kernel/bpf/verifier.c | 327 ++++++++++++++++++++---------------------- > > 3 files changed, 165 insertions(+), 173 deletions(-) > > > > diff --git a/include/linux/tnum.h b/include/linux/tnum.h > > index 1c3948a1d6ad..3c13240077b8 100644 > > --- a/include/linux/tnum.h > > +++ b/include/linux/tnum.h > > @@ -106,6 +106,10 @@ int tnum_sbin(char *str, size_t size, struct tnum a); > > struct tnum tnum_subreg(struct tnum a); > > /* Returns the tnum with the lower 32-bit subreg cleared */ > > struct tnum tnum_clear_subreg(struct tnum a); > > +/* Returns the tnum with the lower 32-bit subreg in *reg* set to the lower > > + * 32-bit subreg in *subreg* > > + */ > > +struct tnum tnum_with_subreg(struct tnum reg, struct tnum subreg); > > /* Returns the tnum with the lower 32-bit subreg set to value */ > > struct tnum tnum_const_subreg(struct tnum a, u32 value); > > /* Returns true if 32-bit subreg @a is a known constant*/ > > diff --git a/kernel/bpf/tnum.c b/kernel/bpf/tnum.c > > index 3d7127f439a1..f4c91c9b27d7 100644 > > --- a/kernel/bpf/tnum.c > > +++ b/kernel/bpf/tnum.c > > @@ -208,7 +208,12 @@ struct tnum tnum_clear_subreg(struct tnum a) > > return tnum_lshift(tnum_rshift(a, 32), 32); > > } > > > > +struct tnum tnum_with_subreg(struct tnum reg, struct tnum subreg) > > +{ > > + return tnum_or(tnum_clear_subreg(reg), tnum_subreg(subreg)); > > +} > > + > > struct tnum tnum_const_subreg(struct tnum a, u32 value) > > { > > - return tnum_or(tnum_clear_subreg(a), tnum_const(value)); > > + return tnum_with_subreg(a, tnum_const(value)); > > } > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > > index 2197385d91dc..52934080042c 100644 > > --- a/kernel/bpf/verifier.c > > +++ b/kernel/bpf/verifier.c > > @@ -14379,218 +14379,211 @@ static int is_branch_taken(struct bpf_reg_state *reg1, struct bpf_reg_state *reg > > return is_scalar_branch_taken(reg1, reg2, opcode, is_jmp32); > > } > > > > -/* Adjusts the register min/max values in the case that the dst_reg and > > - * src_reg are both SCALAR_VALUE registers (or we are simply doing a BPF_K > > - * check, in which case we havea fake SCALAR_VALUE representing insn->imm). > > - * Technically we can do similar adjustments for pointers to the same object, > > - * but we don't support that right now. > > +/* Opcode that corresponds to a *false* branch condition. > > + * E.g., if r1 < r2, then reverse (false) condition is r1 >= r2 > > */ > > -static void reg_set_min_max(struct bpf_reg_state *true_reg1, > > - struct bpf_reg_state *true_reg2, > > - struct bpf_reg_state *false_reg1, > > - struct bpf_reg_state *false_reg2, > > - u8 opcode, bool is_jmp32) > > +static u8 rev_opcode(u8 opcode) > > Nit: rev_opcode and flip_opcode seems like a possible source of confusing > down the line. Flip and reverse are often interchangable, i.e. "flip the > order" and "reverse the order" is the same thing. > > Maybe "neg_opcode" or "neg_cond_opcode"? > > Or do it the otherway around, keep rev_opcode but rename flip_opcode. > > One more comment about BPF_JSET below > > > { > > - struct tnum false_32off, false_64off; > > - struct tnum true_32off, true_64off; > > - u64 uval; > > - u32 uval32; > > - s64 sval; > > - s32 sval32; > > - > > - /* If either register is a pointer, we can't learn anything about its > > - * variable offset from the compare (unless they were a pointer into > > - * the same object, but we don't bother with that). > > + switch (opcode) { > > + case BPF_JEQ: return BPF_JNE; > > + case BPF_JNE: return BPF_JEQ; > > + /* JSET doesn't have it's reverse opcode in BPF, so add > > + * BPF_X flag to denote the reverse of that operation > > */ > > - if (false_reg1->type != SCALAR_VALUE || false_reg2->type != SCALAR_VALUE) > > - return; > > - > > - /* we expect right-hand registers (src ones) to be constants, for now */ > > - if (!is_reg_const(false_reg2, is_jmp32)) { > > - opcode = flip_opcode(opcode); > > - swap(true_reg1, true_reg2); > > - swap(false_reg1, false_reg2); > > + case BPF_JSET: return BPF_JSET | BPF_X; > > + case BPF_JSET | BPF_X: return BPF_JSET; > > + case BPF_JGE: return BPF_JLT; > > + case BPF_JGT: return BPF_JLE; > > + case BPF_JLE: return BPF_JGT; > > + case BPF_JLT: return BPF_JGE; > > + case BPF_JSGE: return BPF_JSLT; > > + case BPF_JSGT: return BPF_JSLE; > > + case BPF_JSLE: return BPF_JSGT; > > + case BPF_JSLT: return BPF_JSGE; > > + default: return 0; > > } > > - if (!is_reg_const(false_reg2, is_jmp32)) > > - return; > > +} > > > > - false_32off = tnum_subreg(false_reg1->var_off); > > - false_64off = false_reg1->var_off; > > - true_32off = tnum_subreg(true_reg1->var_off); > > - true_64off = true_reg1->var_off; > > - uval = false_reg2->var_off.value; > > - uval32 = (u32)tnum_subreg(false_reg2->var_off).value; > > - sval = (s64)uval; > > - sval32 = (s32)uval32; > > +/* Refine range knowledge for <reg1> <op> <reg>2 conditional operation. */ > > +static void regs_refine_cond_op(struct bpf_reg_state *reg1, struct bpf_reg_state *reg2, > > + u8 opcode, bool is_jmp32) > > +{ > > + struct tnum t; > > > > switch (opcode) { > > - /* JEQ/JNE comparison doesn't change the register equivalence. > > - * > > - * r1 = r2; > > - * if (r1 == 42) goto label; > > - * ... > > - * label: // here both r1 and r2 are known to be 42. > > - * > > - * Hence when marking register as known preserve it's ID. > > - */ > > case BPF_JEQ: > > if (is_jmp32) { > > - __mark_reg32_known(true_reg1, uval32); > > - true_32off = tnum_subreg(true_reg1->var_off); > > + reg1->u32_min_value = max(reg1->u32_min_value, reg2->u32_min_value); > > + reg1->u32_max_value = min(reg1->u32_max_value, reg2->u32_max_value); > > + reg1->s32_min_value = max(reg1->s32_min_value, reg2->s32_min_value); > > + reg1->s32_max_value = min(reg1->s32_max_value, reg2->s32_max_value); > > + reg2->u32_min_value = reg1->u32_min_value; > > + reg2->u32_max_value = reg1->u32_max_value; > > + reg2->s32_min_value = reg1->s32_min_value; > > + reg2->s32_max_value = reg1->s32_max_value; > > + > > + t = tnum_intersect(tnum_subreg(reg1->var_off), tnum_subreg(reg2->var_off)); > > + reg1->var_off = tnum_with_subreg(reg1->var_off, t); > > + reg2->var_off = tnum_with_subreg(reg2->var_off, t); > > } else { > > - ___mark_reg_known(true_reg1, uval); > > - true_64off = true_reg1->var_off; > > + reg1->umin_value = max(reg1->umin_value, reg2->umin_value); > > + reg1->umax_value = min(reg1->umax_value, reg2->umax_value); > > + reg1->smin_value = max(reg1->smin_value, reg2->smin_value); > > + reg1->smax_value = min(reg1->smax_value, reg2->smax_value); > > + reg2->umin_value = reg1->umin_value; > > + reg2->umax_value = reg1->umax_value; > > + reg2->smin_value = reg1->smin_value; > > + reg2->smax_value = reg1->smax_value; > > + > > + reg1->var_off = tnum_intersect(reg1->var_off, reg2->var_off); > > + reg2->var_off = reg1->var_off; > > } > > break; > > case BPF_JNE: > > + /* we don't derive any new information for inequality yet */ > > + break; > > + case BPF_JSET: > > + case BPF_JSET | BPF_X: { /* BPF_JSET and its reverse, see rev_opcode() */ > > + u64 val; > > + > > + if (!is_reg_const(reg2, is_jmp32)) > > + swap(reg1, reg2); > > + if (!is_reg_const(reg2, is_jmp32)) > > + break; > > + > > + val = reg_const_value(reg2, is_jmp32); > > + /* BPF_JSET (i.e., TRUE branch, *not* BPF_JSET | BPF_X) > > + * requires single bit to learn something useful. E.g., if we > > + * know that `r1 & 0x3` is true, then which bits (0, 1, or both) > > + * are actually set? We can learn something definite only if > > + * it's a single-bit value to begin with. > > + * > > + * BPF_JSET | BPF_X (i.e., negation of BPF_JSET) doesn't have > > + * this restriction. I.e., !(r1 & 0x3) means neither bit 0 nor > > + * bit 1 is set, which we can readily use in adjustments. > > + */ > > + if (!(opcode & BPF_X) && !is_power_of_2(val)) > > + break; > > + > > if (is_jmp32) { > > - __mark_reg32_known(false_reg1, uval32); > > - false_32off = tnum_subreg(false_reg1->var_off); > > + if (opcode & BPF_X) > > + t = tnum_and(tnum_subreg(reg1->var_off), tnum_const(~val)); > > + else > > + t = tnum_or(tnum_subreg(reg1->var_off), tnum_const(val)); > > + reg1->var_off = tnum_with_subreg(reg1->var_off, t); > > } else { > > - ___mark_reg_known(false_reg1, uval); > > - false_64off = false_reg1->var_off; > > + if (opcode & BPF_X) > > + reg1->var_off = tnum_and(reg1->var_off, tnum_const(~val)); > > + else > > + reg1->var_off = tnum_or(reg1->var_off, tnum_const(val)); > > } > > break; > > Since you're already adding a tnum helper, I think we can add one more > for BPF_JSET here > > struct tnum tnum_neg(struct tnum a) > { > return TNUM(~a.value, a.mask); > } Didn't think it through well enough, with the above we might end up with a invalid tnum because the unknown bits gets negated as well, need to mask the unknown bits out. struct tnum tnum_neg(struct tnum a) { return TNUM(~a.value & ~a.mask, a.mask); } > So instead of getting a value out of tnum then putting the value back > into tnum again > > u64 val; > val = reg_const_value(reg2, is_jmp32); > tnum_ops(..., tnum_const(val or ~val); > > Keep the value in tnum and process it as-is if possible > > tnum_ops(..., reg2->var_off or tnum_neg(reg2->var_off)); > > And with that hopefully make this fragment short enough that we don't > mind duplicate a bit of code to seperate the BPF_JSET case from the > BPF_JSET | BPF_X case. IMO a conditional is_power_of_2 check followed by > two level of branching is a bit too much to follow, it is better to have > them seperated just like how you're doing it for the others already. > > I.e. something like the follow > > case BPF_JSET: { > if (!is_reg_const(reg2, is_jmp32)) > swap(reg1, reg2); > if (!is_reg_const(reg2, is_jmp32)) > break; > /* comment */ > if (!is_power_of_2(reg_const_value(reg2, is_jmp32)) > break; > > if (is_jmp32) { > t = tnum_or(tnum_subreg(reg1->var_off), tnum_subreg(reg2->var_off)); > reg1->var_off = tnum_with_subreg(reg1->var_off, t); > } else { > reg1->var_off = tnum_or(reg1->var_off, reg2->var_off); > } > break; > } > case BPF_JSET | BPF_X: { > if (!is_reg_const(reg2, is_jmp32)) > swap(reg1, reg2); > if (!is_reg_const(reg2, is_jmp32)) > break; > > if (is_jmp32) { > /* a slightly long line ... */ > t = tnum_and(tnum_subreg(reg1->var_off), tnum_neg(tnum_subreg(reg2->var_off))); > reg1->var_off = tnum_with_subreg(reg1->var_off, t); > } else { > reg1->var_off = tnum_and(reg1->var_off, tnum_neg(reg2->var_off)); > } > break; > } > > > ... ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 01/13] bpf: generalize reg_set_min_max() to handle non-const register comparisons 2023-11-03 7:52 ` Shung-Hsi Yu 2023-11-03 8:33 ` Shung-Hsi Yu @ 2023-11-03 20:39 ` Andrii Nakryiko 2023-11-03 20:48 ` Andrii Nakryiko 1 sibling, 1 reply; 39+ messages in thread From: Andrii Nakryiko @ 2023-11-03 20:39 UTC (permalink / raw) To: Shung-Hsi Yu; +Cc: Andrii Nakryiko, bpf, ast, daniel, martin.lau, kernel-team On Fri, Nov 3, 2023 at 12:52 AM Shung-Hsi Yu <shung-hsi.yu@suse.com> wrote: > > On Thu, Nov 02, 2023 at 05:08:10PM -0700, Andrii Nakryiko wrote: > > Generalize bounds adjustment logic of reg_set_min_max() to handle not > > just register vs constant case, but in general any register vs any > > register cases. For most of the operations it's trivial extension based > > on range vs range comparison logic, we just need to properly pick > > min/max of a range to compare against min/max of the other range. > > > > For BPF_JSET we keep the original capabilities, just make sure JSET is > > integrated in the common framework. This is manifested in the > > internal-only BPF_KSET + BPF_X "opcode" to allow for simpler and more > ^ typo? > > Two more comments below > > > uniform rev_opcode() handling. See the code for details. This allows to > > reuse the same code exactly both for TRUE and FALSE branches without > > explicitly handling both conditions with custom code. > > > > Note also that now we don't need a special handling of BPF_JEQ/BPF_JNE > > case none of the registers are constants. This is now just a normal > > generic case handled by reg_set_min_max(). > > > > To make tnum handling cleaner, tnum_with_subreg() helper is added, as > > that's a common operator when dealing with 32-bit subregister bounds. > > This keeps the overall logic much less noisy when it comes to tnums. > > > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org> > > --- > > include/linux/tnum.h | 4 + > > kernel/bpf/tnum.c | 7 +- > > kernel/bpf/verifier.c | 327 ++++++++++++++++++++---------------------- > > 3 files changed, 165 insertions(+), 173 deletions(-) > > > > diff --git a/include/linux/tnum.h b/include/linux/tnum.h > > index 1c3948a1d6ad..3c13240077b8 100644 > > --- a/include/linux/tnum.h > > +++ b/include/linux/tnum.h > > @@ -106,6 +106,10 @@ int tnum_sbin(char *str, size_t size, struct tnum a); > > struct tnum tnum_subreg(struct tnum a); > > /* Returns the tnum with the lower 32-bit subreg cleared */ > > struct tnum tnum_clear_subreg(struct tnum a); > > +/* Returns the tnum with the lower 32-bit subreg in *reg* set to the lower > > + * 32-bit subreg in *subreg* > > + */ > > +struct tnum tnum_with_subreg(struct tnum reg, struct tnum subreg); > > /* Returns the tnum with the lower 32-bit subreg set to value */ > > struct tnum tnum_const_subreg(struct tnum a, u32 value); > > /* Returns true if 32-bit subreg @a is a known constant*/ > > diff --git a/kernel/bpf/tnum.c b/kernel/bpf/tnum.c > > index 3d7127f439a1..f4c91c9b27d7 100644 > > --- a/kernel/bpf/tnum.c > > +++ b/kernel/bpf/tnum.c > > @@ -208,7 +208,12 @@ struct tnum tnum_clear_subreg(struct tnum a) > > return tnum_lshift(tnum_rshift(a, 32), 32); > > } > > > > +struct tnum tnum_with_subreg(struct tnum reg, struct tnum subreg) > > +{ > > + return tnum_or(tnum_clear_subreg(reg), tnum_subreg(subreg)); > > +} > > + > > struct tnum tnum_const_subreg(struct tnum a, u32 value) > > { > > - return tnum_or(tnum_clear_subreg(a), tnum_const(value)); > > + return tnum_with_subreg(a, tnum_const(value)); > > } > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > > index 2197385d91dc..52934080042c 100644 > > --- a/kernel/bpf/verifier.c > > +++ b/kernel/bpf/verifier.c > > @@ -14379,218 +14379,211 @@ static int is_branch_taken(struct bpf_reg_state *reg1, struct bpf_reg_state *reg > > return is_scalar_branch_taken(reg1, reg2, opcode, is_jmp32); > > } > > > > -/* Adjusts the register min/max values in the case that the dst_reg and > > - * src_reg are both SCALAR_VALUE registers (or we are simply doing a BPF_K > > - * check, in which case we havea fake SCALAR_VALUE representing insn->imm). > > - * Technically we can do similar adjustments for pointers to the same object, > > - * but we don't support that right now. > > +/* Opcode that corresponds to a *false* branch condition. > > + * E.g., if r1 < r2, then reverse (false) condition is r1 >= r2 > > */ > > -static void reg_set_min_max(struct bpf_reg_state *true_reg1, > > - struct bpf_reg_state *true_reg2, > > - struct bpf_reg_state *false_reg1, > > - struct bpf_reg_state *false_reg2, > > - u8 opcode, bool is_jmp32) > > +static u8 rev_opcode(u8 opcode) > > Nit: rev_opcode and flip_opcode seems like a possible source of confusing > down the line. Flip and reverse are often interchangable, i.e. "flip the > order" and "reverse the order" is the same thing. > > Maybe "neg_opcode" or "neg_cond_opcode"? neg has too strong connotation with BPF_NEG, so not really happy with this one. In selftest I used "complement_op", but it's also quite arbitrary. > > Or do it the otherway around, keep rev_opcode but rename flip_opcode. how about flip_opcode -> swap_opcode? and then keep reg_opcode as is? > > One more comment about BPF_JSET below > please trim big chunks of code you are not commenting on to keep emails a bit shorter [...] > > if (is_jmp32) { > > - __mark_reg32_known(false_reg1, uval32); > > - false_32off = tnum_subreg(false_reg1->var_off); > > + if (opcode & BPF_X) > > + t = tnum_and(tnum_subreg(reg1->var_off), tnum_const(~val)); > > + else > > + t = tnum_or(tnum_subreg(reg1->var_off), tnum_const(val)); > > + reg1->var_off = tnum_with_subreg(reg1->var_off, t); > > } else { > > - ___mark_reg_known(false_reg1, uval); > > - false_64off = false_reg1->var_off; > > + if (opcode & BPF_X) > > + reg1->var_off = tnum_and(reg1->var_off, tnum_const(~val)); > > + else > > + reg1->var_off = tnum_or(reg1->var_off, tnum_const(val)); > > } > > break; > > Since you're already adding a tnum helper, I think we can add one more > for BPF_JSET here > > struct tnum tnum_neg(struct tnum a) > { > return TNUM(~a.value, a.mask); > } > I'm not sure what tnum_neg() does (even if the correct implementation), but either way I'd like to minimize touching tnum stuff, it's too tricky :) we can address that as a separate patch if you'd like > So instead of getting a value out of tnum then putting the value back > into tnum again > > u64 val; > val = reg_const_value(reg2, is_jmp32); > tnum_ops(..., tnum_const(val or ~val); > > Keep the value in tnum and process it as-is if possible > > tnum_ops(..., reg2->var_off or tnum_neg(reg2->var_off)); > > And with that hopefully make this fragment short enough that we don't > mind duplicate a bit of code to seperate the BPF_JSET case from the > BPF_JSET | BPF_X case. IMO a conditional is_power_of_2 check followed by > two level of branching is a bit too much to follow, it is better to have > them seperated just like how you're doing it for the others already. I can split those two cases without any new tnum helpers, the duplicated part is just const checking, basically, no big deal > > I.e. something like the follow > > case BPF_JSET: { > if (!is_reg_const(reg2, is_jmp32)) > swap(reg1, reg2); > if (!is_reg_const(reg2, is_jmp32)) > break; > /* comment */ > if (!is_power_of_2(reg_const_value(reg2, is_jmp32)) > break; > > if (is_jmp32) { > t = tnum_or(tnum_subreg(reg1->var_off), tnum_subreg(reg2->var_off)); > reg1->var_off = tnum_with_subreg(reg1->var_off, t); > } else { > reg1->var_off = tnum_or(reg1->var_off, reg2->var_off); > } > break; > } > case BPF_JSET | BPF_X: { > if (!is_reg_const(reg2, is_jmp32)) > swap(reg1, reg2); > if (!is_reg_const(reg2, is_jmp32)) > break; > > if (is_jmp32) { > /* a slightly long line ... */ > t = tnum_and(tnum_subreg(reg1->var_off), tnum_neg(tnum_subreg(reg2->var_off))); > reg1->var_off = tnum_with_subreg(reg1->var_off, t); > } else { > reg1->var_off = tnum_and(reg1->var_off, tnum_neg(reg2->var_off)); > } > break; > } > > > ... ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 01/13] bpf: generalize reg_set_min_max() to handle non-const register comparisons 2023-11-03 20:39 ` Andrii Nakryiko @ 2023-11-03 20:48 ` Andrii Nakryiko 2023-11-06 2:22 ` Shung-Hsi Yu 0 siblings, 1 reply; 39+ messages in thread From: Andrii Nakryiko @ 2023-11-03 20:48 UTC (permalink / raw) To: Shung-Hsi Yu; +Cc: Andrii Nakryiko, bpf, ast, daniel, martin.lau, kernel-team On Fri, Nov 3, 2023 at 1:39 PM Andrii Nakryiko <andrii.nakryiko@gmail.com> wrote: > > On Fri, Nov 3, 2023 at 12:52 AM Shung-Hsi Yu <shung-hsi.yu@suse.com> wrote: > > > > On Thu, Nov 02, 2023 at 05:08:10PM -0700, Andrii Nakryiko wrote: > > > Generalize bounds adjustment logic of reg_set_min_max() to handle not > > > just register vs constant case, but in general any register vs any > > > register cases. For most of the operations it's trivial extension based > > > on range vs range comparison logic, we just need to properly pick > > > min/max of a range to compare against min/max of the other range. > > > > > > For BPF_JSET we keep the original capabilities, just make sure JSET is > > > integrated in the common framework. This is manifested in the > > > internal-only BPF_KSET + BPF_X "opcode" to allow for simpler and more > > ^ typo? > > > > Two more comments below > > > > > uniform rev_opcode() handling. See the code for details. This allows to > > > reuse the same code exactly both for TRUE and FALSE branches without > > > explicitly handling both conditions with custom code. > > > > > > Note also that now we don't need a special handling of BPF_JEQ/BPF_JNE > > > case none of the registers are constants. This is now just a normal > > > generic case handled by reg_set_min_max(). > > > > > > To make tnum handling cleaner, tnum_with_subreg() helper is added, as > > > that's a common operator when dealing with 32-bit subregister bounds. > > > This keeps the overall logic much less noisy when it comes to tnums. > > > > > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org> > > > --- > > > include/linux/tnum.h | 4 + > > > kernel/bpf/tnum.c | 7 +- > > > kernel/bpf/verifier.c | 327 ++++++++++++++++++++---------------------- > > > 3 files changed, 165 insertions(+), 173 deletions(-) > > > > > > diff --git a/include/linux/tnum.h b/include/linux/tnum.h > > > index 1c3948a1d6ad..3c13240077b8 100644 > > > --- a/include/linux/tnum.h > > > +++ b/include/linux/tnum.h > > > @@ -106,6 +106,10 @@ int tnum_sbin(char *str, size_t size, struct tnum a); > > > struct tnum tnum_subreg(struct tnum a); > > > /* Returns the tnum with the lower 32-bit subreg cleared */ > > > struct tnum tnum_clear_subreg(struct tnum a); > > > +/* Returns the tnum with the lower 32-bit subreg in *reg* set to the lower > > > + * 32-bit subreg in *subreg* > > > + */ > > > +struct tnum tnum_with_subreg(struct tnum reg, struct tnum subreg); > > > /* Returns the tnum with the lower 32-bit subreg set to value */ > > > struct tnum tnum_const_subreg(struct tnum a, u32 value); > > > /* Returns true if 32-bit subreg @a is a known constant*/ > > > diff --git a/kernel/bpf/tnum.c b/kernel/bpf/tnum.c > > > index 3d7127f439a1..f4c91c9b27d7 100644 > > > --- a/kernel/bpf/tnum.c > > > +++ b/kernel/bpf/tnum.c > > > @@ -208,7 +208,12 @@ struct tnum tnum_clear_subreg(struct tnum a) > > > return tnum_lshift(tnum_rshift(a, 32), 32); > > > } > > > > > > +struct tnum tnum_with_subreg(struct tnum reg, struct tnum subreg) > > > +{ > > > + return tnum_or(tnum_clear_subreg(reg), tnum_subreg(subreg)); > > > +} > > > + > > > struct tnum tnum_const_subreg(struct tnum a, u32 value) > > > { > > > - return tnum_or(tnum_clear_subreg(a), tnum_const(value)); > > > + return tnum_with_subreg(a, tnum_const(value)); > > > } > > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > > > index 2197385d91dc..52934080042c 100644 > > > --- a/kernel/bpf/verifier.c > > > +++ b/kernel/bpf/verifier.c > > > @@ -14379,218 +14379,211 @@ static int is_branch_taken(struct bpf_reg_state *reg1, struct bpf_reg_state *reg > > > return is_scalar_branch_taken(reg1, reg2, opcode, is_jmp32); > > > } > > > > > > -/* Adjusts the register min/max values in the case that the dst_reg and > > > - * src_reg are both SCALAR_VALUE registers (or we are simply doing a BPF_K > > > - * check, in which case we havea fake SCALAR_VALUE representing insn->imm). > > > - * Technically we can do similar adjustments for pointers to the same object, > > > - * but we don't support that right now. > > > +/* Opcode that corresponds to a *false* branch condition. > > > + * E.g., if r1 < r2, then reverse (false) condition is r1 >= r2 > > > */ > > > -static void reg_set_min_max(struct bpf_reg_state *true_reg1, > > > - struct bpf_reg_state *true_reg2, > > > - struct bpf_reg_state *false_reg1, > > > - struct bpf_reg_state *false_reg2, > > > - u8 opcode, bool is_jmp32) > > > +static u8 rev_opcode(u8 opcode) > > > > Nit: rev_opcode and flip_opcode seems like a possible source of confusing > > down the line. Flip and reverse are often interchangable, i.e. "flip the > > order" and "reverse the order" is the same thing. > > > > Maybe "neg_opcode" or "neg_cond_opcode"? > > neg has too strong connotation with BPF_NEG, so not really happy with > this one. In selftest I used "complement_op", but it's also quite > arbitrary. > > > > > Or do it the otherway around, keep rev_opcode but rename flip_opcode. > > how about flip_opcode -> swap_opcode? and then keep reg_opcode as is? nah, swap_opcode sounds wrong as well. I guess I'll just leave it as is for now. > > > > > One more comment about BPF_JSET below > > > > please trim big chunks of code you are not commenting on to keep > emails a bit shorter > > [...] > > > > > if (is_jmp32) { > > > - __mark_reg32_known(false_reg1, uval32); > > > - false_32off = tnum_subreg(false_reg1->var_off); > > > + if (opcode & BPF_X) > > > + t = tnum_and(tnum_subreg(reg1->var_off), tnum_const(~val)); > > > + else > > > + t = tnum_or(tnum_subreg(reg1->var_off), tnum_const(val)); > > > + reg1->var_off = tnum_with_subreg(reg1->var_off, t); > > > } else { > > > - ___mark_reg_known(false_reg1, uval); > > > - false_64off = false_reg1->var_off; > > > + if (opcode & BPF_X) > > > + reg1->var_off = tnum_and(reg1->var_off, tnum_const(~val)); > > > + else > > > + reg1->var_off = tnum_or(reg1->var_off, tnum_const(val)); > > > } > > > break; > > > > Since you're already adding a tnum helper, I think we can add one more > > for BPF_JSET here > > > > struct tnum tnum_neg(struct tnum a) > > { > > return TNUM(~a.value, a.mask); > > } > > > > I'm not sure what tnum_neg() does (even if the correct > implementation), but either way I'd like to minimize touching tnum > stuff, it's too tricky :) we can address that as a separate patch if > you'd like > > > > So instead of getting a value out of tnum then putting the value back > > into tnum again > > > > u64 val; > > val = reg_const_value(reg2, is_jmp32); > > tnum_ops(..., tnum_const(val or ~val); > > > > Keep the value in tnum and process it as-is if possible > > > > tnum_ops(..., reg2->var_off or tnum_neg(reg2->var_off)); > > > > > And with that hopefully make this fragment short enough that we don't > > mind duplicate a bit of code to seperate the BPF_JSET case from the > > BPF_JSET | BPF_X case. IMO a conditional is_power_of_2 check followed by > > two level of branching is a bit too much to follow, it is better to have > > them seperated just like how you're doing it for the others already. > > I can split those two cases without any new tnum helpers, the > duplicated part is just const checking, basically, no big deal > > > > > I.e. something like the follow > > > > case BPF_JSET: { > > if (!is_reg_const(reg2, is_jmp32)) > > swap(reg1, reg2); > > if (!is_reg_const(reg2, is_jmp32)) > > break; > > /* comment */ > > if (!is_power_of_2(reg_const_value(reg2, is_jmp32)) > > break; > > > > if (is_jmp32) { > > t = tnum_or(tnum_subreg(reg1->var_off), tnum_subreg(reg2->var_off)); > > reg1->var_off = tnum_with_subreg(reg1->var_off, t); > > } else { > > reg1->var_off = tnum_or(reg1->var_off, reg2->var_off); > > } > > break; > > } > > case BPF_JSET | BPF_X: { > > if (!is_reg_const(reg2, is_jmp32)) > > swap(reg1, reg2); > > if (!is_reg_const(reg2, is_jmp32)) > > break; > > > > if (is_jmp32) { > > /* a slightly long line ... */ > > t = tnum_and(tnum_subreg(reg1->var_off), tnum_neg(tnum_subreg(reg2->var_off))); > > reg1->var_off = tnum_with_subreg(reg1->var_off, t); > > } else { > > reg1->var_off = tnum_and(reg1->var_off, tnum_neg(reg2->var_off)); > > } > > break; > > } > > > > > ... ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 01/13] bpf: generalize reg_set_min_max() to handle non-const register comparisons 2023-11-03 20:48 ` Andrii Nakryiko @ 2023-11-06 2:22 ` Shung-Hsi Yu 0 siblings, 0 replies; 39+ messages in thread From: Shung-Hsi Yu @ 2023-11-06 2:22 UTC (permalink / raw) To: Andrii Nakryiko Cc: Andrii Nakryiko, bpf, ast, daniel, martin.lau, kernel-team On Fri, Nov 03, 2023 at 01:48:32PM -0700, Andrii Nakryiko wrote: > On Fri, Nov 3, 2023 at 1:39 PM Andrii Nakryiko > <andrii.nakryiko@gmail.com> wrote: > > On Fri, Nov 3, 2023 at 12:52 AM Shung-Hsi Yu <shung-hsi.yu@suse.com> wrote: > > > On Thu, Nov 02, 2023 at 05:08:10PM -0700, Andrii Nakryiko wrote: > > > > Generalize bounds adjustment logic of reg_set_min_max() to handle not > > > > just register vs constant case, but in general any register vs any > > > > register cases. For most of the operations it's trivial extension based > > > > on range vs range comparison logic, we just need to properly pick > > > > min/max of a range to compare against min/max of the other range. > > > > > > > > For BPF_JSET we keep the original capabilities, just make sure JSET is > > > > integrated in the common framework. This is manifested in the > > > > internal-only BPF_KSET + BPF_X "opcode" to allow for simpler and more > > > ^ typo? > > > > > > Two more comments below > > > > > > > uniform rev_opcode() handling. See the code for details. This allows to > > > > reuse the same code exactly both for TRUE and FALSE branches without > > > > explicitly handling both conditions with custom code. > > > > > > > > Note also that now we don't need a special handling of BPF_JEQ/BPF_JNE > > > > case none of the registers are constants. This is now just a normal > > > > generic case handled by reg_set_min_max(). > > > > > > > > To make tnum handling cleaner, tnum_with_subreg() helper is added, as > > > > that's a common operator when dealing with 32-bit subregister bounds. > > > > This keeps the overall logic much less noisy when it comes to tnums. > > > > > > > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org> > > > > --- > > > > include/linux/tnum.h | 4 + > > > > kernel/bpf/tnum.c | 7 +- > > > > kernel/bpf/verifier.c | 327 ++++++++++++++++++++---------------------- > > > > 3 files changed, 165 insertions(+), 173 deletions(-) ...] > > > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > > > > index 2197385d91dc..52934080042c 100644 > > > > --- a/kernel/bpf/verifier.c > > > > +++ b/kernel/bpf/verifier.c > > > > @@ -14379,218 +14379,211 @@ static int is_branch_taken(struct bpf_reg_state *reg1, struct bpf_reg_state *reg > > > > return is_scalar_branch_taken(reg1, reg2, opcode, is_jmp32); > > > > } > > > > > > > > -/* Adjusts the register min/max values in the case that the dst_reg and > > > > - * src_reg are both SCALAR_VALUE registers (or we are simply doing a BPF_K > > > > - * check, in which case we havea fake SCALAR_VALUE representing insn->imm). > > > > - * Technically we can do similar adjustments for pointers to the same object, > > > > - * but we don't support that right now. > > > > +/* Opcode that corresponds to a *false* branch condition. > > > > + * E.g., if r1 < r2, then reverse (false) condition is r1 >= r2 > > > > */ > > > > -static void reg_set_min_max(struct bpf_reg_state *true_reg1, > > > > - struct bpf_reg_state *true_reg2, > > > > - struct bpf_reg_state *false_reg1, > > > > - struct bpf_reg_state *false_reg2, > > > > - u8 opcode, bool is_jmp32) > > > > +static u8 rev_opcode(u8 opcode) > > > > > > Nit: rev_opcode and flip_opcode seems like a possible source of confusing > > > down the line. Flip and reverse are often interchangable, i.e. "flip the > > > order" and "reverse the order" is the same thing. > > > > > > Maybe "neg_opcode" or "neg_cond_opcode"? > > > > neg has too strong connotation with BPF_NEG, so not really happy with > > this one. That's true. > > In selftest I used "complement_op", but it's also quite arbitrary. > > > > > Or do it the otherway around, keep rev_opcode but rename flip_opcode. > > > > how about flip_opcode -> swap_opcode? and then keep reg_opcode as is? > > nah, swap_opcode sounds wrong as well. I guess I'll just leave it as is for now. I don't have any better suggestion in mind, so no objection here. > > > > > > One more comment about BPF_JSET below > > > > please trim big chunks of code you are not commenting on to keep > > emails a bit shorter Noted, will do so next time. > > [...] > > > > > > if (is_jmp32) { > > > > - __mark_reg32_known(false_reg1, uval32); > > > > - false_32off = tnum_subreg(false_reg1->var_off); > > > > + if (opcode & BPF_X) > > > > + t = tnum_and(tnum_subreg(reg1->var_off), tnum_const(~val)); > > > > + else > > > > + t = tnum_or(tnum_subreg(reg1->var_off), tnum_const(val)); > > > > + reg1->var_off = tnum_with_subreg(reg1->var_off, t); > > > > } else { > > > > - ___mark_reg_known(false_reg1, uval); > > > > - false_64off = false_reg1->var_off; > > > > + if (opcode & BPF_X) > > > > + reg1->var_off = tnum_and(reg1->var_off, tnum_const(~val)); > > > > + else > > > > + reg1->var_off = tnum_or(reg1->var_off, tnum_const(val)); > > > > } > > > > break; > > > > > > Since you're already adding a tnum helper, I think we can add one more > > > for BPF_JSET here > > > > > > struct tnum tnum_neg(struct tnum a) > > > { > > > return TNUM(~a.value, a.mask); > > > } > > > > > > > I'm not sure what tnum_neg() does (even if the correct > > implementation), but either way I'd like to minimize touching tnum > > stuff, it's too tricky :) we can address that as a separate patch if > > you'd like Tricky, but not as tricky as this patchset :) Seizing this change chance for some shameless self-promotion of slides I had on tnum https://docs.google.com/presentation/d/1Nz2AIvYwAi3rgMNiLV_bn5JjulHJynu9JHulNrTJuZU/edit#slide=id.g16cabc3ff80_0_87 I've send out the tnum change as RFC for now[0]; will resend it along with the changes proposed here once this patchset or its successor is merged as suggested. 0: https://lore.kernel.org/bpf/20231106021119.10455-1-shung-hsi.yu@suse.com/ > > > So instead of getting a value out of tnum then putting the value back > > > into tnum again > > > > > > u64 val; > > > val = reg_const_value(reg2, is_jmp32); > > > tnum_ops(..., tnum_const(val or ~val); > > > > > > Keep the value in tnum and process it as-is if possible > > > > > > tnum_ops(..., reg2->var_off or tnum_neg(reg2->var_off)); > > > > > > > > And with that hopefully make this fragment short enough that we don't > > > mind duplicate a bit of code to seperate the BPF_JSET case from the > > > BPF_JSET | BPF_X case. IMO a conditional is_power_of_2 check followed by > > > two level of branching is a bit too much to follow, it is better to have > > > them seperated just like how you're doing it for the others already. > > > > I can split those two cases without any new tnum helpers, the > > duplicated part is just const checking, basically, no big deal > > > > > > > > I.e. something like the follow > > > > > > case BPF_JSET: { > > > if (!is_reg_const(reg2, is_jmp32)) > > > swap(reg1, reg2); > > > if (!is_reg_const(reg2, is_jmp32)) > > > break; > > > /* comment */ > > > if (!is_power_of_2(reg_const_value(reg2, is_jmp32)) > > > break; > > > > > > if (is_jmp32) { > > > t = tnum_or(tnum_subreg(reg1->var_off), tnum_subreg(reg2->var_off)); > > > reg1->var_off = tnum_with_subreg(reg1->var_off, t); > > > } else { > > > reg1->var_off = tnum_or(reg1->var_off, reg2->var_off); > > > } > > > break; > > > } > > > case BPF_JSET | BPF_X: { > > > if (!is_reg_const(reg2, is_jmp32)) > > > swap(reg1, reg2); > > > if (!is_reg_const(reg2, is_jmp32)) > > > break; > > > > > > if (is_jmp32) { > > > /* a slightly long line ... */ > > > t = tnum_and(tnum_subreg(reg1->var_off), tnum_neg(tnum_subreg(reg2->var_off))); > > > reg1->var_off = tnum_with_subreg(reg1->var_off, t); > > > } else { > > > reg1->var_off = tnum_and(reg1->var_off, tnum_neg(reg2->var_off)); > > > } > > > break; > > > } > > > > > > > ... ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 01/13] bpf: generalize reg_set_min_max() to handle non-const register comparisons 2023-11-03 0:08 ` [PATCH bpf-next 01/13] bpf: generalize reg_set_min_max() to handle non-const register comparisons Andrii Nakryiko 2023-11-03 7:52 ` Shung-Hsi Yu @ 2023-11-03 16:20 ` Eduard Zingerman 2023-11-03 20:39 ` Andrii Nakryiko 1 sibling, 1 reply; 39+ messages in thread From: Eduard Zingerman @ 2023-11-03 16:20 UTC (permalink / raw) To: Andrii Nakryiko, bpf, ast, daniel, martin.lau; +Cc: kernel-team On Thu, 2023-11-02 at 17:08 -0700, Andrii Nakryiko wrote: > Generalize bounds adjustment logic of reg_set_min_max() to handle not > just register vs constant case, but in general any register vs any > register cases. For most of the operations it's trivial extension based > on range vs range comparison logic, we just need to properly pick > min/max of a range to compare against min/max of the other range. > > For BPF_JSET we keep the original capabilities, just make sure JSET is > integrated in the common framework. This is manifested in the > internal-only BPF_KSET + BPF_X "opcode" to allow for simpler and more > uniform rev_opcode() handling. See the code for details. This allows to > reuse the same code exactly both for TRUE and FALSE branches without > explicitly handling both conditions with custom code. > > Note also that now we don't need a special handling of BPF_JEQ/BPF_JNE > case none of the registers are constants. This is now just a normal > generic case handled by reg_set_min_max(). > > To make tnum handling cleaner, tnum_with_subreg() helper is added, as > that's a common operator when dealing with 32-bit subregister bounds. > This keeps the overall logic much less noisy when it comes to tnums. > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org> Acked-by: Eduard Zingerman <eddyz87@gmail.com> (With one bit of a bikeshedding below). > --- > include/linux/tnum.h | 4 + > kernel/bpf/tnum.c | 7 +- > kernel/bpf/verifier.c | 327 ++++++++++++++++++++---------------------- > 3 files changed, 165 insertions(+), 173 deletions(-) > > diff --git a/include/linux/tnum.h b/include/linux/tnum.h > index 1c3948a1d6ad..3c13240077b8 100644 > --- a/include/linux/tnum.h > +++ b/include/linux/tnum.h > @@ -106,6 +106,10 @@ int tnum_sbin(char *str, size_t size, struct tnum a); > struct tnum tnum_subreg(struct tnum a); > /* Returns the tnum with the lower 32-bit subreg cleared */ > struct tnum tnum_clear_subreg(struct tnum a); > +/* Returns the tnum with the lower 32-bit subreg in *reg* set to the lower > + * 32-bit subreg in *subreg* > + */ > +struct tnum tnum_with_subreg(struct tnum reg, struct tnum subreg); > /* Returns the tnum with the lower 32-bit subreg set to value */ > struct tnum tnum_const_subreg(struct tnum a, u32 value); > /* Returns true if 32-bit subreg @a is a known constant*/ > diff --git a/kernel/bpf/tnum.c b/kernel/bpf/tnum.c > index 3d7127f439a1..f4c91c9b27d7 100644 > --- a/kernel/bpf/tnum.c > +++ b/kernel/bpf/tnum.c > @@ -208,7 +208,12 @@ struct tnum tnum_clear_subreg(struct tnum a) > return tnum_lshift(tnum_rshift(a, 32), 32); > } > > +struct tnum tnum_with_subreg(struct tnum reg, struct tnum subreg) > +{ > + return tnum_or(tnum_clear_subreg(reg), tnum_subreg(subreg)); > +} > + > struct tnum tnum_const_subreg(struct tnum a, u32 value) > { > - return tnum_or(tnum_clear_subreg(a), tnum_const(value)); > + return tnum_with_subreg(a, tnum_const(value)); > } > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 2197385d91dc..52934080042c 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -14379,218 +14379,211 @@ static int is_branch_taken(struct bpf_reg_state *reg1, struct bpf_reg_state *reg > return is_scalar_branch_taken(reg1, reg2, opcode, is_jmp32); > } > > -/* Adjusts the register min/max values in the case that the dst_reg and > - * src_reg are both SCALAR_VALUE registers (or we are simply doing a BPF_K > - * check, in which case we havea fake SCALAR_VALUE representing insn->imm). > - * Technically we can do similar adjustments for pointers to the same object, > - * but we don't support that right now. > +/* Opcode that corresponds to a *false* branch condition. > + * E.g., if r1 < r2, then reverse (false) condition is r1 >= r2 > */ > -static void reg_set_min_max(struct bpf_reg_state *true_reg1, > - struct bpf_reg_state *true_reg2, > - struct bpf_reg_state *false_reg1, > - struct bpf_reg_state *false_reg2, > - u8 opcode, bool is_jmp32) > +static u8 rev_opcode(u8 opcode) > { > - struct tnum false_32off, false_64off; > - struct tnum true_32off, true_64off; > - u64 uval; > - u32 uval32; > - s64 sval; > - s32 sval32; > - > - /* If either register is a pointer, we can't learn anything about its > - * variable offset from the compare (unless they were a pointer into > - * the same object, but we don't bother with that). > + switch (opcode) { > + case BPF_JEQ: return BPF_JNE; > + case BPF_JNE: return BPF_JEQ; > + /* JSET doesn't have it's reverse opcode in BPF, so add > + * BPF_X flag to denote the reverse of that operation > */ > - if (false_reg1->type != SCALAR_VALUE || false_reg2->type != SCALAR_VALUE) > - return; > - > - /* we expect right-hand registers (src ones) to be constants, for now */ > - if (!is_reg_const(false_reg2, is_jmp32)) { > - opcode = flip_opcode(opcode); > - swap(true_reg1, true_reg2); > - swap(false_reg1, false_reg2); > + case BPF_JSET: return BPF_JSET | BPF_X; > + case BPF_JSET | BPF_X: return BPF_JSET; > + case BPF_JGE: return BPF_JLT; > + case BPF_JGT: return BPF_JLE; > + case BPF_JLE: return BPF_JGT; > + case BPF_JLT: return BPF_JGE; > + case BPF_JSGE: return BPF_JSLT; > + case BPF_JSGT: return BPF_JSLE; > + case BPF_JSLE: return BPF_JSGT; > + case BPF_JSLT: return BPF_JSGE; > + default: return 0; > } > - if (!is_reg_const(false_reg2, is_jmp32)) > - return; > +} > > - false_32off = tnum_subreg(false_reg1->var_off); > - false_64off = false_reg1->var_off; > - true_32off = tnum_subreg(true_reg1->var_off); > - true_64off = true_reg1->var_off; > - uval = false_reg2->var_off.value; > - uval32 = (u32)tnum_subreg(false_reg2->var_off).value; > - sval = (s64)uval; > - sval32 = (s32)uval32; > +/* Refine range knowledge for <reg1> <op> <reg>2 conditional operation. */ > +static void regs_refine_cond_op(struct bpf_reg_state *reg1, struct bpf_reg_state *reg2, > + u8 opcode, bool is_jmp32) > +{ > + struct tnum t; > > switch (opcode) { > - /* JEQ/JNE comparison doesn't change the register equivalence. > - * > - * r1 = r2; > - * if (r1 == 42) goto label; > - * ... > - * label: // here both r1 and r2 are known to be 42. > - * > - * Hence when marking register as known preserve it's ID. > - */ > case BPF_JEQ: > if (is_jmp32) { > - __mark_reg32_known(true_reg1, uval32); > - true_32off = tnum_subreg(true_reg1->var_off); > + reg1->u32_min_value = max(reg1->u32_min_value, reg2->u32_min_value); > + reg1->u32_max_value = min(reg1->u32_max_value, reg2->u32_max_value); > + reg1->s32_min_value = max(reg1->s32_min_value, reg2->s32_min_value); > + reg1->s32_max_value = min(reg1->s32_max_value, reg2->s32_max_value); > + reg2->u32_min_value = reg1->u32_min_value; > + reg2->u32_max_value = reg1->u32_max_value; > + reg2->s32_min_value = reg1->s32_min_value; > + reg2->s32_max_value = reg1->s32_max_value; > + > + t = tnum_intersect(tnum_subreg(reg1->var_off), tnum_subreg(reg2->var_off)); > + reg1->var_off = tnum_with_subreg(reg1->var_off, t); > + reg2->var_off = tnum_with_subreg(reg2->var_off, t); > } else { > - ___mark_reg_known(true_reg1, uval); > - true_64off = true_reg1->var_off; > + reg1->umin_value = max(reg1->umin_value, reg2->umin_value); > + reg1->umax_value = min(reg1->umax_value, reg2->umax_value); > + reg1->smin_value = max(reg1->smin_value, reg2->smin_value); > + reg1->smax_value = min(reg1->smax_value, reg2->smax_value); > + reg2->umin_value = reg1->umin_value; > + reg2->umax_value = reg1->umax_value; > + reg2->smin_value = reg1->smin_value; > + reg2->smax_value = reg1->smax_value; > + > + reg1->var_off = tnum_intersect(reg1->var_off, reg2->var_off); > + reg2->var_off = reg1->var_off; > } > break; > case BPF_JNE: > + /* we don't derive any new information for inequality yet */ > + break; > + case BPF_JSET: > + case BPF_JSET | BPF_X: { /* BPF_JSET and its reverse, see rev_opcode() */ > + u64 val; > + > + if (!is_reg_const(reg2, is_jmp32)) > + swap(reg1, reg2); > + if (!is_reg_const(reg2, is_jmp32)) > + break; > + > + val = reg_const_value(reg2, is_jmp32); > + /* BPF_JSET (i.e., TRUE branch, *not* BPF_JSET | BPF_X) > + * requires single bit to learn something useful. E.g., if we > + * know that `r1 & 0x3` is true, then which bits (0, 1, or both) > + * are actually set? We can learn something definite only if > + * it's a single-bit value to begin with. > + * > + * BPF_JSET | BPF_X (i.e., negation of BPF_JSET) doesn't have > + * this restriction. I.e., !(r1 & 0x3) means neither bit 0 nor > + * bit 1 is set, which we can readily use in adjustments. > + */ > + if (!(opcode & BPF_X) && !is_power_of_2(val)) > + break; > + > if (is_jmp32) { > - __mark_reg32_known(false_reg1, uval32); > - false_32off = tnum_subreg(false_reg1->var_off); > + if (opcode & BPF_X) > + t = tnum_and(tnum_subreg(reg1->var_off), tnum_const(~val)); > + else > + t = tnum_or(tnum_subreg(reg1->var_off), tnum_const(val)); > + reg1->var_off = tnum_with_subreg(reg1->var_off, t); > } else { > - ___mark_reg_known(false_reg1, uval); > - false_64off = false_reg1->var_off; > + if (opcode & BPF_X) > + reg1->var_off = tnum_and(reg1->var_off, tnum_const(~val)); > + else > + reg1->var_off = tnum_or(reg1->var_off, tnum_const(val)); > } > break; > - case BPF_JSET: > + } > + case BPF_JGE: > if (is_jmp32) { > - false_32off = tnum_and(false_32off, tnum_const(~uval32)); > - if (is_power_of_2(uval32)) > - true_32off = tnum_or(true_32off, > - tnum_const(uval32)); > + reg1->u32_min_value = max(reg1->u32_min_value, reg2->u32_min_value); > + reg2->u32_max_value = min(reg1->u32_max_value, reg2->u32_max_value); > } else { > - false_64off = tnum_and(false_64off, tnum_const(~uval)); > - if (is_power_of_2(uval)) > - true_64off = tnum_or(true_64off, > - tnum_const(uval)); > + reg1->umin_value = max(reg1->umin_value, reg2->umin_value); > + reg2->umax_value = min(reg1->umax_value, reg2->umax_value); > } > break; > - case BPF_JGE: > case BPF_JGT: > - { > if (is_jmp32) { > - u32 false_umax = opcode == BPF_JGT ? uval32 : uval32 - 1; > - u32 true_umin = opcode == BPF_JGT ? uval32 + 1 : uval32; > - > - false_reg1->u32_max_value = min(false_reg1->u32_max_value, > - false_umax); > - true_reg1->u32_min_value = max(true_reg1->u32_min_value, > - true_umin); > + reg1->u32_min_value = max(reg1->u32_min_value, reg2->u32_min_value + 1); > + reg2->u32_max_value = min(reg1->u32_max_value - 1, reg2->u32_max_value); > } else { > - u64 false_umax = opcode == BPF_JGT ? uval : uval - 1; > - u64 true_umin = opcode == BPF_JGT ? uval + 1 : uval; > - > - false_reg1->umax_value = min(false_reg1->umax_value, false_umax); > - true_reg1->umin_value = max(true_reg1->umin_value, true_umin); > + reg1->umin_value = max(reg1->umin_value, reg2->umin_value + 1); > + reg2->umax_value = min(reg1->umax_value - 1, reg2->umax_value); > } > break; > - } > case BPF_JSGE: > + if (is_jmp32) { > + reg1->s32_min_value = max(reg1->s32_min_value, reg2->s32_min_value); > + reg2->s32_max_value = min(reg1->s32_max_value, reg2->s32_max_value); > + } else { > + reg1->smin_value = max(reg1->smin_value, reg2->smin_value); > + reg2->smax_value = min(reg1->smax_value, reg2->smax_value); > + } > + break; > case BPF_JSGT: It is possible to spare some code by swapping arguments here: case BPF_JLE: case BPF_JLT: case BPF_JSLE: case BPF_JSLT: return regs_refine_cond_op(reg2, reg1, flip_opcode(opcode), is_jmp32); > - { > if (is_jmp32) { > - s32 false_smax = opcode == BPF_JSGT ? sval32 : sval32 - 1; > - s32 true_smin = opcode == BPF_JSGT ? sval32 + 1 : sval32; > - > - false_reg1->s32_max_value = min(false_reg1->s32_max_value, false_smax); > - true_reg1->s32_min_value = max(true_reg1->s32_min_value, true_smin); > + reg1->s32_min_value = max(reg1->s32_min_value, reg2->s32_min_value + 1); > + reg2->s32_max_value = min(reg1->s32_max_value - 1, reg2->s32_max_value); > } else { > - s64 false_smax = opcode == BPF_JSGT ? sval : sval - 1; > - s64 true_smin = opcode == BPF_JSGT ? sval + 1 : sval; > - > - false_reg1->smax_value = min(false_reg1->smax_value, false_smax); > - true_reg1->smin_value = max(true_reg1->smin_value, true_smin); > + reg1->smin_value = max(reg1->smin_value, reg2->smin_value + 1); > + reg2->smax_value = min(reg1->smax_value - 1, reg2->smax_value); > } > break; > - } > case BPF_JLE: > + if (is_jmp32) { > + reg1->u32_max_value = min(reg1->u32_max_value, reg2->u32_max_value); > + reg2->u32_min_value = max(reg1->u32_min_value, reg2->u32_min_value); > + } else { > + reg1->umax_value = min(reg1->umax_value, reg2->umax_value); > + reg2->umin_value = max(reg1->umin_value, reg2->umin_value); > + } > + break; > case BPF_JLT: > - { > if (is_jmp32) { > - u32 false_umin = opcode == BPF_JLT ? uval32 : uval32 + 1; > - u32 true_umax = opcode == BPF_JLT ? uval32 - 1 : uval32; > - > - false_reg1->u32_min_value = max(false_reg1->u32_min_value, > - false_umin); > - true_reg1->u32_max_value = min(true_reg1->u32_max_value, > - true_umax); > + reg1->u32_max_value = min(reg1->u32_max_value, reg2->u32_max_value - 1); > + reg2->u32_min_value = max(reg1->u32_min_value + 1, reg2->u32_min_value); > } else { > - u64 false_umin = opcode == BPF_JLT ? uval : uval + 1; > - u64 true_umax = opcode == BPF_JLT ? uval - 1 : uval; > - > - false_reg1->umin_value = max(false_reg1->umin_value, false_umin); > - true_reg1->umax_value = min(true_reg1->umax_value, true_umax); > + reg1->umax_value = min(reg1->umax_value, reg2->umax_value - 1); > + reg2->umin_value = max(reg1->umin_value + 1, reg2->umin_value); > } > break; > - } > case BPF_JSLE: > + if (is_jmp32) { > + reg1->s32_max_value = min(reg1->s32_max_value, reg2->s32_max_value); > + reg2->s32_min_value = max(reg1->s32_min_value, reg2->s32_min_value); > + } else { > + reg1->smax_value = min(reg1->smax_value, reg2->smax_value); > + reg2->smin_value = max(reg1->smin_value, reg2->smin_value); > + } > + break; > case BPF_JSLT: > - { > if (is_jmp32) { > - s32 false_smin = opcode == BPF_JSLT ? sval32 : sval32 + 1; > - s32 true_smax = opcode == BPF_JSLT ? sval32 - 1 : sval32; > - > - false_reg1->s32_min_value = max(false_reg1->s32_min_value, false_smin); > - true_reg1->s32_max_value = min(true_reg1->s32_max_value, true_smax); > + reg1->s32_max_value = min(reg1->s32_max_value, reg2->s32_max_value - 1); > + reg2->s32_min_value = max(reg1->s32_min_value + 1, reg2->s32_min_value); > } else { > - s64 false_smin = opcode == BPF_JSLT ? sval : sval + 1; > - s64 true_smax = opcode == BPF_JSLT ? sval - 1 : sval; > - > - false_reg1->smin_value = max(false_reg1->smin_value, false_smin); > - true_reg1->smax_value = min(true_reg1->smax_value, true_smax); > + reg1->smax_value = min(reg1->smax_value, reg2->smax_value - 1); > + reg2->smin_value = max(reg1->smin_value + 1, reg2->smin_value); > } > break; > - } > default: > return; > } > - > - if (is_jmp32) { > - false_reg1->var_off = tnum_or(tnum_clear_subreg(false_64off), > - tnum_subreg(false_32off)); > - true_reg1->var_off = tnum_or(tnum_clear_subreg(true_64off), > - tnum_subreg(true_32off)); > - reg_bounds_sync(false_reg1); > - reg_bounds_sync(true_reg1); > - } else { > - false_reg1->var_off = false_64off; > - true_reg1->var_off = true_64off; > - reg_bounds_sync(false_reg1); > - reg_bounds_sync(true_reg1); > - } > -} > - > -/* Regs are known to be equal, so intersect their min/max/var_off */ > -static void __reg_combine_min_max(struct bpf_reg_state *src_reg, > - struct bpf_reg_state *dst_reg) > -{ > - src_reg->umin_value = dst_reg->umin_value = max(src_reg->umin_value, > - dst_reg->umin_value); > - src_reg->umax_value = dst_reg->umax_value = min(src_reg->umax_value, > - dst_reg->umax_value); > - src_reg->smin_value = dst_reg->smin_value = max(src_reg->smin_value, > - dst_reg->smin_value); > - src_reg->smax_value = dst_reg->smax_value = min(src_reg->smax_value, > - dst_reg->smax_value); > - src_reg->var_off = dst_reg->var_off = tnum_intersect(src_reg->var_off, > - dst_reg->var_off); > - reg_bounds_sync(src_reg); > - reg_bounds_sync(dst_reg); > } > > -static void reg_combine_min_max(struct bpf_reg_state *true_src, > - struct bpf_reg_state *true_dst, > - struct bpf_reg_state *false_src, > - struct bpf_reg_state *false_dst, > - u8 opcode) > +/* Adjusts the register min/max values in the case that the dst_reg and > + * src_reg are both SCALAR_VALUE registers (or we are simply doing a BPF_K > + * check, in which case we havea fake SCALAR_VALUE representing insn->imm). > + * Technically we can do similar adjustments for pointers to the same object, > + * but we don't support that right now. > + */ > +static void reg_set_min_max(struct bpf_reg_state *true_reg1, > + struct bpf_reg_state *true_reg2, > + struct bpf_reg_state *false_reg1, > + struct bpf_reg_state *false_reg2, > + u8 opcode, bool is_jmp32) > { > - switch (opcode) { > - case BPF_JEQ: > - __reg_combine_min_max(true_src, true_dst); > - break; > - case BPF_JNE: > - __reg_combine_min_max(false_src, false_dst); > - break; > - } > + /* If either register is a pointer, we can't learn anything about its > + * variable offset from the compare (unless they were a pointer into > + * the same object, but we don't bother with that). > + */ > + if (false_reg1->type != SCALAR_VALUE || false_reg2->type != SCALAR_VALUE) > + return; > + > + /* fallthrough (FALSE) branch */ > + regs_refine_cond_op(false_reg1, false_reg2, rev_opcode(opcode), is_jmp32); > + reg_bounds_sync(false_reg1); > + reg_bounds_sync(false_reg2); > + > + /* jump (TRUE) branch */ > + regs_refine_cond_op(true_reg1, true_reg2, opcode, is_jmp32); > + reg_bounds_sync(true_reg1); > + reg_bounds_sync(true_reg2); > } > > static void mark_ptr_or_null_reg(struct bpf_func_state *state, > @@ -14887,22 +14880,12 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env, > reg_set_min_max(&other_branch_regs[insn->dst_reg], > &other_branch_regs[insn->src_reg], > dst_reg, src_reg, opcode, is_jmp32); > - > - if (dst_reg->type == SCALAR_VALUE && > - src_reg->type == SCALAR_VALUE && > - !is_jmp32 && (opcode == BPF_JEQ || opcode == BPF_JNE)) { > - /* Comparing for equality, we can combine knowledge */ > - reg_combine_min_max(&other_branch_regs[insn->src_reg], > - &other_branch_regs[insn->dst_reg], > - src_reg, dst_reg, opcode); > - } > } else /* BPF_SRC(insn->code) == BPF_K */ { > reg_set_min_max(&other_branch_regs[insn->dst_reg], > src_reg /* fake one */, > dst_reg, src_reg /* same fake one */, > opcode, is_jmp32); > } > - > if (BPF_SRC(insn->code) == BPF_X && > src_reg->type == SCALAR_VALUE && src_reg->id && > !WARN_ON_ONCE(src_reg->id != other_branch_regs[insn->src_reg].id)) { ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 01/13] bpf: generalize reg_set_min_max() to handle non-const register comparisons 2023-11-03 16:20 ` Eduard Zingerman @ 2023-11-03 20:39 ` Andrii Nakryiko 0 siblings, 0 replies; 39+ messages in thread From: Andrii Nakryiko @ 2023-11-03 20:39 UTC (permalink / raw) To: Eduard Zingerman Cc: Andrii Nakryiko, bpf, ast, daniel, martin.lau, kernel-team On Fri, Nov 3, 2023 at 9:20 AM Eduard Zingerman <eddyz87@gmail.com> wrote: > > On Thu, 2023-11-02 at 17:08 -0700, Andrii Nakryiko wrote: > > Generalize bounds adjustment logic of reg_set_min_max() to handle not > > just register vs constant case, but in general any register vs any > > register cases. For most of the operations it's trivial extension based > > on range vs range comparison logic, we just need to properly pick > > min/max of a range to compare against min/max of the other range. > > > > For BPF_JSET we keep the original capabilities, just make sure JSET is > > integrated in the common framework. This is manifested in the > > internal-only BPF_KSET + BPF_X "opcode" to allow for simpler and more > > uniform rev_opcode() handling. See the code for details. This allows to > > reuse the same code exactly both for TRUE and FALSE branches without > > explicitly handling both conditions with custom code. > > > > Note also that now we don't need a special handling of BPF_JEQ/BPF_JNE > > case none of the registers are constants. This is now just a normal > > generic case handled by reg_set_min_max(). > > > > To make tnum handling cleaner, tnum_with_subreg() helper is added, as > > that's a common operator when dealing with 32-bit subregister bounds. > > This keeps the overall logic much less noisy when it comes to tnums. > > > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org> > > Acked-by: Eduard Zingerman <eddyz87@gmail.com> > > (With one bit of a bikeshedding below). > > > --- > > include/linux/tnum.h | 4 + > > kernel/bpf/tnum.c | 7 +- > > kernel/bpf/verifier.c | 327 ++++++++++++++++++++---------------------- > > 3 files changed, 165 insertions(+), 173 deletions(-) > > please trim irrelevant parts [...] > > case BPF_JSGE: > > + if (is_jmp32) { > > + reg1->s32_min_value = max(reg1->s32_min_value, reg2->s32_min_value); > > + reg2->s32_max_value = min(reg1->s32_max_value, reg2->s32_max_value); > > + } else { > > + reg1->smin_value = max(reg1->smin_value, reg2->smin_value); > > + reg2->smax_value = min(reg1->smax_value, reg2->smax_value); > > + } > > + break; > > case BPF_JSGT: > > It is possible to spare some code by swapping arguments here: > > case BPF_JLE: > case BPF_JLT: > case BPF_JSLE: > case BPF_JSLT: > return regs_refine_cond_op(reg2, reg1, flip_opcode(opcode), is_jmp32); yep, math is nice like that :) I'm a bit hesitant to add recursive-looking calls (even though it's not recursion), so maybe I'll just do: case BPF_JLE: case BPF_JLT: case BPF_JSLE: case BPF_JSLT: opcode = flip_opcode(opcode); swap(reg1, reg2); goto again; and goto again will just jump to the beginning of this function? Oh, and I more naturally think about LT/LE as "base conditions", so I'll do the above for GE/GT operations. > > > > - { > > if (is_jmp32) { > > - s32 false_smax = opcode == BPF_JSGT ? sval32 : sval32 - 1; > > - s32 true_smin = opcode == BPF_JSGT ? sval32 + 1 : sval32; > > - > > - false_reg1->s32_max_value = min(false_reg1->s32_max_value, false_smax); > > - true_reg1->s32_min_value = max(true_reg1->s32_min_value, true_smin); > > + reg1->s32_min_value = max(reg1->s32_min_value, reg2->s32_min_value + 1); > > + reg2->s32_max_value = min(reg1->s32_max_value - 1, reg2->s32_max_value); [...] ^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH bpf-next 02/13] bpf: generalize is_scalar_branch_taken() logic 2023-11-03 0:08 [PATCH bpf-next 00/13] BPF register bounds range vs range support Andrii Nakryiko 2023-11-03 0:08 ` [PATCH bpf-next 01/13] bpf: generalize reg_set_min_max() to handle non-const register comparisons Andrii Nakryiko @ 2023-11-03 0:08 ` Andrii Nakryiko 2023-11-03 0:13 ` Andrii Nakryiko 2023-11-03 16:47 ` Eduard Zingerman 2023-11-03 0:08 ` [PATCH bpf-next 03/13] bpf: enhance BPF_JEQ/BPF_JNE is_branch_taken logic Andrii Nakryiko ` (10 subsequent siblings) 12 siblings, 2 replies; 39+ messages in thread From: Andrii Nakryiko @ 2023-11-03 0:08 UTC (permalink / raw) To: bpf, ast, daniel, martin.lau; +Cc: andrii, kernel-team Generalize is_branch_taken logic for SCALAR_VALUE register to handle cases when both registers are not constants. Previously supported <range> vs <scalar> cases are a natural subset of more generic <range> vs <range> set of cases. Generalized logic relies on straightforward segment intersection checks. Signed-off-by: Andrii Nakryiko <andrii@kernel.org> --- kernel/bpf/verifier.c | 103 ++++++++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 40 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 52934080042c..2627461164ed 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -14187,82 +14187,104 @@ static int is_scalar_branch_taken(struct bpf_reg_state *reg1, struct bpf_reg_sta u8 opcode, bool is_jmp32) { struct tnum t1 = is_jmp32 ? tnum_subreg(reg1->var_off) : reg1->var_off; + struct tnum t2 = is_jmp32 ? tnum_subreg(reg2->var_off) : reg2->var_off; u64 umin1 = is_jmp32 ? (u64)reg1->u32_min_value : reg1->umin_value; u64 umax1 = is_jmp32 ? (u64)reg1->u32_max_value : reg1->umax_value; s64 smin1 = is_jmp32 ? (s64)reg1->s32_min_value : reg1->smin_value; s64 smax1 = is_jmp32 ? (s64)reg1->s32_max_value : reg1->smax_value; - u64 uval = is_jmp32 ? (u32)tnum_subreg(reg2->var_off).value : reg2->var_off.value; - s64 sval = is_jmp32 ? (s32)uval : (s64)uval; + u64 umin2 = is_jmp32 ? (u64)reg2->u32_min_value : reg2->umin_value; + u64 umax2 = is_jmp32 ? (u64)reg2->u32_max_value : reg2->umax_value; + s64 smin2 = is_jmp32 ? (s64)reg2->s32_min_value : reg2->smin_value; + s64 smax2 = is_jmp32 ? (s64)reg2->s32_max_value : reg2->smax_value; switch (opcode) { case BPF_JEQ: - if (tnum_is_const(t1)) - return !!tnum_equals_const(t1, uval); - else if (uval < umin1 || uval > umax1) + /* constants, umin/umax and smin/smax checks would be + * redundant in this case because they all should match + */ + if (tnum_is_const(t1) && tnum_is_const(t2)) + return t1.value == t2.value; + /* const ranges */ + if (umin1 == umax1 && umin2 == umax2) + return umin1 == umin2; + if (smin1 == smax1 && smin2 == smax2) + return smin1 == smin2; + /* non-overlapping ranges */ + if (umin1 > umax2 || umax1 < umin2) return 0; - else if (sval < smin1 || sval > smax1) + if (smin1 > smax2 || smax1 < smin2) return 0; break; case BPF_JNE: - if (tnum_is_const(t1)) - return !tnum_equals_const(t1, uval); - else if (uval < umin1 || uval > umax1) + /* constants, umin/umax and smin/smax checks would be + * redundant in this case because they all should match + */ + if (tnum_is_const(t1) && tnum_is_const(t2)) + return t1.value != t2.value; + /* non-overlapping ranges */ + if (umin1 > umax2 || umax1 < umin2) return 1; - else if (sval < smin1 || sval > smax1) + if (smin1 > smax2 || smax1 < smin2) return 1; break; case BPF_JSET: - if ((~t1.mask & t1.value) & uval) + if (!is_reg_const(reg2, is_jmp32)) { + swap(reg1, reg2); + swap(t1, t2); + } + if (!is_reg_const(reg2, is_jmp32)) + return -1; + if ((~t1.mask & t1.value) & t2.value) return 1; - if (!((t1.mask | t1.value) & uval)) + if (!((t1.mask | t1.value) & t2.value)) return 0; break; case BPF_JGT: - if (umin1 > uval ) + if (umin1 > umax2) return 1; - else if (umax1 <= uval) + else if (umax1 <= umin2) return 0; break; case BPF_JSGT: - if (smin1 > sval) + if (smin1 > smax2) return 1; - else if (smax1 <= sval) + else if (smax1 <= smin2) return 0; break; case BPF_JLT: - if (umax1 < uval) + if (umax1 < umin2) return 1; - else if (umin1 >= uval) + else if (umin1 >= umax2) return 0; break; case BPF_JSLT: - if (smax1 < sval) + if (smax1 < smin2) return 1; - else if (smin1 >= sval) + else if (smin1 >= smax2) return 0; break; case BPF_JGE: - if (umin1 >= uval) + if (umin1 >= umax2) return 1; - else if (umax1 < uval) + else if (umax1 < umin2) return 0; break; case BPF_JSGE: - if (smin1 >= sval) + if (smin1 >= smax2) return 1; - else if (smax1 < sval) + else if (smax1 < smin2) return 0; break; case BPF_JLE: - if (umax1 <= uval) + if (umax1 <= umin2) return 1; - else if (umin1 > uval) + else if (umin1 > umax2) return 0; break; case BPF_JSLE: - if (smax1 <= sval) + if (smax1 <= smin2) return 1; - else if (smin1 > sval) + else if (smin1 > smax2) return 0; break; } @@ -14341,28 +14363,28 @@ static int is_pkt_ptr_branch_taken(struct bpf_reg_state *dst_reg, static int is_branch_taken(struct bpf_reg_state *reg1, struct bpf_reg_state *reg2, u8 opcode, bool is_jmp32) { - u64 val; - if (reg_is_pkt_pointer_any(reg1) && reg_is_pkt_pointer_any(reg2) && !is_jmp32) return is_pkt_ptr_branch_taken(reg1, reg2, opcode); - /* try to make sure reg2 is a constant SCALAR_VALUE */ - if (!is_reg_const(reg2, is_jmp32)) { - opcode = flip_opcode(opcode); - swap(reg1, reg2); - } - /* for now we expect reg2 to be a constant to make any useful decisions */ - if (!is_reg_const(reg2, is_jmp32)) - return -1; - val = reg_const_value(reg2, is_jmp32); + if (__is_pointer_value(false, reg1) || __is_pointer_value(false, reg2)) { + u64 val; + + /* arrange that reg2 is a scalar, and reg1 is a pointer */ + if (!is_reg_const(reg2, is_jmp32)) { + opcode = flip_opcode(opcode); + swap(reg1, reg2); + } + /* and ensure that reg2 is a constant */ + if (!is_reg_const(reg2, is_jmp32)) + return -1; - if (__is_pointer_value(false, reg1)) { if (!reg_not_null(reg1)) return -1; /* If pointer is valid tests against zero will fail so we can * use this to direct branch taken. */ + val = reg_const_value(reg2, is_jmp32); if (val != 0) return -1; @@ -14376,6 +14398,7 @@ static int is_branch_taken(struct bpf_reg_state *reg1, struct bpf_reg_state *reg } } + /* now deal with two scalars, but not necessarily constants */ return is_scalar_branch_taken(reg1, reg2, opcode, is_jmp32); } -- 2.34.1 ^ permalink raw reply related [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 02/13] bpf: generalize is_scalar_branch_taken() logic 2023-11-03 0:08 ` [PATCH bpf-next 02/13] bpf: generalize is_scalar_branch_taken() logic Andrii Nakryiko @ 2023-11-03 0:13 ` Andrii Nakryiko 2023-11-03 16:47 ` Eduard Zingerman 1 sibling, 0 replies; 39+ messages in thread From: Andrii Nakryiko @ 2023-11-03 0:13 UTC (permalink / raw) To: Andrii Nakryiko; +Cc: bpf, ast, daniel, martin.lau, kernel-team On Thu, Nov 2, 2023 at 5:08 PM Andrii Nakryiko <andrii@kernel.org> wrote: > > Generalize is_branch_taken logic for SCALAR_VALUE register to handle > cases when both registers are not constants. Previously supported > <range> vs <scalar> cases are a natural subset of more generic <range> > vs <range> set of cases. > > Generalized logic relies on straightforward segment intersection checks. > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org> > --- > kernel/bpf/verifier.c | 103 ++++++++++++++++++++++++++---------------- > 1 file changed, 63 insertions(+), 40 deletions(-) > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 52934080042c..2627461164ed 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -14187,82 +14187,104 @@ static int is_scalar_branch_taken(struct bpf_reg_state *reg1, struct bpf_reg_sta > u8 opcode, bool is_jmp32) > { > struct tnum t1 = is_jmp32 ? tnum_subreg(reg1->var_off) : reg1->var_off; > + struct tnum t2 = is_jmp32 ? tnum_subreg(reg2->var_off) : reg2->var_off; > u64 umin1 = is_jmp32 ? (u64)reg1->u32_min_value : reg1->umin_value; > u64 umax1 = is_jmp32 ? (u64)reg1->u32_max_value : reg1->umax_value; > s64 smin1 = is_jmp32 ? (s64)reg1->s32_min_value : reg1->smin_value; > s64 smax1 = is_jmp32 ? (s64)reg1->s32_max_value : reg1->smax_value; > - u64 uval = is_jmp32 ? (u32)tnum_subreg(reg2->var_off).value : reg2->var_off.value; > - s64 sval = is_jmp32 ? (s32)uval : (s64)uval; > + u64 umin2 = is_jmp32 ? (u64)reg2->u32_min_value : reg2->umin_value; > + u64 umax2 = is_jmp32 ? (u64)reg2->u32_max_value : reg2->umax_value; > + s64 smin2 = is_jmp32 ? (s64)reg2->s32_min_value : reg2->smin_value; > + s64 smax2 = is_jmp32 ? (s64)reg2->s32_max_value : reg2->smax_value; > > switch (opcode) { > case BPF_JEQ: > - if (tnum_is_const(t1)) > - return !!tnum_equals_const(t1, uval); > - else if (uval < umin1 || uval > umax1) > + /* constants, umin/umax and smin/smax checks would be > + * redundant in this case because they all should match > + */ > + if (tnum_is_const(t1) && tnum_is_const(t2)) > + return t1.value == t2.value; > + /* const ranges */ > + if (umin1 == umax1 && umin2 == umax2) > + return umin1 == umin2; > + if (smin1 == smax1 && smin2 == smax2) > + return smin1 == smin2; seems like I didn't remove these checks from BPF_JEQ (but I did for BPF_JNE below). I'll fix it in next revision, but will wait for people to review this one first. > + /* non-overlapping ranges */ > + if (umin1 > umax2 || umax1 < umin2) > return 0; > - else if (sval < smin1 || sval > smax1) > + if (smin1 > smax2 || smax1 < smin2) > return 0; > break; > case BPF_JNE: > - if (tnum_is_const(t1)) > - return !tnum_equals_const(t1, uval); > - else if (uval < umin1 || uval > umax1) > + /* constants, umin/umax and smin/smax checks would be > + * redundant in this case because they all should match > + */ > + if (tnum_is_const(t1) && tnum_is_const(t2)) > + return t1.value != t2.value; > + /* non-overlapping ranges */ > + if (umin1 > umax2 || umax1 < umin2) > return 1; > - else if (sval < smin1 || sval > smax1) > + if (smin1 > smax2 || smax1 < smin2) > return 1; > break; [...] ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 02/13] bpf: generalize is_scalar_branch_taken() logic 2023-11-03 0:08 ` [PATCH bpf-next 02/13] bpf: generalize is_scalar_branch_taken() logic Andrii Nakryiko 2023-11-03 0:13 ` Andrii Nakryiko @ 2023-11-03 16:47 ` Eduard Zingerman 2023-11-03 20:59 ` Andrii Nakryiko 1 sibling, 1 reply; 39+ messages in thread From: Eduard Zingerman @ 2023-11-03 16:47 UTC (permalink / raw) To: Andrii Nakryiko, bpf, ast, daniel, martin.lau; +Cc: kernel-team On Thu, 2023-11-02 at 17:08 -0700, Andrii Nakryiko wrote: > Generalize is_branch_taken logic for SCALAR_VALUE register to handle > cases when both registers are not constants. Previously supported > <range> vs <scalar> cases are a natural subset of more generic <range> > vs <range> set of cases. > > Generalized logic relies on straightforward segment intersection checks. > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org> Acked-by: Eduard Zingerman <eddyz87@gmail.com> (With the same nitpick that '<' cases could be converted to '>' cases). > --- > kernel/bpf/verifier.c | 103 ++++++++++++++++++++++++++---------------- > 1 file changed, 63 insertions(+), 40 deletions(-) > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 52934080042c..2627461164ed 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -14187,82 +14187,104 @@ static int is_scalar_branch_taken(struct bpf_reg_state *reg1, struct bpf_reg_sta > u8 opcode, bool is_jmp32) > { > struct tnum t1 = is_jmp32 ? tnum_subreg(reg1->var_off) : reg1->var_off; > + struct tnum t2 = is_jmp32 ? tnum_subreg(reg2->var_off) : reg2->var_off; > u64 umin1 = is_jmp32 ? (u64)reg1->u32_min_value : reg1->umin_value; > u64 umax1 = is_jmp32 ? (u64)reg1->u32_max_value : reg1->umax_value; > s64 smin1 = is_jmp32 ? (s64)reg1->s32_min_value : reg1->smin_value; > s64 smax1 = is_jmp32 ? (s64)reg1->s32_max_value : reg1->smax_value; > - u64 uval = is_jmp32 ? (u32)tnum_subreg(reg2->var_off).value : reg2->var_off.value; > - s64 sval = is_jmp32 ? (s32)uval : (s64)uval; > + u64 umin2 = is_jmp32 ? (u64)reg2->u32_min_value : reg2->umin_value; > + u64 umax2 = is_jmp32 ? (u64)reg2->u32_max_value : reg2->umax_value; > + s64 smin2 = is_jmp32 ? (s64)reg2->s32_min_value : reg2->smin_value; > + s64 smax2 = is_jmp32 ? (s64)reg2->s32_max_value : reg2->smax_value; > > switch (opcode) { > case BPF_JEQ: > - if (tnum_is_const(t1)) > - return !!tnum_equals_const(t1, uval); > - else if (uval < umin1 || uval > umax1) > + /* constants, umin/umax and smin/smax checks would be > + * redundant in this case because they all should match > + */ > + if (tnum_is_const(t1) && tnum_is_const(t2)) > + return t1.value == t2.value; > + /* const ranges */ > + if (umin1 == umax1 && umin2 == umax2) > + return umin1 == umin2; > + if (smin1 == smax1 && smin2 == smax2) > + return smin1 == smin2; > + /* non-overlapping ranges */ > + if (umin1 > umax2 || umax1 < umin2) > return 0; > - else if (sval < smin1 || sval > smax1) > + if (smin1 > smax2 || smax1 < smin2) > return 0; > break; > case BPF_JNE: > - if (tnum_is_const(t1)) > - return !tnum_equals_const(t1, uval); > - else if (uval < umin1 || uval > umax1) > + /* constants, umin/umax and smin/smax checks would be > + * redundant in this case because they all should match > + */ > + if (tnum_is_const(t1) && tnum_is_const(t2)) > + return t1.value != t2.value; > + /* non-overlapping ranges */ > + if (umin1 > umax2 || umax1 < umin2) > return 1; > - else if (sval < smin1 || sval > smax1) > + if (smin1 > smax2 || smax1 < smin2) > return 1; > break; > case BPF_JSET: > - if ((~t1.mask & t1.value) & uval) > + if (!is_reg_const(reg2, is_jmp32)) { > + swap(reg1, reg2); > + swap(t1, t2); > + } > + if (!is_reg_const(reg2, is_jmp32)) > + return -1; > + if ((~t1.mask & t1.value) & t2.value) > return 1; > - if (!((t1.mask | t1.value) & uval)) > + if (!((t1.mask | t1.value) & t2.value)) > return 0; > break; > case BPF_JGT: > - if (umin1 > uval ) > + if (umin1 > umax2) > return 1; > - else if (umax1 <= uval) > + else if (umax1 <= umin2) > return 0; > break; > case BPF_JSGT: > - if (smin1 > sval) > + if (smin1 > smax2) > return 1; > - else if (smax1 <= sval) > + else if (smax1 <= smin2) > return 0; > break; > case BPF_JLT: > - if (umax1 < uval) > + if (umax1 < umin2) > return 1; > - else if (umin1 >= uval) > + else if (umin1 >= umax2) > return 0; > break; > case BPF_JSLT: > - if (smax1 < sval) > + if (smax1 < smin2) > return 1; > - else if (smin1 >= sval) > + else if (smin1 >= smax2) > return 0; > break; > case BPF_JGE: > - if (umin1 >= uval) > + if (umin1 >= umax2) > return 1; > - else if (umax1 < uval) > + else if (umax1 < umin2) > return 0; > break; > case BPF_JSGE: > - if (smin1 >= sval) > + if (smin1 >= smax2) > return 1; > - else if (smax1 < sval) > + else if (smax1 < smin2) > return 0; > break; > case BPF_JLE: > - if (umax1 <= uval) > + if (umax1 <= umin2) > return 1; > - else if (umin1 > uval) > + else if (umin1 > umax2) > return 0; > break; > case BPF_JSLE: > - if (smax1 <= sval) > + if (smax1 <= smin2) > return 1; > - else if (smin1 > sval) > + else if (smin1 > smax2) > return 0; > break; > } > @@ -14341,28 +14363,28 @@ static int is_pkt_ptr_branch_taken(struct bpf_reg_state *dst_reg, > static int is_branch_taken(struct bpf_reg_state *reg1, struct bpf_reg_state *reg2, > u8 opcode, bool is_jmp32) > { > - u64 val; > - > if (reg_is_pkt_pointer_any(reg1) && reg_is_pkt_pointer_any(reg2) && !is_jmp32) > return is_pkt_ptr_branch_taken(reg1, reg2, opcode); > > - /* try to make sure reg2 is a constant SCALAR_VALUE */ > - if (!is_reg_const(reg2, is_jmp32)) { > - opcode = flip_opcode(opcode); > - swap(reg1, reg2); > - } > - /* for now we expect reg2 to be a constant to make any useful decisions */ > - if (!is_reg_const(reg2, is_jmp32)) > - return -1; > - val = reg_const_value(reg2, is_jmp32); > + if (__is_pointer_value(false, reg1) || __is_pointer_value(false, reg2)) { > + u64 val; > + > + /* arrange that reg2 is a scalar, and reg1 is a pointer */ > + if (!is_reg_const(reg2, is_jmp32)) { > + opcode = flip_opcode(opcode); > + swap(reg1, reg2); > + } > + /* and ensure that reg2 is a constant */ > + if (!is_reg_const(reg2, is_jmp32)) > + return -1; > > - if (__is_pointer_value(false, reg1)) { > if (!reg_not_null(reg1)) > return -1; > > /* If pointer is valid tests against zero will fail so we can > * use this to direct branch taken. > */ > + val = reg_const_value(reg2, is_jmp32); > if (val != 0) > return -1; > > @@ -14376,6 +14398,7 @@ static int is_branch_taken(struct bpf_reg_state *reg1, struct bpf_reg_state *reg > } > } > > + /* now deal with two scalars, but not necessarily constants */ > return is_scalar_branch_taken(reg1, reg2, opcode, is_jmp32); > } > ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 02/13] bpf: generalize is_scalar_branch_taken() logic 2023-11-03 16:47 ` Eduard Zingerman @ 2023-11-03 20:59 ` Andrii Nakryiko 2023-11-03 21:02 ` Andrii Nakryiko 0 siblings, 1 reply; 39+ messages in thread From: Andrii Nakryiko @ 2023-11-03 20:59 UTC (permalink / raw) To: Eduard Zingerman Cc: Andrii Nakryiko, bpf, ast, daniel, martin.lau, kernel-team On Fri, Nov 3, 2023 at 9:47 AM Eduard Zingerman <eddyz87@gmail.com> wrote: > > On Thu, 2023-11-02 at 17:08 -0700, Andrii Nakryiko wrote: > > Generalize is_branch_taken logic for SCALAR_VALUE register to handle > > cases when both registers are not constants. Previously supported > > <range> vs <scalar> cases are a natural subset of more generic <range> > > vs <range> set of cases. > > > > Generalized logic relies on straightforward segment intersection checks. > > > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org> > > Acked-by: Eduard Zingerman <eddyz87@gmail.com> > > (With the same nitpick that '<' cases could be converted to '>' cases). > Ok. > > --- > > kernel/bpf/verifier.c | 103 ++++++++++++++++++++++++++---------------- > > 1 file changed, 63 insertions(+), 40 deletions(-) > > [...] ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 02/13] bpf: generalize is_scalar_branch_taken() logic 2023-11-03 20:59 ` Andrii Nakryiko @ 2023-11-03 21:02 ` Andrii Nakryiko 0 siblings, 0 replies; 39+ messages in thread From: Andrii Nakryiko @ 2023-11-03 21:02 UTC (permalink / raw) To: Eduard Zingerman Cc: Andrii Nakryiko, bpf, ast, daniel, martin.lau, kernel-team On Fri, Nov 3, 2023 at 1:59 PM Andrii Nakryiko <andrii.nakryiko@gmail.com> wrote: > > On Fri, Nov 3, 2023 at 9:47 AM Eduard Zingerman <eddyz87@gmail.com> wrote: > > > > On Thu, 2023-11-02 at 17:08 -0700, Andrii Nakryiko wrote: > > > Generalize is_branch_taken logic for SCALAR_VALUE register to handle > > > cases when both registers are not constants. Previously supported > > > <range> vs <scalar> cases are a natural subset of more generic <range> > > > vs <range> set of cases. > > > > > > Generalized logic relies on straightforward segment intersection checks. > > > > > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org> > > > > Acked-by: Eduard Zingerman <eddyz87@gmail.com> > > > > (With the same nitpick that '<' cases could be converted to '>' cases). > > > > Ok. Actually, this one is more annoying because of all the umin1/umin2/etc initialization at the beginning. The conditions are very straightforward, so I'm inclined to keep it as is for simplicity. > > > > > --- > > > kernel/bpf/verifier.c | 103 ++++++++++++++++++++++++++---------------- > > > 1 file changed, 63 insertions(+), 40 deletions(-) > > > > > [...] ^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH bpf-next 03/13] bpf: enhance BPF_JEQ/BPF_JNE is_branch_taken logic 2023-11-03 0:08 [PATCH bpf-next 00/13] BPF register bounds range vs range support Andrii Nakryiko 2023-11-03 0:08 ` [PATCH bpf-next 01/13] bpf: generalize reg_set_min_max() to handle non-const register comparisons Andrii Nakryiko 2023-11-03 0:08 ` [PATCH bpf-next 02/13] bpf: generalize is_scalar_branch_taken() logic Andrii Nakryiko @ 2023-11-03 0:08 ` Andrii Nakryiko 2023-11-03 17:28 ` Eduard Zingerman 2023-11-09 8:39 ` Shung-Hsi Yu 2023-11-03 0:08 ` [PATCH bpf-next 04/13] bpf: add register bounds sanity checks and sanitization Andrii Nakryiko ` (9 subsequent siblings) 12 siblings, 2 replies; 39+ messages in thread From: Andrii Nakryiko @ 2023-11-03 0:08 UTC (permalink / raw) To: bpf, ast, daniel, martin.lau; +Cc: andrii, kernel-team Use 32-bit subranges to prune some 64-bit BPF_JEQ/BPF_JNE conditions that otherwise would be "inconclusive" (i.e., is_branch_taken() would return -1). This can happen, for example, when registers are initialized as 64-bit u64/s64, then compared for inequality as 32-bit subregisters, and then followed by 64-bit equality/inequality check. That 32-bit inequality can establish some pattern for lower 32 bits of a register (e.g., s< 0 condition determines whether the bit #31 is zero or not), while overall 64-bit value could be anything (according to a value range representation). This is not a fancy quirky special case, but actually a handling that's necessary to prevent correctness issue with BPF verifier's range tracking: set_range_min_max() assumes that register ranges are non-overlapping, and if that condition is not guaranteed by is_branch_taken() we can end up with invalid ranges, where min > max. [0] https://lore.kernel.org/bpf/CACkBjsY2q1_fUohD7hRmKGqv1MV=eP2f6XK8kjkYNw7BaiF8iQ@mail.gmail.com/ Signed-off-by: Andrii Nakryiko <andrii@kernel.org> --- kernel/bpf/verifier.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 2627461164ed..8691cacd3ad3 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -14214,6 +14214,18 @@ static int is_scalar_branch_taken(struct bpf_reg_state *reg1, struct bpf_reg_sta return 0; if (smin1 > smax2 || smax1 < smin2) return 0; + if (!is_jmp32) { + /* if 64-bit ranges are inconclusive, see if we can + * utilize 32-bit subrange knowledge to eliminate + * branches that can't be taken a priori + */ + if (reg1->u32_min_value > reg2->u32_max_value || + reg1->u32_max_value < reg2->u32_min_value) + return 0; + if (reg1->s32_min_value > reg2->s32_max_value || + reg1->s32_max_value < reg2->s32_min_value) + return 0; + } break; case BPF_JNE: /* constants, umin/umax and smin/smax checks would be @@ -14226,6 +14238,18 @@ static int is_scalar_branch_taken(struct bpf_reg_state *reg1, struct bpf_reg_sta return 1; if (smin1 > smax2 || smax1 < smin2) return 1; + if (!is_jmp32) { + /* if 64-bit ranges are inconclusive, see if we can + * utilize 32-bit subrange knowledge to eliminate + * branches that can't be taken a priori + */ + if (reg1->u32_min_value > reg2->u32_max_value || + reg1->u32_max_value < reg2->u32_min_value) + return 1; + if (reg1->s32_min_value > reg2->s32_max_value || + reg1->s32_max_value < reg2->s32_min_value) + return 1; + } break; case BPF_JSET: if (!is_reg_const(reg2, is_jmp32)) { -- 2.34.1 ^ permalink raw reply related [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 03/13] bpf: enhance BPF_JEQ/BPF_JNE is_branch_taken logic 2023-11-03 0:08 ` [PATCH bpf-next 03/13] bpf: enhance BPF_JEQ/BPF_JNE is_branch_taken logic Andrii Nakryiko @ 2023-11-03 17:28 ` Eduard Zingerman 2023-11-09 8:39 ` Shung-Hsi Yu 1 sibling, 0 replies; 39+ messages in thread From: Eduard Zingerman @ 2023-11-03 17:28 UTC (permalink / raw) To: Andrii Nakryiko, bpf, ast, daniel, martin.lau; +Cc: kernel-team On Thu, 2023-11-02 at 17:08 -0700, Andrii Nakryiko wrote: > Use 32-bit subranges to prune some 64-bit BPF_JEQ/BPF_JNE conditions > that otherwise would be "inconclusive" (i.e., is_branch_taken() would > return -1). This can happen, for example, when registers are initialized > as 64-bit u64/s64, then compared for inequality as 32-bit subregisters, > and then followed by 64-bit equality/inequality check. That 32-bit > inequality can establish some pattern for lower 32 bits of a register > (e.g., s< 0 condition determines whether the bit #31 is zero or not), > while overall 64-bit value could be anything (according to a value range > representation). > > This is not a fancy quirky special case, but actually a handling that's > necessary to prevent correctness issue with BPF verifier's range > tracking: set_range_min_max() assumes that register ranges are > non-overlapping, and if that condition is not guaranteed by > is_branch_taken() we can end up with invalid ranges, where min > max. Acked-by: Eduard Zingerman <eddyz87@gmail.com> > > [0] https://lore.kernel.org/bpf/CACkBjsY2q1_fUohD7hRmKGqv1MV=eP2f6XK8kjkYNw7BaiF8iQ@mail.gmail.com/ > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org> > --- > kernel/bpf/verifier.c | 24 ++++++++++++++++++++++++ > 1 file changed, 24 insertions(+) > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 2627461164ed..8691cacd3ad3 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -14214,6 +14214,18 @@ static int is_scalar_branch_taken(struct bpf_reg_state *reg1, struct bpf_reg_sta > return 0; > if (smin1 > smax2 || smax1 < smin2) > return 0; > + if (!is_jmp32) { > + /* if 64-bit ranges are inconclusive, see if we can > + * utilize 32-bit subrange knowledge to eliminate > + * branches that can't be taken a priori > + */ > + if (reg1->u32_min_value > reg2->u32_max_value || > + reg1->u32_max_value < reg2->u32_min_value) > + return 0; > + if (reg1->s32_min_value > reg2->s32_max_value || > + reg1->s32_max_value < reg2->s32_min_value) > + return 0; > + } > break; > case BPF_JNE: > /* constants, umin/umax and smin/smax checks would be > @@ -14226,6 +14238,18 @@ static int is_scalar_branch_taken(struct bpf_reg_state *reg1, struct bpf_reg_sta > return 1; > if (smin1 > smax2 || smax1 < smin2) > return 1; > + if (!is_jmp32) { > + /* if 64-bit ranges are inconclusive, see if we can > + * utilize 32-bit subrange knowledge to eliminate > + * branches that can't be taken a priori > + */ > + if (reg1->u32_min_value > reg2->u32_max_value || > + reg1->u32_max_value < reg2->u32_min_value) > + return 1; > + if (reg1->s32_min_value > reg2->s32_max_value || > + reg1->s32_max_value < reg2->s32_min_value) > + return 1; > + } > break; > case BPF_JSET: > if (!is_reg_const(reg2, is_jmp32)) { ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 03/13] bpf: enhance BPF_JEQ/BPF_JNE is_branch_taken logic 2023-11-03 0:08 ` [PATCH bpf-next 03/13] bpf: enhance BPF_JEQ/BPF_JNE is_branch_taken logic Andrii Nakryiko 2023-11-03 17:28 ` Eduard Zingerman @ 2023-11-09 8:39 ` Shung-Hsi Yu 1 sibling, 0 replies; 39+ messages in thread From: Shung-Hsi Yu @ 2023-11-09 8:39 UTC (permalink / raw) To: Andrii Nakryiko; +Cc: bpf, ast, daniel, martin.lau, kernel-team On Thu, Nov 02, 2023 at 05:08:12PM -0700, Andrii Nakryiko wrote: > Use 32-bit subranges to prune some 64-bit BPF_JEQ/BPF_JNE conditions > that otherwise would be "inconclusive" (i.e., is_branch_taken() would > return -1). This can happen, for example, when registers are initialized > as 64-bit u64/s64, then compared for inequality as 32-bit subregisters, > and then followed by 64-bit equality/inequality check. That 32-bit > inequality can establish some pattern for lower 32 bits of a register > (e.g., s< 0 condition determines whether the bit #31 is zero or not), > while overall 64-bit value could be anything (according to a value range > representation). > > This is not a fancy quirky special case, but actually a handling that's > necessary to prevent correctness issue with BPF verifier's range > tracking: set_range_min_max() assumes that register ranges are > non-overlapping, and if that condition is not guaranteed by > is_branch_taken() we can end up with invalid ranges, where min > max. > > [0] https://lore.kernel.org/bpf/CACkBjsY2q1_fUohD7hRmKGqv1MV=eP2f6XK8kjkYNw7BaiF8iQ@mail.gmail.com/ > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org> Acked-by: Shung-Hsi Yu <shung-hsi.yu@suse.com> ^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH bpf-next 04/13] bpf: add register bounds sanity checks and sanitization 2023-11-03 0:08 [PATCH bpf-next 00/13] BPF register bounds range vs range support Andrii Nakryiko ` (2 preceding siblings ...) 2023-11-03 0:08 ` [PATCH bpf-next 03/13] bpf: enhance BPF_JEQ/BPF_JNE is_branch_taken logic Andrii Nakryiko @ 2023-11-03 0:08 ` Andrii Nakryiko 2023-11-03 2:13 ` Andrii Nakryiko ` (2 more replies) 2023-11-03 0:08 ` [PATCH bpf-next 05/13] bpf: remove redundant s{32,64} -> u{32,64} deduction logic Andrii Nakryiko ` (8 subsequent siblings) 12 siblings, 3 replies; 39+ messages in thread From: Andrii Nakryiko @ 2023-11-03 0:08 UTC (permalink / raw) To: bpf, ast, daniel, martin.lau; +Cc: andrii, kernel-team Add simple sanity checks that validate well-formed ranges (min <= max) across u64, s64, u32, and s32 ranges. Also for cases when the value is constant (either 64-bit or 32-bit), we validate that ranges and tnums are in agreement. These bounds checks are performed at the end of BPF_ALU/BPF_ALU64 operations, on conditional jumps, and for LDX instructions (where subreg zero/sign extension is probably the most important to check). This covers most of the interesting cases. Also, we validate the sanity of the return register when manually adjusting it for some special helpers. By default, sanity violation will trigger a warning in verifier log and resetting register bounds to "unbounded" ones. But to aid development and debugging, BPF_F_TEST_SANITY_STRICT flag is added, which will trigger hard failure of verification with -EFAULT on register bounds violations. This allows selftests to catch such issues. veristat will also gain a CLI option to enable this behavior. Signed-off-by: Andrii Nakryiko <andrii@kernel.org> --- include/linux/bpf_verifier.h | 1 + include/uapi/linux/bpf.h | 3 + kernel/bpf/syscall.c | 3 +- kernel/bpf/verifier.c | 117 ++++++++++++++++++++++++++------- tools/include/uapi/linux/bpf.h | 3 + 5 files changed, 101 insertions(+), 26 deletions(-) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index 24213a99cc79..402b6bc44a1b 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -602,6 +602,7 @@ struct bpf_verifier_env { int stack_size; /* number of states to be processed */ bool strict_alignment; /* perform strict pointer alignment checks */ bool test_state_freq; /* test verifier with different pruning frequency */ + bool test_sanity_strict; /* fail verification on sanity violations */ struct bpf_verifier_state *cur_state; /* current verifier state */ struct bpf_verifier_state_list **explored_states; /* search pruning optimization */ struct bpf_verifier_state_list *free_list; diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 0f6cdf52b1da..b99c1e0e2730 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -1200,6 +1200,9 @@ enum bpf_perf_event_type { */ #define BPF_F_XDP_DEV_BOUND_ONLY (1U << 6) +/* The verifier internal test flag. Behavior is undefined */ +#define BPF_F_TEST_SANITY_STRICT (1U << 7) + /* link_create.kprobe_multi.flags used in LINK_CREATE command for * BPF_TRACE_KPROBE_MULTI attach type to create return probe. */ diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 0ed286b8a0f0..f266e03ba342 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -2573,7 +2573,8 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size) BPF_F_SLEEPABLE | BPF_F_TEST_RND_HI32 | BPF_F_XDP_HAS_FRAGS | - BPF_F_XDP_DEV_BOUND_ONLY)) + BPF_F_XDP_DEV_BOUND_ONLY | + BPF_F_TEST_SANITY_STRICT)) return -EINVAL; if (!IS_ENABLED(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS) && diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 8691cacd3ad3..af4e2fecbef2 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -2615,6 +2615,56 @@ static void reg_bounds_sync(struct bpf_reg_state *reg) __update_reg_bounds(reg); } +static int reg_bounds_sanity_check(struct bpf_verifier_env *env, + struct bpf_reg_state *reg, const char *ctx) +{ + const char *msg; + + if (reg->umin_value > reg->umax_value || + reg->smin_value > reg->smax_value || + reg->u32_min_value > reg->u32_max_value || + reg->s32_min_value > reg->s32_max_value) { + msg = "range bounds violation"; + goto out; + } + + if (tnum_is_const(reg->var_off)) { + u64 uval = reg->var_off.value; + s64 sval = (s64)uval; + + if (reg->umin_value != uval || reg->umax_value != uval || + reg->smin_value != sval || reg->smax_value != sval) { + msg = "const tnum out of sync with range bounds"; + goto out; + } + } + + if (tnum_subreg_is_const(reg->var_off)) { + u32 uval32 = tnum_subreg(reg->var_off).value; + s32 sval32 = (s32)uval32; + + if (reg->u32_min_value != uval32 || reg->u32_max_value != uval32 || + reg->s32_min_value != sval32 || reg->s32_max_value != sval32) { + msg = "const subreg tnum out of sync with range bounds"; + goto out; + } + } + + return 0; +out: + verbose(env, "REG SANITY VIOLATION (%s): %s u64=[%#llx, %#llx] " + "s64=[%#llx, %#llx] u32=[%#x, %#x] s32=[%#x, %#x] var_off=(%#llx, %#llx)\n", + ctx, msg, reg->umin_value, reg->umax_value, + reg->smin_value, reg->smax_value, + reg->u32_min_value, reg->u32_max_value, + reg->s32_min_value, reg->s32_max_value, + reg->var_off.value, reg->var_off.mask); + if (env->test_sanity_strict) + return -EFAULT; + __mark_reg_unbounded(reg); + return 0; +} + static bool __reg32_bound_s64(s32 a) { return a >= 0 && a <= S32_MAX; @@ -9928,14 +9978,15 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx) return 0; } -static void do_refine_retval_range(struct bpf_reg_state *regs, int ret_type, - int func_id, - struct bpf_call_arg_meta *meta) +static int do_refine_retval_range(struct bpf_verifier_env *env, + struct bpf_reg_state *regs, int ret_type, + int func_id, + struct bpf_call_arg_meta *meta) { struct bpf_reg_state *ret_reg = ®s[BPF_REG_0]; if (ret_type != RET_INTEGER) - return; + return 0; switch (func_id) { case BPF_FUNC_get_stack: @@ -9961,6 +10012,8 @@ static void do_refine_retval_range(struct bpf_reg_state *regs, int ret_type, reg_bounds_sync(ret_reg); break; } + + return reg_bounds_sanity_check(env, ret_reg, "retval"); } static int @@ -10612,7 +10665,9 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn regs[BPF_REG_0].ref_obj_id = id; } - do_refine_retval_range(regs, fn->ret_type, func_id, &meta); + err = do_refine_retval_range(env, regs, fn->ret_type, func_id, &meta); + if (err) + return err; err = check_map_func_compatibility(env, meta.map_ptr, func_id); if (err) @@ -14079,13 +14134,12 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn) /* check dest operand */ err = check_reg_arg(env, insn->dst_reg, DST_OP_NO_MARK); + err = err ?: adjust_reg_min_max_vals(env, insn); if (err) return err; - - return adjust_reg_min_max_vals(env, insn); } - return 0; + return reg_bounds_sanity_check(env, ®s[insn->dst_reg], "alu"); } static void find_good_pkt_pointers(struct bpf_verifier_state *vstate, @@ -14609,18 +14663,21 @@ static void regs_refine_cond_op(struct bpf_reg_state *reg1, struct bpf_reg_state * Technically we can do similar adjustments for pointers to the same object, * but we don't support that right now. */ -static void reg_set_min_max(struct bpf_reg_state *true_reg1, - struct bpf_reg_state *true_reg2, - struct bpf_reg_state *false_reg1, - struct bpf_reg_state *false_reg2, - u8 opcode, bool is_jmp32) +static int reg_set_min_max(struct bpf_verifier_env *env, + struct bpf_reg_state *true_reg1, + struct bpf_reg_state *true_reg2, + struct bpf_reg_state *false_reg1, + struct bpf_reg_state *false_reg2, + u8 opcode, bool is_jmp32) { + int err; + /* If either register is a pointer, we can't learn anything about its * variable offset from the compare (unless they were a pointer into * the same object, but we don't bother with that). */ if (false_reg1->type != SCALAR_VALUE || false_reg2->type != SCALAR_VALUE) - return; + return 0; /* fallthrough (FALSE) branch */ regs_refine_cond_op(false_reg1, false_reg2, rev_opcode(opcode), is_jmp32); @@ -14631,6 +14688,12 @@ static void reg_set_min_max(struct bpf_reg_state *true_reg1, regs_refine_cond_op(true_reg1, true_reg2, opcode, is_jmp32); reg_bounds_sync(true_reg1); reg_bounds_sync(true_reg2); + + err = reg_bounds_sanity_check(env, true_reg1, "true_reg1"); + err = err ?: reg_bounds_sanity_check(env, true_reg2, "true_reg2"); + err = err ?: reg_bounds_sanity_check(env, false_reg1, "false_reg1"); + err = err ?: reg_bounds_sanity_check(env, false_reg2, "false_reg2"); + return err; } static void mark_ptr_or_null_reg(struct bpf_func_state *state, @@ -14924,15 +14987,20 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env, other_branch_regs = other_branch->frame[other_branch->curframe]->regs; if (BPF_SRC(insn->code) == BPF_X) { - reg_set_min_max(&other_branch_regs[insn->dst_reg], - &other_branch_regs[insn->src_reg], - dst_reg, src_reg, opcode, is_jmp32); + err = reg_set_min_max(env, + &other_branch_regs[insn->dst_reg], + &other_branch_regs[insn->src_reg], + dst_reg, src_reg, opcode, is_jmp32); } else /* BPF_SRC(insn->code) == BPF_K */ { - reg_set_min_max(&other_branch_regs[insn->dst_reg], - src_reg /* fake one */, - dst_reg, src_reg /* same fake one */, - opcode, is_jmp32); + err = reg_set_min_max(env, + &other_branch_regs[insn->dst_reg], + src_reg /* fake one */, + dst_reg, src_reg /* same fake one */, + opcode, is_jmp32); } + if (err) + return err; + if (BPF_SRC(insn->code) == BPF_X && src_reg->type == SCALAR_VALUE && src_reg->id && !WARN_ON_ONCE(src_reg->id != other_branch_regs[insn->src_reg].id)) { @@ -17435,10 +17503,8 @@ static int do_check(struct bpf_verifier_env *env) insn->off, BPF_SIZE(insn->code), BPF_READ, insn->dst_reg, false, BPF_MODE(insn->code) == BPF_MEMSX); - if (err) - return err; - - err = save_aux_ptr_type(env, src_reg_type, true); + err = err ?: save_aux_ptr_type(env, src_reg_type, true); + err = err ?: reg_bounds_sanity_check(env, ®s[insn->dst_reg], "ldx"); if (err) return err; } else if (class == BPF_STX) { @@ -20725,6 +20791,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3 if (is_priv) env->test_state_freq = attr->prog_flags & BPF_F_TEST_STATE_FREQ; + env->test_sanity_strict = attr->prog_flags & BPF_F_TEST_SANITY_STRICT; env->explored_states = kvcalloc(state_htab_size(env), sizeof(struct bpf_verifier_state_list *), diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 0f6cdf52b1da..b99c1e0e2730 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -1200,6 +1200,9 @@ enum bpf_perf_event_type { */ #define BPF_F_XDP_DEV_BOUND_ONLY (1U << 6) +/* The verifier internal test flag. Behavior is undefined */ +#define BPF_F_TEST_SANITY_STRICT (1U << 7) + /* link_create.kprobe_multi.flags used in LINK_CREATE command for * BPF_TRACE_KPROBE_MULTI attach type to create return probe. */ -- 2.34.1 ^ permalink raw reply related [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 04/13] bpf: add register bounds sanity checks and sanitization 2023-11-03 0:08 ` [PATCH bpf-next 04/13] bpf: add register bounds sanity checks and sanitization Andrii Nakryiko @ 2023-11-03 2:13 ` Andrii Nakryiko 2023-11-03 17:56 ` Eduard Zingerman 2023-11-09 8:30 ` Shung-Hsi Yu 2 siblings, 0 replies; 39+ messages in thread From: Andrii Nakryiko @ 2023-11-03 2:13 UTC (permalink / raw) To: Andrii Nakryiko; +Cc: bpf, ast, daniel, martin.lau, kernel-team On Thu, Nov 2, 2023 at 5:08 PM Andrii Nakryiko <andrii@kernel.org> wrote: > > Add simple sanity checks that validate well-formed ranges (min <= max) > across u64, s64, u32, and s32 ranges. Also for cases when the value is > constant (either 64-bit or 32-bit), we validate that ranges and tnums > are in agreement. > > These bounds checks are performed at the end of BPF_ALU/BPF_ALU64 > operations, on conditional jumps, and for LDX instructions (where subreg > zero/sign extension is probably the most important to check). This > covers most of the interesting cases. > > Also, we validate the sanity of the return register when manually > adjusting it for some special helpers. > > By default, sanity violation will trigger a warning in verifier log and > resetting register bounds to "unbounded" ones. But to aid development > and debugging, BPF_F_TEST_SANITY_STRICT flag is added, which will > trigger hard failure of verification with -EFAULT on register bounds > violations. This allows selftests to catch such issues. veristat will > also gain a CLI option to enable this behavior. > BTW, besides two verifier_bounds "artificial" selftests, we seem to have one more violation in more real-world-like test: bpf_cubic.bpf.c's bpf_cubic_cong_avoid: ; if (!(x & (~0ull << (BITS_PER_U64-1)))) 166: (65) if r1 s> 0xffffffff goto pc+1 REG SANITY VIOLATION (true_reg1): range bounds violation u64=[0x4000000000000000, 0x1fd809fd00000000] s64=[0x0, 0x1fd809fd00000000] u32=[0x0, 0x0] s32=[0x0, 0x0] var_off=(0x0, 0x1fd809fd00000000) We get to the point where R1 state is like this (before violation checks detection was added): R1=scalar(id=59,smin=umin=4611686018427387904,smax=umax=2294594992376643584,smin32=0,smax32=umax32=0,var_off=(0x0; 0x1fd809fd00000000)) umin >= umax, smin >= smax. All this before any of the verifier changes we landed in the previous patch set. I.e., none of my changes caused this breakage, it's pre-existing problem. > Signed-off-by: Andrii Nakryiko <andrii@kernel.org> > --- > include/linux/bpf_verifier.h | 1 + > include/uapi/linux/bpf.h | 3 + > kernel/bpf/syscall.c | 3 +- > kernel/bpf/verifier.c | 117 ++++++++++++++++++++++++++------- > tools/include/uapi/linux/bpf.h | 3 + > 5 files changed, 101 insertions(+), 26 deletions(-) > > diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h > index 24213a99cc79..402b6bc44a1b 100644 > --- a/include/linux/bpf_verifier.h > +++ b/include/linux/bpf_verifier.h > @@ -602,6 +602,7 @@ struct bpf_verifier_env { > int stack_size; /* number of states to be processed */ > bool strict_alignment; /* perform strict pointer alignment checks */ > bool test_state_freq; /* test verifier with different pruning frequency */ > + bool test_sanity_strict; /* fail verification on sanity violations */ > struct bpf_verifier_state *cur_state; /* current verifier state */ > struct bpf_verifier_state_list **explored_states; /* search pruning optimization */ > struct bpf_verifier_state_list *free_list; > diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h > index 0f6cdf52b1da..b99c1e0e2730 100644 > --- a/include/uapi/linux/bpf.h > +++ b/include/uapi/linux/bpf.h > @@ -1200,6 +1200,9 @@ enum bpf_perf_event_type { > */ > #define BPF_F_XDP_DEV_BOUND_ONLY (1U << 6) > > +/* The verifier internal test flag. Behavior is undefined */ > +#define BPF_F_TEST_SANITY_STRICT (1U << 7) > + > /* link_create.kprobe_multi.flags used in LINK_CREATE command for > * BPF_TRACE_KPROBE_MULTI attach type to create return probe. > */ > diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c > index 0ed286b8a0f0..f266e03ba342 100644 > --- a/kernel/bpf/syscall.c > +++ b/kernel/bpf/syscall.c > @@ -2573,7 +2573,8 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size) > BPF_F_SLEEPABLE | > BPF_F_TEST_RND_HI32 | > BPF_F_XDP_HAS_FRAGS | > - BPF_F_XDP_DEV_BOUND_ONLY)) > + BPF_F_XDP_DEV_BOUND_ONLY | > + BPF_F_TEST_SANITY_STRICT)) > return -EINVAL; > > if (!IS_ENABLED(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS) && > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 8691cacd3ad3..af4e2fecbef2 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -2615,6 +2615,56 @@ static void reg_bounds_sync(struct bpf_reg_state *reg) > __update_reg_bounds(reg); > } > > +static int reg_bounds_sanity_check(struct bpf_verifier_env *env, > + struct bpf_reg_state *reg, const char *ctx) > +{ > + const char *msg; > + > + if (reg->umin_value > reg->umax_value || > + reg->smin_value > reg->smax_value || > + reg->u32_min_value > reg->u32_max_value || > + reg->s32_min_value > reg->s32_max_value) { > + msg = "range bounds violation"; > + goto out; > + } > + > + if (tnum_is_const(reg->var_off)) { > + u64 uval = reg->var_off.value; > + s64 sval = (s64)uval; > + > + if (reg->umin_value != uval || reg->umax_value != uval || > + reg->smin_value != sval || reg->smax_value != sval) { > + msg = "const tnum out of sync with range bounds"; > + goto out; > + } > + } > + > + if (tnum_subreg_is_const(reg->var_off)) { > + u32 uval32 = tnum_subreg(reg->var_off).value; > + s32 sval32 = (s32)uval32; > + > + if (reg->u32_min_value != uval32 || reg->u32_max_value != uval32 || > + reg->s32_min_value != sval32 || reg->s32_max_value != sval32) { > + msg = "const subreg tnum out of sync with range bounds"; > + goto out; > + } > + } > + > + return 0; > +out: > + verbose(env, "REG SANITY VIOLATION (%s): %s u64=[%#llx, %#llx] " > + "s64=[%#llx, %#llx] u32=[%#x, %#x] s32=[%#x, %#x] var_off=(%#llx, %#llx)\n", > + ctx, msg, reg->umin_value, reg->umax_value, > + reg->smin_value, reg->smax_value, > + reg->u32_min_value, reg->u32_max_value, > + reg->s32_min_value, reg->s32_max_value, > + reg->var_off.value, reg->var_off.mask); > + if (env->test_sanity_strict) > + return -EFAULT; > + __mark_reg_unbounded(reg); > + return 0; > +} > + > static bool __reg32_bound_s64(s32 a) > { > return a >= 0 && a <= S32_MAX; > @@ -9928,14 +9978,15 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx) > return 0; > } > > -static void do_refine_retval_range(struct bpf_reg_state *regs, int ret_type, > - int func_id, > - struct bpf_call_arg_meta *meta) > +static int do_refine_retval_range(struct bpf_verifier_env *env, > + struct bpf_reg_state *regs, int ret_type, > + int func_id, > + struct bpf_call_arg_meta *meta) > { > struct bpf_reg_state *ret_reg = ®s[BPF_REG_0]; > > if (ret_type != RET_INTEGER) > - return; > + return 0; > > switch (func_id) { > case BPF_FUNC_get_stack: > @@ -9961,6 +10012,8 @@ static void do_refine_retval_range(struct bpf_reg_state *regs, int ret_type, > reg_bounds_sync(ret_reg); > break; > } > + > + return reg_bounds_sanity_check(env, ret_reg, "retval"); > } > > static int > @@ -10612,7 +10665,9 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn > regs[BPF_REG_0].ref_obj_id = id; > } > > - do_refine_retval_range(regs, fn->ret_type, func_id, &meta); > + err = do_refine_retval_range(env, regs, fn->ret_type, func_id, &meta); > + if (err) > + return err; > > err = check_map_func_compatibility(env, meta.map_ptr, func_id); > if (err) > @@ -14079,13 +14134,12 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn) > > /* check dest operand */ > err = check_reg_arg(env, insn->dst_reg, DST_OP_NO_MARK); > + err = err ?: adjust_reg_min_max_vals(env, insn); > if (err) > return err; > - > - return adjust_reg_min_max_vals(env, insn); > } > > - return 0; > + return reg_bounds_sanity_check(env, ®s[insn->dst_reg], "alu"); > } > > static void find_good_pkt_pointers(struct bpf_verifier_state *vstate, > @@ -14609,18 +14663,21 @@ static void regs_refine_cond_op(struct bpf_reg_state *reg1, struct bpf_reg_state > * Technically we can do similar adjustments for pointers to the same object, > * but we don't support that right now. > */ > -static void reg_set_min_max(struct bpf_reg_state *true_reg1, > - struct bpf_reg_state *true_reg2, > - struct bpf_reg_state *false_reg1, > - struct bpf_reg_state *false_reg2, > - u8 opcode, bool is_jmp32) > +static int reg_set_min_max(struct bpf_verifier_env *env, > + struct bpf_reg_state *true_reg1, > + struct bpf_reg_state *true_reg2, > + struct bpf_reg_state *false_reg1, > + struct bpf_reg_state *false_reg2, > + u8 opcode, bool is_jmp32) > { > + int err; > + > /* If either register is a pointer, we can't learn anything about its > * variable offset from the compare (unless they were a pointer into > * the same object, but we don't bother with that). > */ > if (false_reg1->type != SCALAR_VALUE || false_reg2->type != SCALAR_VALUE) > - return; > + return 0; > > /* fallthrough (FALSE) branch */ > regs_refine_cond_op(false_reg1, false_reg2, rev_opcode(opcode), is_jmp32); > @@ -14631,6 +14688,12 @@ static void reg_set_min_max(struct bpf_reg_state *true_reg1, > regs_refine_cond_op(true_reg1, true_reg2, opcode, is_jmp32); > reg_bounds_sync(true_reg1); > reg_bounds_sync(true_reg2); > + > + err = reg_bounds_sanity_check(env, true_reg1, "true_reg1"); > + err = err ?: reg_bounds_sanity_check(env, true_reg2, "true_reg2"); > + err = err ?: reg_bounds_sanity_check(env, false_reg1, "false_reg1"); > + err = err ?: reg_bounds_sanity_check(env, false_reg2, "false_reg2"); > + return err; > } > > static void mark_ptr_or_null_reg(struct bpf_func_state *state, > @@ -14924,15 +14987,20 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env, > other_branch_regs = other_branch->frame[other_branch->curframe]->regs; > > if (BPF_SRC(insn->code) == BPF_X) { > - reg_set_min_max(&other_branch_regs[insn->dst_reg], > - &other_branch_regs[insn->src_reg], > - dst_reg, src_reg, opcode, is_jmp32); > + err = reg_set_min_max(env, > + &other_branch_regs[insn->dst_reg], > + &other_branch_regs[insn->src_reg], > + dst_reg, src_reg, opcode, is_jmp32); > } else /* BPF_SRC(insn->code) == BPF_K */ { > - reg_set_min_max(&other_branch_regs[insn->dst_reg], > - src_reg /* fake one */, > - dst_reg, src_reg /* same fake one */, > - opcode, is_jmp32); > + err = reg_set_min_max(env, > + &other_branch_regs[insn->dst_reg], > + src_reg /* fake one */, > + dst_reg, src_reg /* same fake one */, > + opcode, is_jmp32); > } > + if (err) > + return err; > + > if (BPF_SRC(insn->code) == BPF_X && > src_reg->type == SCALAR_VALUE && src_reg->id && > !WARN_ON_ONCE(src_reg->id != other_branch_regs[insn->src_reg].id)) { > @@ -17435,10 +17503,8 @@ static int do_check(struct bpf_verifier_env *env) > insn->off, BPF_SIZE(insn->code), > BPF_READ, insn->dst_reg, false, > BPF_MODE(insn->code) == BPF_MEMSX); > - if (err) > - return err; > - > - err = save_aux_ptr_type(env, src_reg_type, true); > + err = err ?: save_aux_ptr_type(env, src_reg_type, true); > + err = err ?: reg_bounds_sanity_check(env, ®s[insn->dst_reg], "ldx"); > if (err) > return err; > } else if (class == BPF_STX) { > @@ -20725,6 +20791,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3 > > if (is_priv) > env->test_state_freq = attr->prog_flags & BPF_F_TEST_STATE_FREQ; > + env->test_sanity_strict = attr->prog_flags & BPF_F_TEST_SANITY_STRICT; > > env->explored_states = kvcalloc(state_htab_size(env), > sizeof(struct bpf_verifier_state_list *), > diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h > index 0f6cdf52b1da..b99c1e0e2730 100644 > --- a/tools/include/uapi/linux/bpf.h > +++ b/tools/include/uapi/linux/bpf.h > @@ -1200,6 +1200,9 @@ enum bpf_perf_event_type { > */ > #define BPF_F_XDP_DEV_BOUND_ONLY (1U << 6) > > +/* The verifier internal test flag. Behavior is undefined */ > +#define BPF_F_TEST_SANITY_STRICT (1U << 7) > + > /* link_create.kprobe_multi.flags used in LINK_CREATE command for > * BPF_TRACE_KPROBE_MULTI attach type to create return probe. > */ > -- > 2.34.1 > ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 04/13] bpf: add register bounds sanity checks and sanitization 2023-11-03 0:08 ` [PATCH bpf-next 04/13] bpf: add register bounds sanity checks and sanitization Andrii Nakryiko 2023-11-03 2:13 ` Andrii Nakryiko @ 2023-11-03 17:56 ` Eduard Zingerman 2023-11-03 21:11 ` Andrii Nakryiko 2023-11-09 8:30 ` Shung-Hsi Yu 2 siblings, 1 reply; 39+ messages in thread From: Eduard Zingerman @ 2023-11-03 17:56 UTC (permalink / raw) To: Andrii Nakryiko, bpf, ast, daniel, martin.lau; +Cc: kernel-team On Thu, 2023-11-02 at 17:08 -0700, Andrii Nakryiko wrote: > Add simple sanity checks that validate well-formed ranges (min <= max) > across u64, s64, u32, and s32 ranges. Also for cases when the value is > constant (either 64-bit or 32-bit), we validate that ranges and tnums > are in agreement. > > These bounds checks are performed at the end of BPF_ALU/BPF_ALU64 > operations, on conditional jumps, and for LDX instructions (where subreg > zero/sign extension is probably the most important to check). This > covers most of the interesting cases. > > Also, we validate the sanity of the return register when manually > adjusting it for some special helpers. > > By default, sanity violation will trigger a warning in verifier log and > resetting register bounds to "unbounded" ones. But to aid development > and debugging, BPF_F_TEST_SANITY_STRICT flag is added, which will > trigger hard failure of verification with -EFAULT on register bounds > violations. This allows selftests to catch such issues. veristat will > also gain a CLI option to enable this behavior. This is a useful check but I'm not sure about placement. It might be useful to guard calls to coerce_subreg_to_size_sx() as well. Maybe insert it as a part of the main do_check() loop but filter by instruction class (and also force on stack_pop)? > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org> > --- > include/linux/bpf_verifier.h | 1 + > include/uapi/linux/bpf.h | 3 + > kernel/bpf/syscall.c | 3 +- > kernel/bpf/verifier.c | 117 ++++++++++++++++++++++++++------- > tools/include/uapi/linux/bpf.h | 3 + > 5 files changed, 101 insertions(+), 26 deletions(-) > > diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h > index 24213a99cc79..402b6bc44a1b 100644 > --- a/include/linux/bpf_verifier.h > +++ b/include/linux/bpf_verifier.h > @@ -602,6 +602,7 @@ struct bpf_verifier_env { > int stack_size; /* number of states to be processed */ > bool strict_alignment; /* perform strict pointer alignment checks */ > bool test_state_freq; /* test verifier with different pruning frequency */ > + bool test_sanity_strict; /* fail verification on sanity violations */ > struct bpf_verifier_state *cur_state; /* current verifier state */ > struct bpf_verifier_state_list **explored_states; /* search pruning optimization */ > struct bpf_verifier_state_list *free_list; > diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h > index 0f6cdf52b1da..b99c1e0e2730 100644 > --- a/include/uapi/linux/bpf.h > +++ b/include/uapi/linux/bpf.h > @@ -1200,6 +1200,9 @@ enum bpf_perf_event_type { > */ > #define BPF_F_XDP_DEV_BOUND_ONLY (1U << 6) > > +/* The verifier internal test flag. Behavior is undefined */ > +#define BPF_F_TEST_SANITY_STRICT (1U << 7) > + > /* link_create.kprobe_multi.flags used in LINK_CREATE command for > * BPF_TRACE_KPROBE_MULTI attach type to create return probe. > */ > diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c > index 0ed286b8a0f0..f266e03ba342 100644 > --- a/kernel/bpf/syscall.c > +++ b/kernel/bpf/syscall.c > @@ -2573,7 +2573,8 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size) > BPF_F_SLEEPABLE | > BPF_F_TEST_RND_HI32 | > BPF_F_XDP_HAS_FRAGS | > - BPF_F_XDP_DEV_BOUND_ONLY)) > + BPF_F_XDP_DEV_BOUND_ONLY | > + BPF_F_TEST_SANITY_STRICT)) > return -EINVAL; > > if (!IS_ENABLED(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS) && > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 8691cacd3ad3..af4e2fecbef2 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -2615,6 +2615,56 @@ static void reg_bounds_sync(struct bpf_reg_state *reg) > __update_reg_bounds(reg); > } > > +static int reg_bounds_sanity_check(struct bpf_verifier_env *env, > + struct bpf_reg_state *reg, const char *ctx) > +{ > + const char *msg; > + > + if (reg->umin_value > reg->umax_value || > + reg->smin_value > reg->smax_value || > + reg->u32_min_value > reg->u32_max_value || > + reg->s32_min_value > reg->s32_max_value) { > + msg = "range bounds violation"; > + goto out; > + } > + > + if (tnum_is_const(reg->var_off)) { > + u64 uval = reg->var_off.value; > + s64 sval = (s64)uval; > + > + if (reg->umin_value != uval || reg->umax_value != uval || > + reg->smin_value != sval || reg->smax_value != sval) { > + msg = "const tnum out of sync with range bounds"; > + goto out; > + } > + } > + > + if (tnum_subreg_is_const(reg->var_off)) { > + u32 uval32 = tnum_subreg(reg->var_off).value; > + s32 sval32 = (s32)uval32; > + > + if (reg->u32_min_value != uval32 || reg->u32_max_value != uval32 || > + reg->s32_min_value != sval32 || reg->s32_max_value != sval32) { > + msg = "const subreg tnum out of sync with range bounds"; > + goto out; > + } > + } > + > + return 0; > +out: > + verbose(env, "REG SANITY VIOLATION (%s): %s u64=[%#llx, %#llx] " > + "s64=[%#llx, %#llx] u32=[%#x, %#x] s32=[%#x, %#x] var_off=(%#llx, %#llx)\n", > + ctx, msg, reg->umin_value, reg->umax_value, > + reg->smin_value, reg->smax_value, > + reg->u32_min_value, reg->u32_max_value, > + reg->s32_min_value, reg->s32_max_value, > + reg->var_off.value, reg->var_off.mask); > + if (env->test_sanity_strict) > + return -EFAULT; > + __mark_reg_unbounded(reg); > + return 0; > +} > + > static bool __reg32_bound_s64(s32 a) > { > return a >= 0 && a <= S32_MAX; > @@ -9928,14 +9978,15 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx) > return 0; > } > > -static void do_refine_retval_range(struct bpf_reg_state *regs, int ret_type, > - int func_id, > - struct bpf_call_arg_meta *meta) > +static int do_refine_retval_range(struct bpf_verifier_env *env, > + struct bpf_reg_state *regs, int ret_type, > + int func_id, > + struct bpf_call_arg_meta *meta) > { > struct bpf_reg_state *ret_reg = ®s[BPF_REG_0]; > > if (ret_type != RET_INTEGER) > - return; > + return 0; > > switch (func_id) { > case BPF_FUNC_get_stack: > @@ -9961,6 +10012,8 @@ static void do_refine_retval_range(struct bpf_reg_state *regs, int ret_type, > reg_bounds_sync(ret_reg); > break; > } > + > + return reg_bounds_sanity_check(env, ret_reg, "retval"); > } > > static int > @@ -10612,7 +10665,9 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn > regs[BPF_REG_0].ref_obj_id = id; > } > > - do_refine_retval_range(regs, fn->ret_type, func_id, &meta); > + err = do_refine_retval_range(env, regs, fn->ret_type, func_id, &meta); > + if (err) > + return err; > > err = check_map_func_compatibility(env, meta.map_ptr, func_id); > if (err) > @@ -14079,13 +14134,12 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn) > > /* check dest operand */ > err = check_reg_arg(env, insn->dst_reg, DST_OP_NO_MARK); > + err = err ?: adjust_reg_min_max_vals(env, insn); > if (err) > return err; > - > - return adjust_reg_min_max_vals(env, insn); > } > > - return 0; > + return reg_bounds_sanity_check(env, ®s[insn->dst_reg], "alu"); > } > > static void find_good_pkt_pointers(struct bpf_verifier_state *vstate, > @@ -14609,18 +14663,21 @@ static void regs_refine_cond_op(struct bpf_reg_state *reg1, struct bpf_reg_state > * Technically we can do similar adjustments for pointers to the same object, > * but we don't support that right now. > */ > -static void reg_set_min_max(struct bpf_reg_state *true_reg1, > - struct bpf_reg_state *true_reg2, > - struct bpf_reg_state *false_reg1, > - struct bpf_reg_state *false_reg2, > - u8 opcode, bool is_jmp32) > +static int reg_set_min_max(struct bpf_verifier_env *env, > + struct bpf_reg_state *true_reg1, > + struct bpf_reg_state *true_reg2, > + struct bpf_reg_state *false_reg1, > + struct bpf_reg_state *false_reg2, > + u8 opcode, bool is_jmp32) > { > + int err; > + > /* If either register is a pointer, we can't learn anything about its > * variable offset from the compare (unless they were a pointer into > * the same object, but we don't bother with that). > */ > if (false_reg1->type != SCALAR_VALUE || false_reg2->type != SCALAR_VALUE) > - return; > + return 0; > > /* fallthrough (FALSE) branch */ > regs_refine_cond_op(false_reg1, false_reg2, rev_opcode(opcode), is_jmp32); > @@ -14631,6 +14688,12 @@ static void reg_set_min_max(struct bpf_reg_state *true_reg1, > regs_refine_cond_op(true_reg1, true_reg2, opcode, is_jmp32); > reg_bounds_sync(true_reg1); > reg_bounds_sync(true_reg2); > + > + err = reg_bounds_sanity_check(env, true_reg1, "true_reg1"); > + err = err ?: reg_bounds_sanity_check(env, true_reg2, "true_reg2"); > + err = err ?: reg_bounds_sanity_check(env, false_reg1, "false_reg1"); > + err = err ?: reg_bounds_sanity_check(env, false_reg2, "false_reg2"); > + return err; > } > > static void mark_ptr_or_null_reg(struct bpf_func_state *state, > @@ -14924,15 +14987,20 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env, > other_branch_regs = other_branch->frame[other_branch->curframe]->regs; > > if (BPF_SRC(insn->code) == BPF_X) { > - reg_set_min_max(&other_branch_regs[insn->dst_reg], > - &other_branch_regs[insn->src_reg], > - dst_reg, src_reg, opcode, is_jmp32); > + err = reg_set_min_max(env, > + &other_branch_regs[insn->dst_reg], > + &other_branch_regs[insn->src_reg], > + dst_reg, src_reg, opcode, is_jmp32); > } else /* BPF_SRC(insn->code) == BPF_K */ { > - reg_set_min_max(&other_branch_regs[insn->dst_reg], > - src_reg /* fake one */, > - dst_reg, src_reg /* same fake one */, > - opcode, is_jmp32); > + err = reg_set_min_max(env, > + &other_branch_regs[insn->dst_reg], > + src_reg /* fake one */, > + dst_reg, src_reg /* same fake one */, > + opcode, is_jmp32); > } > + if (err) > + return err; > + > if (BPF_SRC(insn->code) == BPF_X && > src_reg->type == SCALAR_VALUE && src_reg->id && > !WARN_ON_ONCE(src_reg->id != other_branch_regs[insn->src_reg].id)) { > @@ -17435,10 +17503,8 @@ static int do_check(struct bpf_verifier_env *env) > insn->off, BPF_SIZE(insn->code), > BPF_READ, insn->dst_reg, false, > BPF_MODE(insn->code) == BPF_MEMSX); > - if (err) > - return err; > - > - err = save_aux_ptr_type(env, src_reg_type, true); > + err = err ?: save_aux_ptr_type(env, src_reg_type, true); > + err = err ?: reg_bounds_sanity_check(env, ®s[insn->dst_reg], "ldx"); > if (err) > return err; > } else if (class == BPF_STX) { > @@ -20725,6 +20791,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3 > > if (is_priv) > env->test_state_freq = attr->prog_flags & BPF_F_TEST_STATE_FREQ; > + env->test_sanity_strict = attr->prog_flags & BPF_F_TEST_SANITY_STRICT; > > env->explored_states = kvcalloc(state_htab_size(env), > sizeof(struct bpf_verifier_state_list *), > diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h > index 0f6cdf52b1da..b99c1e0e2730 100644 > --- a/tools/include/uapi/linux/bpf.h > +++ b/tools/include/uapi/linux/bpf.h > @@ -1200,6 +1200,9 @@ enum bpf_perf_event_type { > */ > #define BPF_F_XDP_DEV_BOUND_ONLY (1U << 6) > > +/* The verifier internal test flag. Behavior is undefined */ > +#define BPF_F_TEST_SANITY_STRICT (1U << 7) > + > /* link_create.kprobe_multi.flags used in LINK_CREATE command for > * BPF_TRACE_KPROBE_MULTI attach type to create return probe. > */ ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 04/13] bpf: add register bounds sanity checks and sanitization 2023-11-03 17:56 ` Eduard Zingerman @ 2023-11-03 21:11 ` Andrii Nakryiko 2023-11-03 21:39 ` Eduard Zingerman 0 siblings, 1 reply; 39+ messages in thread From: Andrii Nakryiko @ 2023-11-03 21:11 UTC (permalink / raw) To: Eduard Zingerman Cc: Andrii Nakryiko, bpf, ast, daniel, martin.lau, kernel-team On Fri, Nov 3, 2023 at 10:56 AM Eduard Zingerman <eddyz87@gmail.com> wrote: > > On Thu, 2023-11-02 at 17:08 -0700, Andrii Nakryiko wrote: > > Add simple sanity checks that validate well-formed ranges (min <= max) > > across u64, s64, u32, and s32 ranges. Also for cases when the value is > > constant (either 64-bit or 32-bit), we validate that ranges and tnums > > are in agreement. > > > > These bounds checks are performed at the end of BPF_ALU/BPF_ALU64 > > operations, on conditional jumps, and for LDX instructions (where subreg > > zero/sign extension is probably the most important to check). This > > covers most of the interesting cases. > > > > Also, we validate the sanity of the return register when manually > > adjusting it for some special helpers. > > > > By default, sanity violation will trigger a warning in verifier log and > > resetting register bounds to "unbounded" ones. But to aid development > > and debugging, BPF_F_TEST_SANITY_STRICT flag is added, which will > > trigger hard failure of verification with -EFAULT on register bounds > > violations. This allows selftests to catch such issues. veristat will > > also gain a CLI option to enable this behavior. > > This is a useful check but I'm not sure about placement. > It might be useful to guard calls to coerce_subreg_to_size_sx() as well. Those are covered as part of the ALU/ALU64 check. My initial idea was to add it into reg_bounds_sync() and make reg_bounds_sync() return int (right now it's void). But discussing with Alexei we came to the conclusion that it would be a bit too much code churn for little gain. This coerce_subreg...() stuff, it's also void, so we'd need to propagate errors out of it as well. In the end I think I'm covering basically all relevant cases (ALU, LDX, RETVAL, COND_JUMP). > Maybe insert it as a part of the main do_check() loop but filter > by instruction class (and also force on stack_pop)? That would be a) a bit wasteful, and b) I'd need to re-interpret BPF_X vs BPF_K and all the other idiosyncrasies of instruction encoding. So it doesn't seem like a good idea. > > > > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org> > > --- > > include/linux/bpf_verifier.h | 1 + > > include/uapi/linux/bpf.h | 3 + > > kernel/bpf/syscall.c | 3 +- > > kernel/bpf/verifier.c | 117 ++++++++++++++++++++++++++------- > > tools/include/uapi/linux/bpf.h | 3 + > > 5 files changed, 101 insertions(+), 26 deletions(-) > > trimming is good [...] ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 04/13] bpf: add register bounds sanity checks and sanitization 2023-11-03 21:11 ` Andrii Nakryiko @ 2023-11-03 21:39 ` Eduard Zingerman 0 siblings, 0 replies; 39+ messages in thread From: Eduard Zingerman @ 2023-11-03 21:39 UTC (permalink / raw) To: Andrii Nakryiko Cc: Andrii Nakryiko, bpf, ast, daniel, martin.lau, kernel-team On Fri, 2023-11-03 at 14:11 -0700, Andrii Nakryiko wrote: [...] > > This is a useful check but I'm not sure about placement. > > It might be useful to guard calls to coerce_subreg_to_size_sx() as well. > > Those are covered as part of the ALU/ALU64 check. Oh, right, sorry. > My initial idea was to add it into reg_bounds_sync() and make > reg_bounds_sync() return int (right now it's void). But discussing > with Alexei we came to the conclusion that it would be a bit too much > code churn for little gain. This coerce_subreg...() stuff, it's also > void, so we'd need to propagate errors out of it as well. > > In the end I think I'm covering basically all relevant cases (ALU, > LDX, RETVAL, COND_JUMP). > > > Maybe insert it as a part of the main do_check() loop but filter > > by instruction class (and also force on stack_pop)? > > That would be a) a bit wasteful, and b) I'd need to re-interpret BPF_X > vs BPF_K and all the other idiosyncrasies of instruction encoding. So > it doesn't seem like a good idea. tbh I think that compartmentalizing this check worth a little bit of churn, but ok, not that important. Acked-by: Eduard Zingerman <eddyz87@gmail.com> ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 04/13] bpf: add register bounds sanity checks and sanitization 2023-11-03 0:08 ` [PATCH bpf-next 04/13] bpf: add register bounds sanity checks and sanitization Andrii Nakryiko 2023-11-03 2:13 ` Andrii Nakryiko 2023-11-03 17:56 ` Eduard Zingerman @ 2023-11-09 8:30 ` Shung-Hsi Yu 2 siblings, 0 replies; 39+ messages in thread From: Shung-Hsi Yu @ 2023-11-09 8:30 UTC (permalink / raw) To: Andrii Nakryiko; +Cc: bpf, ast, daniel, martin.lau, kernel-team On Thu, Nov 02, 2023 at 05:08:13PM -0700, Andrii Nakryiko wrote: > Add simple sanity checks that validate well-formed ranges (min <= max) > across u64, s64, u32, and s32 ranges. Also for cases when the value is > constant (either 64-bit or 32-bit), we validate that ranges and tnums > are in agreement. > > These bounds checks are performed at the end of BPF_ALU/BPF_ALU64 > operations, on conditional jumps, and for LDX instructions (where subreg > zero/sign extension is probably the most important to check). This > covers most of the interesting cases. > > Also, we validate the sanity of the return register when manually > adjusting it for some special helpers. > > By default, sanity violation will trigger a warning in verifier log and > resetting register bounds to "unbounded" ones. But to aid development > and debugging, BPF_F_TEST_SANITY_STRICT flag is added, which will > trigger hard failure of verification with -EFAULT on register bounds > violations. This allows selftests to catch such issues. veristat will > also gain a CLI option to enable this behavior. > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org> > --- > include/linux/bpf_verifier.h | 1 + > include/uapi/linux/bpf.h | 3 + > kernel/bpf/syscall.c | 3 +- > kernel/bpf/verifier.c | 117 ++++++++++++++++++++++++++------- > tools/include/uapi/linux/bpf.h | 3 + > 5 files changed, 101 insertions(+), 26 deletions(-) [...] > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 8691cacd3ad3..af4e2fecbef2 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -2615,6 +2615,56 @@ static void reg_bounds_sync(struct bpf_reg_state *reg) > __update_reg_bounds(reg); > } > > +static int reg_bounds_sanity_check(struct bpf_verifier_env *env, > + struct bpf_reg_state *reg, const char *ctx) > +{ > + const char *msg; > + > + if (reg->umin_value > reg->umax_value || > + reg->smin_value > reg->smax_value || > + reg->u32_min_value > reg->u32_max_value || > + reg->s32_min_value > reg->s32_max_value) { > + msg = "range bounds violation"; > + goto out; > + } Maybe Check tnum validity before comparing it with min/max? The mask bit and value bit at the same position can not both be set. if (reg->var_off.mask & reg->var_off.value) { msg = "tnum invalid"; goto out; } Unfortunately doing tnum_intersect() on two non-overlapping tnum still gives a valid tnum; so we can't readily detect such cases like how we do for ranges above. > + if (tnum_is_const(reg->var_off)) { > + u64 uval = reg->var_off.value; > + s64 sval = (s64)uval; > + > + if (reg->umin_value != uval || reg->umax_value != uval || > + reg->smin_value != sval || reg->smax_value != sval) { > + msg = "const tnum out of sync with range bounds"; > + goto out; > + } > + } > + > + if (tnum_subreg_is_const(reg->var_off)) { > + u32 uval32 = tnum_subreg(reg->var_off).value; > + s32 sval32 = (s32)uval32; > + > + if (reg->u32_min_value != uval32 || reg->u32_max_value != uval32 || > + reg->s32_min_value != sval32 || reg->s32_max_value != sval32) { > + msg = "const subreg tnum out of sync with range bounds"; > + goto out; > + } > + } > + > + return 0; > +out: > + verbose(env, "REG SANITY VIOLATION (%s): %s u64=[%#llx, %#llx] " > + "s64=[%#llx, %#llx] u32=[%#x, %#x] s32=[%#x, %#x] var_off=(%#llx, %#llx)\n", > + ctx, msg, reg->umin_value, reg->umax_value, > + reg->smin_value, reg->smax_value, > + reg->u32_min_value, reg->u32_max_value, > + reg->s32_min_value, reg->s32_max_value, > + reg->var_off.value, reg->var_off.mask); > + if (env->test_sanity_strict) > + return -EFAULT; > + __mark_reg_unbounded(reg); > + return 0; > +} [...] ^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH bpf-next 05/13] bpf: remove redundant s{32,64} -> u{32,64} deduction logic 2023-11-03 0:08 [PATCH bpf-next 00/13] BPF register bounds range vs range support Andrii Nakryiko ` (3 preceding siblings ...) 2023-11-03 0:08 ` [PATCH bpf-next 04/13] bpf: add register bounds sanity checks and sanitization Andrii Nakryiko @ 2023-11-03 0:08 ` Andrii Nakryiko 2023-11-03 22:16 ` Eduard Zingerman 2023-11-09 8:43 ` Shung-Hsi Yu 2023-11-03 0:08 ` [PATCH bpf-next 06/13] bpf: make __reg{32,64}_deduce_bounds logic more robust Andrii Nakryiko ` (7 subsequent siblings) 12 siblings, 2 replies; 39+ messages in thread From: Andrii Nakryiko @ 2023-11-03 0:08 UTC (permalink / raw) To: bpf, ast, daniel, martin.lau; +Cc: andrii, kernel-team Equivalent checks were recently added in more succinct and, arguably, safer form in: - f188765f23a5 ("bpf: derive smin32/smax32 from umin32/umax32 bounds"); - 2e74aef782d3 ("bpf: derive smin/smax from umin/max bounds"). The checks we are removing in this patch set do similar checks to detect if entire u32/u64 range has signed bit set or not set, but does it with two separate checks. Further, we forcefully overwrite either smin or smax (and 32-bit equvalents) without applying normal min/max intersection logic. It's not clear why that would be correct in all cases and seems to work by accident. This logic is also "gated" by previous signed -> unsigned derivation, which returns early. All this is quite confusing and seems error-prone, while we already have at least equivalent checks happening earlier. So remove this duplicate and error-prone logic to simplify things a bit. Signed-off-by: Andrii Nakryiko <andrii@kernel.org> --- kernel/bpf/verifier.c | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index af4e2fecbef2..e7b2fe78a07f 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -2411,24 +2411,6 @@ static void __reg32_deduce_bounds(struct bpf_reg_state *reg) min_t(u32, reg->s32_max_value, reg->u32_max_value); return; } - /* Learn sign from unsigned bounds. Signed bounds cross the sign - * boundary, so we must be careful. - */ - if ((s32)reg->u32_max_value >= 0) { - /* Positive. We can't learn anything from the smin, but smax - * is positive, hence safe. - */ - reg->s32_min_value = reg->u32_min_value; - reg->s32_max_value = reg->u32_max_value = - min_t(u32, reg->s32_max_value, reg->u32_max_value); - } else if ((s32)reg->u32_min_value < 0) { - /* Negative. We can't learn anything from the smax, but smin - * is negative, hence safe. - */ - reg->s32_min_value = reg->u32_min_value = - max_t(u32, reg->s32_min_value, reg->u32_min_value); - reg->s32_max_value = reg->u32_max_value; - } } static void __reg64_deduce_bounds(struct bpf_reg_state *reg) @@ -2516,24 +2498,6 @@ static void __reg64_deduce_bounds(struct bpf_reg_state *reg) reg->umax_value); return; } - /* Learn sign from unsigned bounds. Signed bounds cross the sign - * boundary, so we must be careful. - */ - if ((s64)reg->umax_value >= 0) { - /* Positive. We can't learn anything from the smin, but smax - * is positive, hence safe. - */ - reg->smin_value = reg->umin_value; - reg->smax_value = reg->umax_value = min_t(u64, reg->smax_value, - reg->umax_value); - } else if ((s64)reg->umin_value < 0) { - /* Negative. We can't learn anything from the smax, but smin - * is negative, hence safe. - */ - reg->smin_value = reg->umin_value = max_t(u64, reg->smin_value, - reg->umin_value); - reg->smax_value = reg->umax_value; - } } static void __reg_deduce_mixed_bounds(struct bpf_reg_state *reg) -- 2.34.1 ^ permalink raw reply related [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 05/13] bpf: remove redundant s{32,64} -> u{32,64} deduction logic 2023-11-03 0:08 ` [PATCH bpf-next 05/13] bpf: remove redundant s{32,64} -> u{32,64} deduction logic Andrii Nakryiko @ 2023-11-03 22:16 ` Eduard Zingerman 2023-11-09 8:43 ` Shung-Hsi Yu 1 sibling, 0 replies; 39+ messages in thread From: Eduard Zingerman @ 2023-11-03 22:16 UTC (permalink / raw) To: Andrii Nakryiko, bpf, ast, daniel, martin.lau; +Cc: kernel-team On Thu, 2023-11-02 at 17:08 -0700, Andrii Nakryiko wrote: > Equivalent checks were recently added in more succinct and, arguably, > safer form in: > - f188765f23a5 ("bpf: derive smin32/smax32 from umin32/umax32 bounds"); > - 2e74aef782d3 ("bpf: derive smin/smax from umin/max bounds"). > > The checks we are removing in this patch set do similar checks to detect > if entire u32/u64 range has signed bit set or not set, but does it with > two separate checks. > > Further, we forcefully overwrite either smin or smax (and 32-bit equvalents) > without applying normal min/max intersection logic. It's not clear why > that would be correct in all cases and seems to work by accident. This > logic is also "gated" by previous signed -> unsigned derivation, which > returns early. > > All this is quite confusing and seems error-prone, while we already have > at least equivalent checks happening earlier. So remove this duplicate > and error-prone logic to simplify things a bit. > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org> Acked-by: Eduard Zingerman <eddyz87@gmail.com> ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 05/13] bpf: remove redundant s{32,64} -> u{32,64} deduction logic 2023-11-03 0:08 ` [PATCH bpf-next 05/13] bpf: remove redundant s{32,64} -> u{32,64} deduction logic Andrii Nakryiko 2023-11-03 22:16 ` Eduard Zingerman @ 2023-11-09 8:43 ` Shung-Hsi Yu 1 sibling, 0 replies; 39+ messages in thread From: Shung-Hsi Yu @ 2023-11-09 8:43 UTC (permalink / raw) To: Andrii Nakryiko; +Cc: bpf, ast, daniel, martin.lau, kernel-team On Thu, Nov 02, 2023 at 05:08:14PM -0700, Andrii Nakryiko wrote: > Equivalent checks were recently added in more succinct and, arguably, > safer form in: > - f188765f23a5 ("bpf: derive smin32/smax32 from umin32/umax32 bounds"); > - 2e74aef782d3 ("bpf: derive smin/smax from umin/max bounds"). > > The checks we are removing in this patch set do similar checks to detect > if entire u32/u64 range has signed bit set or not set, but does it with > two separate checks. > > Further, we forcefully overwrite either smin or smax (and 32-bit equvalents) > without applying normal min/max intersection logic. It's not clear why > that would be correct in all cases and seems to work by accident. This > logic is also "gated" by previous signed -> unsigned derivation, which > returns early. > > All this is quite confusing and seems error-prone, while we already have > at least equivalent checks happening earlier. So remove this duplicate > and error-prone logic to simplify things a bit. > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org> Acked-by: Shung-Hsi Yu <shung-hsi.yu@suse.com> ^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH bpf-next 06/13] bpf: make __reg{32,64}_deduce_bounds logic more robust 2023-11-03 0:08 [PATCH bpf-next 00/13] BPF register bounds range vs range support Andrii Nakryiko ` (4 preceding siblings ...) 2023-11-03 0:08 ` [PATCH bpf-next 05/13] bpf: remove redundant s{32,64} -> u{32,64} deduction logic Andrii Nakryiko @ 2023-11-03 0:08 ` Andrii Nakryiko 2023-11-03 22:27 ` Eduard Zingerman 2023-11-09 9:02 ` Shung-Hsi Yu 2023-11-03 0:08 ` [PATCH bpf-next 07/13] selftests/bpf: BPF register range bounds tester Andrii Nakryiko ` (6 subsequent siblings) 12 siblings, 2 replies; 39+ messages in thread From: Andrii Nakryiko @ 2023-11-03 0:08 UTC (permalink / raw) To: bpf, ast, daniel, martin.lau; +Cc: andrii, kernel-team This change doesn't seem to have any effect on selftests and production BPF object files, but we preemptively try to make it more robust. First, "learn sign from signed bounds" comment is misleading, as we are learning not just sign, but also values. Second, we simplify the check for determining whether entire range is positive or negative similarly to other checks added earlier, using appropriate u32/u64 cast and single comparisons. As explain in comments in __reg64_deduce_bounds(), the checks are equivalent. Last but not least, smin/smax and s32_min/s32_max reassignment based on min/max of both umin/umax and smin/smax (and 32-bit equivalents) is hard to explain and justify. We are updating unsigned bounds from signed bounds, why would we update signed bounds at the same time? This might be correct, but it's far from obvious why and the code or comments don't try to justify this. Given we've added a separate deduction of signed bounds from unsigned bounds earlier, this seems at least redundant, if not just wrong. In short, we remove doubtful pieces, and streamline the rest to follow the logic and approach of the rest of reg_bounds_sync() checks. Signed-off-by: Andrii Nakryiko <andrii@kernel.org> --- kernel/bpf/verifier.c | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index e7b2fe78a07f..91271961c9c2 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -2399,17 +2399,13 @@ static void __reg32_deduce_bounds(struct bpf_reg_state *reg) reg->s32_min_value = max_t(s32, reg->s32_min_value, reg->u32_min_value); reg->s32_max_value = min_t(s32, reg->s32_max_value, reg->u32_max_value); } - /* Learn sign from signed bounds. - * If we cannot cross the sign boundary, then signed and unsigned bounds + /* If we cannot cross the sign boundary, then signed and unsigned bounds * are the same, so combine. This works even in the negative case, e.g. * -3 s<= x s<= -1 implies 0xf...fd u<= x u<= 0xf...ff. */ - if (reg->s32_min_value >= 0 || reg->s32_max_value < 0) { - reg->s32_min_value = reg->u32_min_value = - max_t(u32, reg->s32_min_value, reg->u32_min_value); - reg->s32_max_value = reg->u32_max_value = - min_t(u32, reg->s32_max_value, reg->u32_max_value); - return; + if ((u32)reg->s32_min_value <= (u32)reg->s32_max_value) { + reg->u32_min_value = max_t(u32, reg->s32_min_value, reg->u32_min_value); + reg->u32_max_value = min_t(u32, reg->s32_max_value, reg->u32_max_value); } } @@ -2486,17 +2482,13 @@ static void __reg64_deduce_bounds(struct bpf_reg_state *reg) reg->smin_value = max_t(s64, reg->smin_value, reg->umin_value); reg->smax_value = min_t(s64, reg->smax_value, reg->umax_value); } - /* Learn sign from signed bounds. - * If we cannot cross the sign boundary, then signed and unsigned bounds + /* If we cannot cross the sign boundary, then signed and unsigned bounds * are the same, so combine. This works even in the negative case, e.g. * -3 s<= x s<= -1 implies 0xf...fd u<= x u<= 0xf...ff. */ - if (reg->smin_value >= 0 || reg->smax_value < 0) { - reg->smin_value = reg->umin_value = max_t(u64, reg->smin_value, - reg->umin_value); - reg->smax_value = reg->umax_value = min_t(u64, reg->smax_value, - reg->umax_value); - return; + if ((u64)reg->smin_value <= (u64)reg->smax_value) { + reg->umin_value = max_t(u64, reg->smin_value, reg->umin_value); + reg->umax_value = min_t(u64, reg->smax_value, reg->umax_value); } } -- 2.34.1 ^ permalink raw reply related [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 06/13] bpf: make __reg{32,64}_deduce_bounds logic more robust 2023-11-03 0:08 ` [PATCH bpf-next 06/13] bpf: make __reg{32,64}_deduce_bounds logic more robust Andrii Nakryiko @ 2023-11-03 22:27 ` Eduard Zingerman 2023-11-09 9:02 ` Shung-Hsi Yu 1 sibling, 0 replies; 39+ messages in thread From: Eduard Zingerman @ 2023-11-03 22:27 UTC (permalink / raw) To: Andrii Nakryiko, bpf, ast, daniel, martin.lau; +Cc: kernel-team On Thu, 2023-11-02 at 17:08 -0700, Andrii Nakryiko wrote: > This change doesn't seem to have any effect on selftests and production > BPF object files, but we preemptively try to make it more robust. > > First, "learn sign from signed bounds" comment is misleading, as we are > learning not just sign, but also values. > > Second, we simplify the check for determining whether entire range is > positive or negative similarly to other checks added earlier, using > appropriate u32/u64 cast and single comparisons. As explain in comments > in __reg64_deduce_bounds(), the checks are equivalent. > > Last but not least, smin/smax and s32_min/s32_max reassignment based on > min/max of both umin/umax and smin/smax (and 32-bit equivalents) is hard > to explain and justify. We are updating unsigned bounds from signed > bounds, why would we update signed bounds at the same time? This might > be correct, but it's far from obvious why and the code or comments don't > try to justify this. Given we've added a separate deduction of signed > bounds from unsigned bounds earlier, this seems at least redundant, if > not just wrong. > > In short, we remove doubtful pieces, and streamline the rest to follow > the logic and approach of the rest of reg_bounds_sync() checks. > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org> Acked-by: Eduard Zingerman <eddyz87@gmail.com> [...] ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 06/13] bpf: make __reg{32,64}_deduce_bounds logic more robust 2023-11-03 0:08 ` [PATCH bpf-next 06/13] bpf: make __reg{32,64}_deduce_bounds logic more robust Andrii Nakryiko 2023-11-03 22:27 ` Eduard Zingerman @ 2023-11-09 9:02 ` Shung-Hsi Yu 1 sibling, 0 replies; 39+ messages in thread From: Shung-Hsi Yu @ 2023-11-09 9:02 UTC (permalink / raw) To: Andrii Nakryiko; +Cc: bpf, ast, daniel, martin.lau, kernel-team On Thu, Nov 02, 2023 at 05:08:15PM -0700, Andrii Nakryiko wrote: > This change doesn't seem to have any effect on selftests and production > BPF object files, but we preemptively try to make it more robust. > > First, "learn sign from signed bounds" comment is misleading, as we are > learning not just sign, but also values. > > Second, we simplify the check for determining whether entire range is > positive or negative similarly to other checks added earlier, using > appropriate u32/u64 cast and single comparisons. As explain in comments > in __reg64_deduce_bounds(), the checks are equivalent. > > Last but not least, smin/smax and s32_min/s32_max reassignment based on > min/max of both umin/umax and smin/smax (and 32-bit equivalents) is hard > to explain and justify. We are updating unsigned bounds from signed > bounds, why would we update signed bounds at the same time? This might > be correct, but it's far from obvious why and the code or comments don't > try to justify this. Given we've added a separate deduction of signed > bounds from unsigned bounds earlier, this seems at least redundant, if > not just wrong. > > In short, we remove doubtful pieces, and streamline the rest to follow > the logic and approach of the rest of reg_bounds_sync() checks. > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org> > --- > kernel/bpf/verifier.c | 24 ++++++++---------------- > 1 file changed, 8 insertions(+), 16 deletions(-) > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index e7b2fe78a07f..91271961c9c2 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -2399,17 +2399,13 @@ static void __reg32_deduce_bounds(struct bpf_reg_state *reg) > reg->s32_min_value = max_t(s32, reg->s32_min_value, reg->u32_min_value); > reg->s32_max_value = min_t(s32, reg->s32_max_value, reg->u32_max_value); > } > - /* Learn sign from signed bounds. > - * If we cannot cross the sign boundary, then signed and unsigned bounds > + /* If we cannot cross the sign boundary, then signed and unsigned bounds > * are the same, so combine. This works even in the negative case, e.g. > * -3 s<= x s<= -1 implies 0xf...fd u<= x u<= 0xf...ff. > */ > - if (reg->s32_min_value >= 0 || reg->s32_max_value < 0) { > - reg->s32_min_value = reg->u32_min_value = > - max_t(u32, reg->s32_min_value, reg->u32_min_value); > - reg->s32_max_value = reg->u32_max_value = > - min_t(u32, reg->s32_max_value, reg->u32_max_value); > - return; I'd guess updating signed bounds here is sort of a shortcut to reach the tighest bound possible without going having to go through __reg32_deduce_bounds() twice, maybe. Agree that the changes below is more straight forward, same goes for __reg64_deduce_bounds(). Acked-by: Shung-Hsi Yu <shung-hsi.yu@suse.com> > + if ((u32)reg->s32_min_value <= (u32)reg->s32_max_value) { > + reg->u32_min_value = max_t(u32, reg->s32_min_value, reg->u32_min_value); > + reg->u32_max_value = min_t(u32, reg->s32_max_value, reg->u32_max_value); > } > } > > @@ -2486,17 +2482,13 @@ static void __reg64_deduce_bounds(struct bpf_reg_state *reg) > reg->smin_value = max_t(s64, reg->smin_value, reg->umin_value); > reg->smax_value = min_t(s64, reg->smax_value, reg->umax_value); > } > - /* Learn sign from signed bounds. > - * If we cannot cross the sign boundary, then signed and unsigned bounds > + /* If we cannot cross the sign boundary, then signed and unsigned bounds > * are the same, so combine. This works even in the negative case, e.g. > * -3 s<= x s<= -1 implies 0xf...fd u<= x u<= 0xf...ff. > */ > - if (reg->smin_value >= 0 || reg->smax_value < 0) { > - reg->smin_value = reg->umin_value = max_t(u64, reg->smin_value, > - reg->umin_value); > - reg->smax_value = reg->umax_value = min_t(u64, reg->smax_value, > - reg->umax_value); > - return; > + if ((u64)reg->smin_value <= (u64)reg->smax_value) { > + reg->umin_value = max_t(u64, reg->smin_value, reg->umin_value); > + reg->umax_value = min_t(u64, reg->smax_value, reg->umax_value); ^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH bpf-next 07/13] selftests/bpf: BPF register range bounds tester 2023-11-03 0:08 [PATCH bpf-next 00/13] BPF register bounds range vs range support Andrii Nakryiko ` (5 preceding siblings ...) 2023-11-03 0:08 ` [PATCH bpf-next 06/13] bpf: make __reg{32,64}_deduce_bounds logic more robust Andrii Nakryiko @ 2023-11-03 0:08 ` Andrii Nakryiko 2023-11-03 19:19 ` Alexei Starovoitov 2023-11-03 0:08 ` [PATCH bpf-next 08/13] selftests/bpf: adjust OP_EQ/OP_NE handling to use subranges for branch taken Andrii Nakryiko ` (5 subsequent siblings) 12 siblings, 1 reply; 39+ messages in thread From: Andrii Nakryiko @ 2023-11-03 0:08 UTC (permalink / raw) To: bpf, ast, daniel, martin.lau; +Cc: andrii, kernel-team Add test to validate BPF verifier's register range bounds tracking logic. The main bulk is a lot of auto-generated tests based on a small set of seed values for lower and upper 32 bits of full 64-bit values. Currently we validate only range vs const comparisons, but the idea is to start validating range over range comparisons in subsequent patch set. When setting up initial register ranges we treat registers as one of u64/s64/u32/s32 numeric types, and then independently perform conditional comparisons based on a potentially different u64/s64/u32/s32 types. This tests lots of tricky cases of deriving bounds information across different numeric domains. Given there are lots of auto-generated cases, we guard them behind SLOW_TESTS=1 envvar requirement, and skip them altogether otherwise. With current full set of upper/lower seed value, all supported comparison operators and all the combinations of u64/s64/u32/s32 number domains, we get about 7.7 million tests, which run in about 35 minutes on my local qemu instance without parallelization. But we also split those tests by init/cond numeric types, which allows to rely on test_progs's parallelization of tests with `-j` option, getting run time down to about 5 minutes on 8 cores. It's still something that shouldn't be run during normal test_progs run. But we can run it a reasonable time, and so perhaps a nightly CI test run (once we have it) would be a good option for this. We also add a small set of tricky conditions that came up during development and triggered various bugs or corner cases in either selftest's reimplementation of range bounds logic or in verifier's logic itself. These are fast enough to be run as part of normal test_progs test run and are great for a quick sanity checking. Let's take a look at test output to understand what's going on: $ sudo ./test_progs -t reg_bounds_crafted #191/1 reg_bounds_crafted/(u64)[0; 0xffffffff] (u64)< 0:OK ... #191/115 reg_bounds_crafted/(u64)[0; 0x17fffffff] (s32)< 0:OK ... #191/137 reg_bounds_crafted/(u64)[0xffffffff; 0x100000000] (u64)== 0:OK Each test case is uniquely and fully described by this generated string. E.g.: "(u64)[0; 0x17fffffff] (s32)< 0". This means that we initialize a register (R6) in such a way that verifier knows that it can have a value in [(u64)0; (u64)0x17fffffff] range. Another register (R7) is also set up as u64, but this time a constant (zero in this case). They then are compared using 32-bit signed < operation. Resulting TRUE/FALSE branches are evaluated (including cases where it's known that one of the branches will never be taken, in which case we validate that verifier also determines this as a dead code). Test validates that verifier's final register state matches expected state based on selftest's own reg_state logic, implemented from scratch for cross-checking purposes. These test names can be conveniently used for further debugging, and if -vv verboseness is requested we can get a corresponding verifier log (with mark_precise logs filtered out as irrelevant and distracting). Example below is slightly redacted for brevity, omitting irrelevant register output in some places, marked with [...]. $ sudo ./test_progs -a 'reg_bounds_crafted/(u32)[0; U32_MAX] (s32)< -1' -vv ... VERIFIER LOG: ======================== func#0 @0 0: R1=ctx(off=0,imm=0) R10=fp0 0: (05) goto pc+2 3: (85) call bpf_get_current_pid_tgid#14 ; R0_w=scalar() 4: (bc) w6 = w0 ; R0_w=scalar() R6_w=scalar(smin=0,smax=umax=4294967295,var_off=(0x0; 0xffffffff)) 5: (85) call bpf_get_current_pid_tgid#14 ; R0_w=scalar() 6: (bc) w7 = w0 ; R0_w=scalar() R7_w=scalar(smin=0,smax=umax=4294967295,var_off=(0x0; 0xffffffff)) 7: (b4) w1 = 0 ; R1_w=0 8: (b4) w2 = -1 ; R2=4294967295 9: (ae) if w6 < w1 goto pc-9 9: R1=0 R6=scalar(smin=0,smax=umax=4294967295,var_off=(0x0; 0xffffffff)) 10: (2e) if w6 > w2 goto pc-10 10: R2=4294967295 R6=scalar(smin=0,smax=umax=4294967295,var_off=(0x0; 0xffffffff)) 11: (b4) w1 = -1 ; R1_w=4294967295 12: (b4) w2 = -1 ; R2_w=4294967295 13: (ae) if w7 < w1 goto pc-13 ; R1_w=4294967295 R7=4294967295 14: (2e) if w7 > w2 goto pc-14 14: R2_w=4294967295 R7=4294967295 15: (bc) w0 = w6 ; [...] R6=scalar(id=1,smin=0,smax=umax=4294967295,var_off=(0x0; 0xffffffff)) 16: (bc) w0 = w7 ; [...] R7=4294967295 17: (ce) if w6 s< w7 goto pc+3 ; R6=scalar(id=1,smin=0,smax=umax=4294967295,smin32=-1,var_off=(0x0; 0xffffffff)) R7=4294967295 18: (bc) w0 = w6 ; [...] R6=scalar(id=1,smin=0,smax=umax=4294967295,smin32=-1,var_off=(0x0; 0xffffffff)) 19: (bc) w0 = w7 ; [...] R7=4294967295 20: (95) exit from 17 to 21: [...] 21: (bc) w0 = w6 ; [...] R6=scalar(id=1,smin=umin=umin32=2147483648,smax=umax=umax32=4294967294,smax32=-2,var_off=(0x80000000; 0x7fffffff)) 22: (bc) w0 = w7 ; [...] R7=4294967295 23: (95) exit from 13 to 1: [...] 1: [...] 1: (b7) r0 = 0 ; R0_w=0 2: (95) exit processed 24 insns (limit 1000000) max_states_per_insn 0 total_states 2 peak_states 2 mark_read 1 ===================== Verifier log above is for `(u32)[0; U32_MAX] (s32)< -1` use cases, where u32 range is used for initialization, followed by signed < operator. Note how we use w6/w7 in this case for register initialization (it would be R6/R7 for 64-bit types) and then `if w6 s< w7` for comparison at instruction #17. It will be `if R6 < R7` for 64-bit unsigned comparison. Above example gives a good impression of the overall structure of a BPF programs generated for reg_bounds tests. In the future, this "framework" can be extended to test not just conditional jumps, but also arithmetic operations. Adding randomized testing is another possibility. Some implementation notes. We basically have our own generics-like operations on numbers, where all the numbers are stored in u64, but how they are interpreted is passed as runtime argument enum num_t. Further, `struct range` represents a bounds range, and those are collected together into a minimal `struct reg_state`, which collects range bounds across all four numberical domains: u64, s64, u32, s64. Based on these primitives and `enum op` representing possible conditional operation (<, <=, >, >=, ==, !=), there is a set of generic helpers to perform "range arithmetics", which is used to maintain struct reg_state. We simulate what verifier will do for reg bounds of R6 and R7 registers using these range and reg_state primitives. Simulated information is used to determine branch taken conclusion and expected exact register state across all four number domains. Implementation of "range arithmetics" is more generic than what verifier is currently performing: it allows range over range comparisons and adjustments. This is the intended end goal of this patch set overall and verifier logic is enhanced in subsequent patches in this series to handle range vs range operations, at which point selftests are extended to validate these conditions as well. For now it's range vs const cases only. Note that tests are split into multiple groups by their numeric types for initialization of ranges and for comparison operation. This allows to use test_progs's -j parallelization to speed up tests, as we now have 16 groups of parallel running tests. Overall reduction of running time that allows is pretty good, we go down from more than 30 minutes to slightly less than 5 minutes running time. Signed-off-by: Andrii Nakryiko <andrii@kernel.org> --- .../selftests/bpf/prog_tests/reg_bounds.c | 1841 +++++++++++++++++ 1 file changed, 1841 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/reg_bounds.c diff --git a/tools/testing/selftests/bpf/prog_tests/reg_bounds.c b/tools/testing/selftests/bpf/prog_tests/reg_bounds.c new file mode 100644 index 000000000000..ac7354cfe139 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/reg_bounds.c @@ -0,0 +1,1841 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */ + +#define _GNU_SOURCE +#include <limits.h> +#include <test_progs.h> +#include <linux/filter.h> +#include <linux/bpf.h> + +/* ================================= + * SHORT AND CONSISTENT NUMBER TYPES + * ================================= + */ +#define U64_MAX ((u64)UINT64_MAX) +#define U32_MAX ((u32)UINT_MAX) +#define S64_MIN ((s64)INT64_MIN) +#define S64_MAX ((s64)INT64_MAX) +#define S32_MIN ((s32)INT_MIN) +#define S32_MAX ((s32)INT_MAX) + +typedef unsigned long long ___u64; +typedef unsigned int ___u32; +typedef long long ___s64; +typedef int ___s32; + +/* avoid conflicts with already defined types in kernel headers */ +#define u64 ___u64 +#define u32 ___u32 +#define s64 ___s64 +#define s32 ___s32 + +/* ================================== + * STRING BUF ABSTRACTION AND HELPERS + * ================================== + */ +struct strbuf { + size_t buf_sz; + int pos; + char buf[0]; +}; + +#define DEFINE_STRBUF(name, N) \ + struct { struct strbuf buf; char data[(N)]; } ___##name; \ + struct strbuf *name = (___##name.buf.buf_sz = (N), ___##name.buf.pos = 0, &___##name.buf) + +__printf(2, 3) +static inline void snappendf(struct strbuf *s, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + s->pos += vsnprintf(s->buf + s->pos, s->buf_sz - s->pos, fmt, args); + va_end(args); +} + +/* ================================== + * GENERIC NUMBER TYPE AND OPERATIONS + * ================================== + */ +enum num_t { U64, U32, S64, S32 }; +#define MIN_T U64 +#define MAX_T S32 + +static __always_inline u64 min_t(enum num_t t, u64 x, u64 y) +{ + switch (t) { + case U64: return (u64)x < (u64)y ? (u64)x : (u64)y; + case U32: return (u32)x < (u32)y ? (u32)x : (u32)y; + case S64: return (s64)x < (s64)y ? (s64)x : (s64)y; + case S32: return (s32)x < (s32)y ? (s32)x : (s32)y; + default: printf("min_t!\n"); exit(1); + } +} + +static __always_inline u64 max_t(enum num_t t, u64 x, u64 y) +{ + switch (t) { + case U64: return (u64)x > (u64)y ? (u64)x : (u64)y; + case U32: return (u32)x > (u32)y ? (u32)x : (u32)y; + case S64: return (s64)x > (s64)y ? (s64)x : (s64)y; + case S32: return (s32)x > (s32)y ? (u32)(s32)x : (u32)(s32)y; + default: printf("max_t!\n"); exit(1); + } +} + +static const char *t_str(enum num_t t) +{ + switch (t) { + case U64: return "u64"; + case U32: return "u32"; + case S64: return "s64"; + case S32: return "s32"; + default: printf("t_str!\n"); exit(1); + } +} + +static enum num_t t_is_32(enum num_t t) +{ + switch (t) { + case U64: return false; + case U32: return true; + case S64: return false; + case S32: return true; + default: printf("t_is_32!\n"); exit(1); + } +} + +static enum num_t t_signed(enum num_t t) +{ + switch (t) { + case U64: return S64; + case U32: return S32; + case S64: return S64; + case S32: return S32; + default: printf("t_signed!\n"); exit(1); + } +} + +static enum num_t t_unsigned(enum num_t t) +{ + switch (t) { + case U64: return U64; + case U32: return U32; + case S64: return U64; + case S32: return U32; + default: printf("t_unsigned!\n"); exit(1); + } +} + +static bool num_is_small(enum num_t t, u64 x) +{ + switch (t) { + case U64: return (u64)x <= 256; + case U32: return (u32)x <= 256; + case S64: return (s64)x >= -256 && (s64)x <= 256; + case S32: return (s32)x >= -256 && (s32)x <= 256; + default: printf("num_is_small!\n"); exit(1); + } +} + +static void snprintf_num(enum num_t t, struct strbuf *sb, u64 x) +{ + bool is_small = num_is_small(t, x); + + if (is_small) { + switch (t) { + case U64: return snappendf(sb, "%llu", (u64)x); + case U32: return snappendf(sb, "%u", (u32)x); + case S64: return snappendf(sb, "%lld", (s64)x); + case S32: return snappendf(sb, "%d", (s32)x); + default: printf("snprintf_num!\n"); exit(1); + } + } else { + switch (t) { + case U64: + if (x == U64_MAX) + return snappendf(sb, "U64_MAX"); + else if (x >= U64_MAX - 256) + return snappendf(sb, "U64_MAX-%llu", U64_MAX - x); + else + return snappendf(sb, "%#llx", (u64)x); + case U32: + if ((u32)x == U32_MAX) + return snappendf(sb, "U32_MAX"); + else if ((u32)x >= U32_MAX - 256) + return snappendf(sb, "U32_MAX-%u", U32_MAX - (u32)x); + else + return snappendf(sb, "%#x", (u32)x); + case S64: + if ((s64)x == S64_MAX) + return snappendf(sb, "S64_MAX"); + else if ((s64)x >= S64_MAX - 256) + return snappendf(sb, "S64_MAX-%lld", S64_MAX - (s64)x); + else if ((s64)x == S64_MIN) + return snappendf(sb, "S64_MIN"); + else if ((s64)x <= S64_MIN + 256) + return snappendf(sb, "S64_MIN+%lld", (s64)x - S64_MIN); + else + return snappendf(sb, "%#llx", (s64)x); + case S32: + if ((s32)x == S32_MAX) + return snappendf(sb, "S32_MAX"); + else if ((s32)x >= S32_MAX - 256) + return snappendf(sb, "S32_MAX-%d", S32_MAX - (s32)x); + else if ((s32)x == S32_MIN) + return snappendf(sb, "S32_MIN"); + else if ((s32)x <= S32_MIN + 256) + return snappendf(sb, "S32_MIN+%d", (s32)x - S32_MIN); + else + return snappendf(sb, "%#x", (s32)x); + default: printf("snprintf_num!\n"); exit(1); + } + } +} + +/* =================================== + * GENERIC RANGE STRUCT AND OPERATIONS + * =================================== + */ +struct range { + u64 a, b; +}; + +static void snprintf_range(enum num_t t, struct strbuf *sb, struct range x) +{ + if (x.a == x.b) + return snprintf_num(t, sb, x.a); + + snappendf(sb, "["); + snprintf_num(t, sb, x.a); + snappendf(sb, "; "); + snprintf_num(t, sb, x.b); + snappendf(sb, "]"); +} + +static void print_range(enum num_t t, struct range x, const char *sfx) +{ + DEFINE_STRBUF(sb, 128); + + snprintf_range(t, sb, x); + printf("%s%s", sb->buf, sfx); +} + +static const struct range unkn[] = { + [U64] = { 0, U64_MAX }, + [U32] = { 0, U32_MAX }, + [S64] = { (u64)S64_MIN, (u64)S64_MAX }, + [S32] = { (u64)(u32)S32_MIN, (u64)(u32)S32_MAX }, +}; + +static struct range unkn_subreg(enum num_t t) +{ + switch (t) { + case U64: return unkn[U32]; + case U32: return unkn[U32]; + case S64: return unkn[U32]; + case S32: return unkn[S32]; + default: printf("unkn_subreg!\n"); exit(1); + } +} + +static struct range range(enum num_t t, u64 a, u64 b) +{ + switch (t) { + case U64: return (struct range){ (u64)a, (u64)b }; + case U32: return (struct range){ (u32)a, (u32)b }; + case S64: return (struct range){ (s64)a, (s64)b }; + case S32: return (struct range){ (u32)(s32)a, (u32)(s32)b }; + default: printf("range!\n"); exit(1); + } +} + +static __always_inline u32 sign64(u64 x) { return (x >> 63) & 1; } +static __always_inline u32 sign32(u64 x) { return ((u32)x >> 31) & 1; } +static __always_inline u32 upper32(u64 x) { return (u32)(x >> 32); } +static __always_inline u64 swap_low32(u64 x, u32 y) { return (x & 0xffffffff00000000ULL) | y; } + +static bool range_eq(struct range x, struct range y) +{ + return x.a == y.a && x.b == y.b; +} + +static struct range range_cast_to_s32(struct range x) +{ + u64 a = x.a, b = x.b; + + /* if upper 32 bits are constant, lower 32 bits should form a proper + * s32 range to be correct + */ + if (upper32(a) == upper32(b) && (s32)a <= (s32)b) + return range(S32, a, b); + + /* Special case where upper bits form a small sequence of two + * sequential numbers (in 32-bit unsigned space, so 0xffffffff to + * 0x00000000 is also valid), while lower bits form a proper s32 range + * going from negative numbers to positive numbers. + * + * E.g.: [0xfffffff0ffffff00; 0xfffffff100000010]. Iterating + * over full 64-bit numbers range will form a proper [-16, 16] + * ([0xffffff00; 0x00000010]) range in its lower 32 bits. + */ + if (upper32(a) + 1 == upper32(b) && (s32)a < 0 && (s32)b >= 0) + return range(S32, a, b); + + /* otherwise we can't derive much meaningful information */ + return unkn[S32]; +} + +static struct range range_cast_u64(enum num_t to_t, struct range x) +{ + u64 a = (u64)x.a, b = (u64)x.b; + + switch (to_t) { + case U64: + return x; + case U32: + if (upper32(a) != upper32(b)) + return unkn[U32]; + return range(U32, a, b); + case S64: + if (sign64(a) != sign64(b)) + return unkn[S64]; + return range(S64, a, b); + case S32: + return range_cast_to_s32(x); + default: printf("range_cast_u64!\n"); exit(1); + } +} + +static struct range range_cast_s64(enum num_t to_t, struct range x) +{ + s64 a = (s64)x.a, b = (s64)x.b; + + switch (to_t) { + case U64: + /* equivalent to (s64)a <= (s64)b check */ + if (sign64(a) != sign64(b)) + return unkn[U64]; + return range(U64, a, b); + case U32: + if (upper32(a) != upper32(b) || sign32(a) != sign32(b)) + return unkn[U32]; + return range(U32, a, b); + case S64: + return x; + case S32: + return range_cast_to_s32(x); + default: printf("range_cast_s64!\n"); exit(1); + } +} + +static struct range range_cast_u32(enum num_t to_t, struct range x) +{ + u32 a = (u32)x.a, b = (u32)x.b; + + switch (to_t) { + case U64: + case S64: + /* u32 is always a valid zero-extended u64/s64 */ + return range(to_t, a, b); + case U32: + return x; + case S32: + return range_cast_to_s32(range(U32, a, b)); + default: printf("range_cast_u32!\n"); exit(1); + } +} + +static struct range range_cast_s32(enum num_t to_t, struct range x) +{ + s32 a = (s32)x.a, b = (s32)x.b; + + switch (to_t) { + case U64: + case U32: + case S64: + if (sign32(a) != sign32(b)) + return unkn[to_t]; + return range(to_t, a, b); + case S32: + return x; + default: printf("range_cast_s32!\n"); exit(1); + } +} + +/* Reinterpret range in *from_t* domain as a range in *to_t* domain preserving + * all possible information. Worst case, it will be unknown range within + * *to_t* domain, if nothing more specific can be guaranteed during the + * conversion + */ +static struct range range_cast(enum num_t from_t, enum num_t to_t, struct range from) +{ + switch (from_t) { + case U64: return range_cast_u64(to_t, from); + case U32: return range_cast_u32(to_t, from); + case S64: return range_cast_s64(to_t, from); + case S32: return range_cast_s32(to_t, from); + default: printf("range_cast!\n"); exit(1); + } +} + +static bool is_valid_num(enum num_t t, u64 x) +{ + switch (t) { + case U64: return true; + case U32: return upper32(x) == 0; + case S64: return true; + case S32: return upper32(x) == 0; + default: printf("is_valid_num!\n"); exit(1); + } +} + +static bool is_valid_range(enum num_t t, struct range x) +{ + if (!is_valid_num(t, x.a) || !is_valid_num(t, x.b)) + return false; + + switch (t) { + case U64: return (u64)x.a <= (u64)x.b; + case U32: return (u32)x.a <= (u32)x.b; + case S64: return (s64)x.a <= (s64)x.b; + case S32: return (s32)x.a <= (s32)x.b; + default: printf("is_valid_range!\n"); exit(1); + } +} + +static struct range range_improve(enum num_t t, struct range old, struct range new) +{ + return range(t, max_t(t, old.a, new.a), min_t(t, old.b, new.b)); +} + +static struct range range_refine(enum num_t x_t, struct range x, enum num_t y_t, struct range y) +{ + struct range y_cast; + + y_cast = range_cast(y_t, x_t, y); + + /* the case when new range knowledge, *y*, is a 32-bit subregister + * range, while previous range knowledge, *x*, is a full register + * 64-bit range, needs special treatment to take into account upper 32 + * bits of full register range + */ + if (t_is_32(y_t) && !t_is_32(x_t)) { + struct range x_swap; + + /* some combinations of upper 32 bits and sign bit can lead to + * invalid ranges, in such cases it's easier to detect them + * after cast/swap than try to enumerate all the conditions + * under which transformation and knowledge transfer is valid + */ + x_swap = range(x_t, swap_low32(x.a, y_cast.a), swap_low32(x.b, y_cast.b)); + if (!is_valid_range(x_t, x_swap)) + return x; + return range_improve(x_t, x, x_swap); + } + + /* otherwise, plain range cast and intersection works */ + return range_improve(x_t, x, y_cast); +} + +/* ======================= + * GENERIC CONDITIONAL OPS + * ======================= + */ +enum op { OP_LT, OP_LE, OP_GT, OP_GE, OP_EQ, OP_NE }; +#define MIN_OP OP_LT +#define MAX_OP OP_NE + +static enum op complement_op(enum op op) +{ + switch (op) { + case OP_LT: return OP_GE; + case OP_LE: return OP_GT; + case OP_GT: return OP_LE; + case OP_GE: return OP_LT; + case OP_EQ: return OP_NE; + case OP_NE: return OP_EQ; + default: printf("complement_op!\n"); exit(1); + } +} + +static const char *op_str(enum op op) +{ + switch (op) { + case OP_LT: return "<"; + case OP_LE: return "<="; + case OP_GT: return ">"; + case OP_GE: return ">="; + case OP_EQ: return "=="; + case OP_NE: return "!="; + default: printf("op_str!\n"); exit(1); + } +} + +/* Can register with range [x.a, x.b] *EVER* satisfy + * OP (<, <=, >, >=, ==, !=) relation to + * a regsiter with range [y.a, y.b] + * _in *num_t* domain_ + */ +static bool range_canbe_op(enum num_t t, struct range x, struct range y, enum op op) +{ +#define range_canbe(T) do { \ + switch (op) { \ + case OP_LT: return (T)x.a < (T)y.b; \ + case OP_LE: return (T)x.a <= (T)y.b; \ + case OP_GT: return (T)x.b > (T)y.a; \ + case OP_GE: return (T)x.b >= (T)y.a; \ + case OP_EQ: return (T)max_t(t, x.a, y.a) <= (T)min_t(t, x.b, y.b); \ + case OP_NE: return !((T)x.a == (T)x.b && (T)y.a == (T)y.b && (T)x.a == (T)y.a); \ + default: printf("range_canbe op %d\n", op); exit(1); \ + } \ +} while (0) + + switch (t) { + case U64: { range_canbe(u64); } + case U32: { range_canbe(u32); } + case S64: { range_canbe(s64); } + case S32: { range_canbe(s32); } + default: printf("range_canbe!\n"); exit(1); + } +#undef range_canbe +} + +/* Does register with range [x.a, x.b] *ALWAYS* satisfy + * OP (<, <=, >, >=, ==, !=) relation to + * a regsiter with range [y.a, y.b] + * _in *num_t* domain_ + */ +static bool range_always_op(enum num_t t, struct range x, struct range y, enum op op) +{ + /* always op <=> ! canbe complement(op) */ + return !range_canbe_op(t, x, y, complement_op(op)); +} + +/* Does register with range [x.a, x.b] *NEVER* satisfy + * OP (<, <=, >, >=, ==, !=) relation to + * a regsiter with range [y.a, y.b] + * _in *num_t* domain_ + */ +static bool range_never_op(enum num_t t, struct range x, struct range y, enum op op) +{ + return !range_canbe_op(t, x, y, op); +} + +/* similar to verifier's is_branch_taken(): + * 1 - always taken; + * 0 - never taken, + * -1 - unsure. + */ +static int range_branch_taken_op(enum num_t t, struct range x, struct range y, enum op op) +{ + if (range_always_op(t, x, y, op)) + return 1; + if (range_never_op(t, x, y, op)) + return 0; + return -1; +} + +/* What would be the new estimates for register x and y ranges assuming truthful + * OP comparison between them. I.e., (x OP y == true) => x <- newx, y <- newy. + * + * We assume "interesting" cases where ranges overlap. Cases where it's + * obvious that (x OP y) is either always true or false should be filtered with + * range_never and range_always checks. + */ +static void range_cond(enum num_t t, struct range x, struct range y, + enum op op, struct range *newx, struct range *newy) +{ + if (!range_canbe_op(t, x, y, op)) { + /* nothing to adjust, can't happen, return original values */ + *newx = x; + *newy = y; + return; + } + switch (op) { + case OP_LT: + *newx = range(t, x.a, min_t(t, x.b, y.b - 1)); + *newy = range(t, max_t(t, x.a + 1, y.a), y.b); + break; + case OP_LE: + *newx = range(t, x.a, min_t(t, x.b, y.b)); + *newy = range(t, max_t(t, x.a, y.a), y.b); + break; + case OP_GT: + *newx = range(t, max_t(t, x.a, y.a + 1), x.b); + *newy = range(t, y.a, min_t(t, x.b - 1, y.b)); + break; + case OP_GE: + *newx = range(t, max_t(t, x.a, y.a), x.b); + *newy = range(t, y.a, min_t(t, x.b, y.b)); + break; + case OP_EQ: + *newx = range(t, max_t(t, x.a, y.a), min_t(t, x.b, y.b)); + *newy = range(t, max_t(t, x.a, y.a), min_t(t, x.b, y.b)); + break; + case OP_NE: + /* generic case, can't derive more information */ + *newx = range(t, x.a, x.b); + *newy = range(t, y.a, y.b); + break; + + /* below extended logic is not supported by verifier just yet */ + if (x.a == x.b && x.a == y.a) { + /* X is a constant matching left side of Y */ + *newx = range(t, x.a, x.b); + *newy = range(t, y.a + 1, y.b); + } else if (x.a == x.b && x.b == y.b) { + /* X is a constant matching rigth side of Y */ + *newx = range(t, x.a, x.b); + *newy = range(t, y.a, y.b - 1); + } else if (y.a == y.b && x.a == y.a) { + /* Y is a constant matching left side of X */ + *newx = range(t, x.a + 1, x.b); + *newy = range(t, y.a, y.b); + } else if (y.a == y.b && x.b == y.b) { + /* Y is a constant matching rigth side of X */ + *newx = range(t, x.a, x.b - 1); + *newy = range(t, y.a, y.b); + } else { + /* generic case, can't derive more information */ + *newx = range(t, x.a, x.b); + *newy = range(t, y.a, y.b); + } + + break; + default: + break; + } +} + +/* ======================= + * REGISTER STATE HANDLING + * ======================= + */ +struct reg_state { + struct range r[4]; /* indexed by enum num_t: U64, U32, S64, S32 */ + bool valid; +}; + +static void print_reg_state(struct reg_state *r, const char *sfx) +{ + DEFINE_STRBUF(sb, 512); + enum num_t t; + int cnt = 0; + + if (!r->valid) { + printf("<not found>%s", sfx); + return; + } + + snappendf(sb, "scalar("); + for (t = MIN_T; t <= MAX_T; t++) { + snappendf(sb, "%s%s=", cnt++ ? "," : "", t_str(t)); + snprintf_range(t, sb, r->r[t]); + } + snappendf(sb, ")"); + + printf("%s%s", sb->buf, sfx); +} + +static void print_refinement(enum num_t s_t, struct range src, + enum num_t d_t, struct range old, struct range new, + const char *ctx) +{ + printf("REFINING (%s) (%s)SRC=", ctx, t_str(s_t)); + print_range(s_t, src, ""); + printf(" (%s)DST_OLD=", t_str(d_t)); + print_range(d_t, old, ""); + printf(" (%s)DST_NEW=", t_str(d_t)); + print_range(d_t, new, "\n"); +} + +static void reg_state_refine(struct reg_state *r, enum num_t t, struct range x, const char *ctx) +{ + enum num_t d_t, s_t; + struct range old; + bool keep_going = false; + +again: + /* try to derive new knowledge from just learned range x of type t */ + for (d_t = MIN_T; d_t <= MAX_T; d_t++) { + old = r->r[d_t]; + r->r[d_t] = range_refine(d_t, r->r[d_t], t, x); + if (!range_eq(r->r[d_t], old)) { + keep_going = true; + if (env.verbosity >= VERBOSE_VERY) + print_refinement(t, x, d_t, old, r->r[d_t], ctx); + } + } + + /* now see if we can derive anything new from updated reg_state's ranges */ + for (s_t = MIN_T; s_t <= MAX_T; s_t++) { + for (d_t = MIN_T; d_t <= MAX_T; d_t++) { + old = r->r[d_t]; + r->r[d_t] = range_refine(d_t, r->r[d_t], s_t, r->r[s_t]); + if (!range_eq(r->r[d_t], old)) { + keep_going = true; + if (env.verbosity >= VERBOSE_VERY) + print_refinement(s_t, r->r[s_t], d_t, old, r->r[d_t], ctx); + } + } + } + + /* keep refining until we converge */ + if (keep_going) { + keep_going = false; + goto again; + } +} + +static void reg_state_set_const(struct reg_state *rs, enum num_t t, u64 val) +{ + enum num_t tt; + + rs->valid = true; + for (tt = MIN_T; tt <= MAX_T; tt++) + rs->r[tt] = tt == t ? range(t, val, val) : unkn[tt]; + + reg_state_refine(rs, t, rs->r[t], "CONST"); +} + +static void reg_state_cond(enum num_t t, struct reg_state *x, struct reg_state *y, enum op op, + struct reg_state *newx, struct reg_state *newy, const char *ctx) +{ + char buf[32]; + enum num_t ts[2]; + struct reg_state xx = *x, yy = *y; + int i, t_cnt; + struct range z1, z2; + + if (op == OP_EQ || op == OP_NE) { + /* OP_EQ and OP_NE are sign-agnostic, so we need to process + * both signed and unsigned domains at the same time + */ + ts[0] = t_unsigned(t); + ts[1] = t_signed(t); + t_cnt = 2; + } else { + ts[0] = t; + t_cnt = 1; + } + + for (i = 0; i < t_cnt; i++) { + t = ts[i]; + z1 = x->r[t]; + z2 = y->r[t]; + + range_cond(t, z1, z2, op, &z1, &z2); + + if (newx) { + snprintf(buf, sizeof(buf), "%s R1", ctx); + reg_state_refine(&xx, t, z1, buf); + } + if (newy) { + snprintf(buf, sizeof(buf), "%s R2", ctx); + reg_state_refine(&yy, t, z2, buf); + } + } + + if (newx) + *newx = xx; + if (newy) + *newy = yy; +} + +static int reg_state_branch_taken_op(enum num_t t, struct reg_state *x, struct reg_state *y, + enum op op) +{ + if (op == OP_EQ || op == OP_NE) { + /* OP_EQ and OP_NE are sign-agnostic */ + enum num_t tu = t_unsigned(t); + enum num_t ts = t_signed(t); + int br_u, br_s; + + br_u = range_branch_taken_op(tu, x->r[tu], y->r[tu], op); + br_s = range_branch_taken_op(ts, x->r[ts], y->r[ts], op); + + if (br_u >= 0 && br_s >= 0 && br_u != br_s) + ASSERT_FALSE(true, "branch taken inconsistency!\n"); + if (br_u >= 0) + return br_u; + return br_s; + } + return range_branch_taken_op(t, x->r[t], y->r[t], op); +} + +/* ===================================== + * BPF PROGS GENERATION AND VERIFICATION + * ===================================== + */ +struct case_spec { + /* whether to init full register (r1) or sub-register (w1) */ + bool init_subregs; + /* whether to establish initial value range on full register (r1) or + * sub-register (w1) + */ + bool setup_subregs; + /* whether to establish initial value range using signed or unsigned + * comparisons (i.e., initialize umin/umax or smin/smax directly) + */ + bool setup_signed; + /* whether to perform comparison on full registers or sub-registers */ + bool compare_subregs; + /* whether to perform comparison using signed or unsigned operations */ + bool compare_signed; +}; + +/* Generate test BPF program based on provided test ranges, operation, and + * specifications about register bitness and signedness. + */ +static int load_range_cmp_prog(struct range x, struct range y, enum op op, + int branch_taken, struct case_spec spec, + char *log_buf, size_t log_sz, + int *false_pos, int *true_pos) +{ +#define emit(insn) ({ \ + struct bpf_insn __insns[] = { insn }; \ + int __i; \ + for (__i = 0; __i < ARRAY_SIZE(__insns); __i++) \ + insns[cur_pos + __i] = __insns[__i]; \ + cur_pos += __i; \ +}) +#define JMP_TO(target) (target - cur_pos - 1) + int cur_pos = 0, exit_pos, fd, op_code; + struct bpf_insn insns[64]; + LIBBPF_OPTS(bpf_prog_load_opts, opts, + .log_level = 2, + .log_buf = log_buf, + .log_size = log_sz, + ); + + /* ; skip exit block below + * goto +2; + */ + emit(BPF_JMP_A(2)); + exit_pos = cur_pos; + /* ; exit block for all the preparatory conditionals + * out: + * r0 = 0; + * exit; + */ + emit(BPF_MOV64_IMM(BPF_REG_0, 0)); + emit(BPF_EXIT_INSN()); + /* + * ; assign r6/w6 and r7/w7 unpredictable u64/u32 value + * call bpf_get_current_pid_tgid; + * r6 = r0; | w6 = w0; + * call bpf_get_current_pid_tgid; + * r7 = r0; | w7 = w0; + */ + emit(BPF_EMIT_CALL(BPF_FUNC_get_current_pid_tgid)); + if (spec.init_subregs) + emit(BPF_MOV32_REG(BPF_REG_6, BPF_REG_0)); + else + emit(BPF_MOV64_REG(BPF_REG_6, BPF_REG_0)); + emit(BPF_EMIT_CALL(BPF_FUNC_get_current_pid_tgid)); + if (spec.init_subregs) + emit(BPF_MOV32_REG(BPF_REG_7, BPF_REG_0)); + else + emit(BPF_MOV64_REG(BPF_REG_7, BPF_REG_0)); + /* ; setup initial r6/w6 possible value range ([x.a, x.b]) + * r1 = %[x.a] ll; | w1 = %[x.a]; + * r2 = %[x.b] ll; | w2 = %[x.b]; + * if r6 < r1 goto out; | if w6 < w1 goto out; + * if r6 > r2 goto out; | if w6 > w2 goto out; + */ + if (spec.setup_subregs) { + emit(BPF_MOV32_IMM(BPF_REG_1, (s32)x.a)); + emit(BPF_MOV32_IMM(BPF_REG_2, (s32)x.b)); + emit(BPF_JMP32_REG(spec.setup_signed ? BPF_JSLT : BPF_JLT, + BPF_REG_6, BPF_REG_1, JMP_TO(exit_pos))); + emit(BPF_JMP32_REG(spec.setup_signed ? BPF_JSGT : BPF_JGT, + BPF_REG_6, BPF_REG_2, JMP_TO(exit_pos))); + } else { + emit(BPF_LD_IMM64(BPF_REG_1, x.a)); + emit(BPF_LD_IMM64(BPF_REG_2, x.b)); + emit(BPF_JMP_REG(spec.setup_signed ? BPF_JSLT : BPF_JLT, + BPF_REG_6, BPF_REG_1, JMP_TO(exit_pos))); + emit(BPF_JMP_REG(spec.setup_signed ? BPF_JSGT : BPF_JGT, + BPF_REG_6, BPF_REG_2, JMP_TO(exit_pos))); + } + /* ; setup initial r7/w7 possible value range ([y.a, y.b]) + * r1 = %[y.a] ll; | w1 = %[y.a]; + * r2 = %[y.b] ll; | w2 = %[y.b]; + * if r7 < r1 goto out; | if w7 < w1 goto out; + * if r7 > r2 goto out; | if w7 > w2 goto out; + */ + if (spec.setup_subregs) { + emit(BPF_MOV32_IMM(BPF_REG_1, (s32)y.a)); + emit(BPF_MOV32_IMM(BPF_REG_2, (s32)y.b)); + emit(BPF_JMP32_REG(spec.setup_signed ? BPF_JSLT : BPF_JLT, + BPF_REG_7, BPF_REG_1, JMP_TO(exit_pos))); + emit(BPF_JMP32_REG(spec.setup_signed ? BPF_JSGT : BPF_JGT, + BPF_REG_7, BPF_REG_2, JMP_TO(exit_pos))); + } else { + emit(BPF_LD_IMM64(BPF_REG_1, y.a)); + emit(BPF_LD_IMM64(BPF_REG_2, y.b)); + emit(BPF_JMP_REG(spec.setup_signed ? BPF_JSLT : BPF_JLT, + BPF_REG_7, BPF_REG_1, JMP_TO(exit_pos))); + emit(BPF_JMP_REG(spec.setup_signed ? BPF_JSGT : BPF_JGT, + BPF_REG_7, BPF_REG_2, JMP_TO(exit_pos))); + } + /* ; range test instruction + * if r6 <op> r7 goto +3; | if w6 <op> w7 goto +3; + */ + switch (op) { + case OP_LT: op_code = spec.compare_signed ? BPF_JSLT : BPF_JLT; break; + case OP_LE: op_code = spec.compare_signed ? BPF_JSLE : BPF_JLE; break; + case OP_GT: op_code = spec.compare_signed ? BPF_JSGT : BPF_JGT; break; + case OP_GE: op_code = spec.compare_signed ? BPF_JSGE : BPF_JGE; break; + case OP_EQ: op_code = BPF_JEQ; break; + case OP_NE: op_code = BPF_JNE; break; + default: + printf("unrecognized op %d\n", op); + return -ENOTSUP; + } + /* ; BEFORE conditiona, r0/w0 = {r6/w6,r7/w7} is to extract verifier state reliably + * ; this is used for debugging, as verifier doesn't always print + * ; registers states as of condition jump instruction (e.g., when + * ; precision marking happens) + * r0 = r6; | w0 = w6; + * r0 = r7; | w0 = w7; + */ + if (spec.compare_subregs) { + emit(BPF_MOV32_REG(BPF_REG_0, BPF_REG_6)); + emit(BPF_MOV32_REG(BPF_REG_0, BPF_REG_7)); + } else { + emit(BPF_MOV64_REG(BPF_REG_0, BPF_REG_6)); + emit(BPF_MOV64_REG(BPF_REG_0, BPF_REG_7)); + } + if (spec.compare_subregs) + emit(BPF_JMP32_REG(op_code, BPF_REG_6, BPF_REG_7, 3)); + else + emit(BPF_JMP_REG(op_code, BPF_REG_6, BPF_REG_7, 3)); + /* ; FALSE branch, r0/w0 = {r6/w6,r7/w7} is to extract verifier state reliably + * r0 = r6; | w0 = w6; + * r0 = r7; | w0 = w7; + * exit; + */ + *false_pos = cur_pos; + if (spec.compare_subregs) { + emit(BPF_MOV32_REG(BPF_REG_0, BPF_REG_6)); + emit(BPF_MOV32_REG(BPF_REG_0, BPF_REG_7)); + } else { + emit(BPF_MOV64_REG(BPF_REG_0, BPF_REG_6)); + emit(BPF_MOV64_REG(BPF_REG_0, BPF_REG_7)); + } + if (branch_taken == 1) /* false branch is never taken */ + emit(BPF_EMIT_CALL(0xDEAD)); /* poison this branch */ + else + emit(BPF_EXIT_INSN()); + /* ; TRUE branch, r0/w0 = {r6/w6,r7/w7} is to extract verifier state reliably + * r0 = r6; | w0 = w6; + * r0 = r7; | w0 = w7; + * exit; + */ + *true_pos = cur_pos; + if (spec.compare_subregs) { + emit(BPF_MOV32_REG(BPF_REG_0, BPF_REG_6)); + emit(BPF_MOV32_REG(BPF_REG_0, BPF_REG_7)); + } else { + emit(BPF_MOV64_REG(BPF_REG_0, BPF_REG_6)); + emit(BPF_MOV64_REG(BPF_REG_0, BPF_REG_7)); + } + if (branch_taken == 0) /* true branch is never taken */ + emit(BPF_EMIT_CALL(0xDEAD)); /* poison this branch */ + emit(BPF_EXIT_INSN()); /* last instruction has to be exit */ + + fd = bpf_prog_load(BPF_PROG_TYPE_RAW_TRACEPOINT, "reg_bounds_test", + "GPL", insns, cur_pos, &opts); + if (fd < 0) + return fd; + + close(fd); + return 0; +#undef emit +#undef JMP_TO +} + +#define str_has_pfx(str, pfx) \ + (strncmp(str, pfx, __builtin_constant_p(pfx) ? sizeof(pfx) - 1 : strlen(pfx)) == 0) + +/* Parse register state from verifier log. + * `s` should point to the start of "Rx = ..." substring in the verifier log. + */ +static int parse_reg_state(const char *s, struct reg_state *reg) +{ + /* There are two generic forms for SCALAR register: + * - known constant: R6_rwD=P%lld + * - range: R6_rwD=scalar(id=1,...), where "..." is a comma-separated + * list of optional range specifiers: + * - umin=%llu, if missing, assumed 0; + * - umax=%llu, if missing, assumed U64_MAX; + * - smin=%lld, if missing, assumed S64_MIN; + * - smax=%lld, if missing, assummed S64_MAX; + * - umin32=%d, if missing, assumed 0; + * - umax32=%d, if missing, assumed U32_MAX; + * - smin32=%d, if missing, assumed S32_MIN; + * - smax32=%d, if missing, assummed S32_MAX; + * - var_off=(%#llx; %#llx), tnum part, we don't care about it. + * + * If some of the values are equal, they will be grouped (but min/max + * are not mixed together, and similarly negative values are not + * grouped with non-negative ones). E.g.: + * + * R6_w=Pscalar(smin=smin32=0, smax=umax=umax32=1000) + * + * _rwD part is optional (and any of the letters can be missing). + * P (precision mark) is optional as well. + * + * Anything inside scalar() is optional, including id, of course. + */ + struct { + const char *pfx; + const char *fmt; + u64 *dst, def; + bool is_32, is_set; + } *f, fields[8] = { + {"smin=", "%lld", ®->r[S64].a, S64_MIN}, + {"smax=", "%lld", ®->r[S64].b, S64_MAX}, + {"umin=", "%llu", ®->r[U64].a, 0}, + {"umax=", "%llu", ®->r[U64].b, U64_MAX}, + {"smin32=", "%lld", ®->r[S32].a, (u32)S32_MIN, true}, + {"smax32=", "%lld", ®->r[S32].b, (u32)S32_MAX, true}, + {"umin32=", "%llu", ®->r[U32].a, 0, true}, + {"umax32=", "%llu", ®->r[U32].b, U32_MAX, true}, + }; + const char *p, *fmt; + int i; + + p = strchr(s, '='); + if (!p) + return -EINVAL; + p++; + if (*p == 'P') + p++; + + if (!str_has_pfx(p, "scalar(")) { + long long sval; + enum num_t t; + + if (sscanf(p, "%lld", &sval) != 1) + return -EINVAL; + + reg->valid = true; + for (t = MIN_T; t <= MAX_T; t++) { + reg->r[t] = range(t, sval, sval); + } + return 0; + } + + p += sizeof("scalar"); + while (p) { + int midxs[ARRAY_SIZE(fields)], mcnt = 0; + u64 val; + + for (i = 0; i < ARRAY_SIZE(fields); i++) { + f = &fields[i]; + if (!str_has_pfx(p, f->pfx)) + continue; + midxs[mcnt++] = i; + p += strlen(f->pfx); + } + + if (mcnt) { + /* populate all matched fields */ + fmt = fields[midxs[0]].fmt; + if (sscanf(p, fmt, &val) != 1) + return -EINVAL; + + for (i = 0; i < mcnt; i++) { + f = &fields[midxs[i]]; + f->is_set = true; + *f->dst = f->is_32 ? (u64)(u32)val : val; + } + } else if (str_has_pfx(p, "var_off")) { + /* skip "var_off=(0x0; 0x3f)" part completely */ + p = strchr(p, ')'); + if (!p) + return -EINVAL; + p++; + } + + p = strpbrk(p, ",)"); + if (*p == ')') + break; + if (p) + p++; + } + + reg->valid = true; + + for (i = 0; i < ARRAY_SIZE(fields); i++) { + f = &fields[i]; + if (!f->is_set) + *f->dst = f->def; + } + + return 0; +} + + +/* Parse all register states (TRUE/FALSE branches and DST/SRC registers) + * out of the verifier log for a corresponding test case BPF program. + */ +static int parse_range_cmp_log(const char *log_buf, struct case_spec spec, + int false_pos, int true_pos, + struct reg_state *false1_reg, struct reg_state *false2_reg, + struct reg_state *true1_reg, struct reg_state *true2_reg) +{ + struct { + int insn_idx; + int reg_idx; + const char *reg_upper; + struct reg_state *state; + } specs[] = { + {false_pos, 6, "R6=", false1_reg}, + {false_pos + 1, 7, "R7=", false2_reg}, + {true_pos, 6, "R6=", true1_reg}, + {true_pos + 1, 7, "R7=", true2_reg}, + }; + char buf[32]; + const char *p = log_buf, *q; + int i, err; + + for (i = 0; i < 4; i++) { + sprintf(buf, "%d: (%s) %s = %s%d", specs[i].insn_idx, + spec.compare_subregs ? "bc" : "bf", + spec.compare_subregs ? "w0" : "r0", + spec.compare_subregs ? "w" : "r", specs[i].reg_idx); + + q = strstr(p, buf); + if (!q) { + *specs[i].state = (struct reg_state){.valid = false}; + continue; + } + p = strstr(q, specs[i].reg_upper); + if (!p) + return -EINVAL; + err = parse_reg_state(p, specs[i].state); + if (err) + return -EINVAL; + } + return 0; +} + +/* Validate ranges match, and print details if they don't */ +static bool assert_range_eq(enum num_t t, struct range x, struct range y, + const char *ctx1, const char *ctx2) +{ + DEFINE_STRBUF(sb, 512); + + if (range_eq(x, y)) + return true; + + snappendf(sb, "MISMATCH %s.%s: ", ctx1, ctx2); + snprintf_range(t, sb, x); + snappendf(sb, " != "); + snprintf_range(t, sb, y); + + printf("%s\n", sb->buf); + + return false; +} + +/* Validate that register states match, and print details if they don't */ +static bool assert_reg_state_eq(struct reg_state *r, struct reg_state *e, const char *ctx) +{ + bool ok = true; + enum num_t t; + + if (r->valid != e->valid) { + printf("MISMATCH %s: actual %s != expected %s\n", ctx, + r->valid ? "<valid>" : "<invalid>", + e->valid ? "<valid>" : "<invalid>"); + return false; + } + + if (!r->valid) + return true; + + for (t = MIN_T; t <= MAX_T; t++) { + if (!assert_range_eq(t, r->r[t], e->r[t], ctx, t_str(t))) + ok = false; + } + + return ok; +} + +/* Printf verifier log, filtering out irrelevant noise */ +static void print_verifier_log(const char *buf) +{ + const char *p; + + while (buf[0]) { + p = strchrnul(buf, '\n'); + + /* filter out irrelevant precision backtracking logs */ + if (str_has_pfx(buf, "mark_precise: ")) + goto skip_line; + + printf("%.*s\n", (int)(p - buf), buf); + +skip_line: + buf = *p == '\0' ? p : p + 1; + } +} + +/* Simulate provided test case purely with our own range-based logic. + * This is done to set up expectations for verifier's branch_taken logic and + * verifier's register states in the verifier log. + */ +static void sim_case(enum num_t init_t, enum num_t cond_t, + struct range x, struct range y, enum op op, + struct reg_state *fr1, struct reg_state *fr2, + struct reg_state *tr1, struct reg_state *tr2, + int *branch_taken) +{ + const u64 A = x.a; + const u64 B = x.b; + const u64 C = y.a; + const u64 D = y.b; + struct reg_state rc; + enum op rev_op = complement_op(op); + enum num_t t; + + fr1->valid = fr2->valid = true; + tr1->valid = tr2->valid = true; + for (t = MIN_T; t <= MAX_T; t++) { + /* if we are initializing using 32-bit subregisters, + * full registers get upper 32 bits zeroed automatically + */ + struct range z = t_is_32(init_t) ? unkn_subreg(t) : unkn[t]; + + fr1->r[t] = fr2->r[t] = tr1->r[t] = tr2->r[t] = z; + } + + /* step 1: r1 >= A, r2 >= C */ + reg_state_set_const(&rc, init_t, A); + reg_state_cond(init_t, fr1, &rc, OP_GE, fr1, NULL, "r1>=A"); + reg_state_set_const(&rc, init_t, C); + reg_state_cond(init_t, fr2, &rc, OP_GE, fr2, NULL, "r2>=C"); + *tr1 = *fr1; + *tr2 = *fr2; + if (env.verbosity >= VERBOSE_VERY) { + printf("STEP1 (%s) R1: ", t_str(init_t)); print_reg_state(fr1, "\n"); + printf("STEP1 (%s) R2: ", t_str(init_t)); print_reg_state(fr2, "\n"); + } + + /* step 2: r1 <= B, r2 <= D */ + reg_state_set_const(&rc, init_t, B); + reg_state_cond(init_t, fr1, &rc, OP_LE, fr1, NULL, "r1<=B"); + reg_state_set_const(&rc, init_t, D); + reg_state_cond(init_t, fr2, &rc, OP_LE, fr2, NULL, "r2<=D"); + *tr1 = *fr1; + *tr2 = *fr2; + if (env.verbosity >= VERBOSE_VERY) { + printf("STEP2 (%s) R1: ", t_str(init_t)); print_reg_state(fr1, "\n"); + printf("STEP2 (%s) R2: ", t_str(init_t)); print_reg_state(fr2, "\n"); + } + + /* step 3: r1 <op> r2 */ + *branch_taken = reg_state_branch_taken_op(cond_t, fr1, fr2, op); + fr1->valid = fr2->valid = false; + tr1->valid = tr2->valid = false; + if (*branch_taken != 1) { /* FALSE is possible */ + fr1->valid = fr2->valid = true; + reg_state_cond(cond_t, fr1, fr2, rev_op, fr1, fr2, "FALSE"); + } + if (*branch_taken != 0) { /* TRUE is possible */ + tr1->valid = tr2->valid = true; + reg_state_cond(cond_t, tr1, tr2, op, tr1, tr2, "TRUE"); + } + if (env.verbosity >= VERBOSE_VERY) { + printf("STEP3 (%s) FALSE R1:", t_str(cond_t)); print_reg_state(fr1, "\n"); + printf("STEP3 (%s) FALSE R2:", t_str(cond_t)); print_reg_state(fr2, "\n"); + printf("STEP3 (%s) TRUE R1:", t_str(cond_t)); print_reg_state(tr1, "\n"); + printf("STEP3 (%s) TRUE R2:", t_str(cond_t)); print_reg_state(tr2, "\n"); + } +} + +/* =============================== + * HIGH-LEVEL TEST CASE VALIDATION + * =============================== + */ +static u32 upper_seeds[] = { + 0, + 1, + U32_MAX, + U32_MAX - 1, + S32_MAX, + (u32)S32_MIN, +}; + +static u32 lower_seeds[] = { + 0, + 1, + 2, (u32)-2, + 255, (u32)-255, + UINT_MAX, + UINT_MAX - 1, + INT_MAX, + (u32)INT_MIN, +}; + +struct ctx { + int val_cnt, subval_cnt, range_cnt, subrange_cnt; + u64 uvals[ARRAY_SIZE(upper_seeds) * ARRAY_SIZE(lower_seeds)]; + s64 svals[ARRAY_SIZE(upper_seeds) * ARRAY_SIZE(lower_seeds)]; + u32 usubvals[ARRAY_SIZE(lower_seeds)]; + s32 ssubvals[ARRAY_SIZE(lower_seeds)]; + struct range *uranges, *sranges; + struct range *usubranges, *ssubranges; + int max_failure_cnt, cur_failure_cnt; + int total_case_cnt, case_cnt; + __u64 start_ns; + char progress_ctx[32]; +}; + +static void cleanup_ctx(struct ctx *ctx) +{ + free(ctx->uranges); + free(ctx->sranges); + free(ctx->usubranges); + free(ctx->ssubranges); +} + +struct subtest_case { + enum num_t init_t; + enum num_t cond_t; + struct range x; + struct range y; + enum op op; +}; + +static void subtest_case_str(struct strbuf *sb, struct subtest_case *t) +{ + snappendf(sb, "(%s)", t_str(t->init_t)); + snprintf_range(t->init_t, sb, t->x); + snappendf(sb, " (%s)%s ", t_str(t->cond_t), op_str(t->op)); + snprintf_range(t->init_t, sb, t->y); +} + +/* Generate and validate test case based on specific combination of setup + * register ranges (including their expected num_t domain), and conditional + * operation to perform (including num_t domain in which it has to be + * performed) + */ +static int verify_case_op(enum num_t init_t, enum num_t cond_t, + struct range x, struct range y, enum op op) +{ + char log_buf[256 * 1024]; + size_t log_sz = sizeof(log_buf); + int err, false_pos = 0, true_pos = 0, branch_taken; + struct reg_state fr1, fr2, tr1, tr2; + struct reg_state fe1, fe2, te1, te2; + bool failed = false; + struct case_spec spec = { + .init_subregs = (init_t == U32 || init_t == S32), + .setup_subregs = (init_t == U32 || init_t == S32), + .setup_signed = (init_t == S64 || init_t == S32), + .compare_subregs = (cond_t == U32 || cond_t == S32), + .compare_signed = (cond_t == S64 || cond_t == S32), + }; + + log_buf[0] = '\0'; + + sim_case(init_t, cond_t, x, y, op, &fe1, &fe2, &te1, &te2, &branch_taken); + + err = load_range_cmp_prog(x, y, op, branch_taken, spec, + log_buf, log_sz, &false_pos, &true_pos); + if (err) { + ASSERT_OK(err, "load_range_cmp_prog"); + failed = true; + } + + err = parse_range_cmp_log(log_buf, spec, false_pos, true_pos, + &fr1, &fr2, &tr1, &tr2); + if (err) { + ASSERT_OK(err, "parse_range_cmp_log"); + failed = true; + } + + if (!assert_reg_state_eq(&fr1, &fe1, "false_reg1") || + !assert_reg_state_eq(&fr2, &fe2, "false_reg2") || + !assert_reg_state_eq(&tr1, &te1, "true_reg1") || + !assert_reg_state_eq(&tr2, &te2, "true_reg2")) { + failed = true; + } + + if (failed || env.verbosity >= VERBOSE_NORMAL) { + if (failed || env.verbosity >= VERBOSE_VERY) { + printf("VERIFIER LOG:\n========================\n"); + print_verifier_log(log_buf); + printf("=====================\n"); + } + printf("ACTUAL FALSE1: "); print_reg_state(&fr1, "\n"); + printf("EXPECTED FALSE1: "); print_reg_state(&fe1, "\n"); + printf("ACTUAL FALSE2: "); print_reg_state(&fr2, "\n"); + printf("EXPECTED FALSE2: "); print_reg_state(&fe2, "\n"); + printf("ACTUAL TRUE1: "); print_reg_state(&tr1, "\n"); + printf("EXPECTED TRUE1: "); print_reg_state(&te1, "\n"); + printf("ACTUAL TRUE2: "); print_reg_state(&tr2, "\n"); + printf("EXPECTED TRUE2: "); print_reg_state(&te2, "\n"); + + return failed ? -EINVAL : 0; + } + + return 0; +} + +/* Given setup ranges and number types, go over all supported operations, + * generating individual subtest for each allowed combination + */ +static int verify_case(struct ctx *ctx, enum num_t init_t, enum num_t cond_t, + struct range x, struct range y) +{ + DEFINE_STRBUF(sb, 256); + int err; + struct subtest_case sub = { + .init_t = init_t, + .cond_t = cond_t, + .x = x, + .y = y, + }; + + for (sub.op = MIN_OP; sub.op <= MAX_OP; sub.op++) { + sb->pos = 0; /* reset position in strbuf */ + subtest_case_str(sb, &sub); + if (!test__start_subtest(sb->buf)) + continue; + + if (env.verbosity >= VERBOSE_NORMAL) /* this speeds up debugging */ + printf("TEST CASE: %s\n", sb->buf); + + err = verify_case_op(init_t, cond_t, x, y, sub.op); + if (err || env.verbosity >= VERBOSE_NORMAL) + ASSERT_OK(err, sb->buf); + if (err) { + ctx->cur_failure_cnt++; + if (ctx->cur_failure_cnt > ctx->max_failure_cnt) + return err; + return 0; /* keep testing other cases */ + } + ctx->case_cnt++; + if ((ctx->case_cnt % 10000) == 0) { + double progress = (ctx->case_cnt + 0.0) / ctx->total_case_cnt; + u64 elapsed_ns = get_time_ns() - ctx->start_ns; + double remain_ns = elapsed_ns / progress * (1 - progress); + + fprintf(env.stderr, "PROGRESS (%s): %d/%d (%.2lf%%), " + "elapsed %llu mins (%.2lf hrs), " + "ETA %.0lf mins (%.2lf hrs)\n", + ctx->progress_ctx, + ctx->case_cnt, ctx->total_case_cnt, 100.0 * progress, + elapsed_ns / 1000000000 / 60, + elapsed_ns / 1000000000.0 / 3600, + remain_ns / 1000000000.0 / 60, + remain_ns / 1000000000.0 / 3600); + } + } + + return 0; +} + +/* ================================ + * GENERATED CASES FROM SEED VALUES + * ================================ + */ +static int u64_cmp(const void *p1, const void *p2) +{ + u64 x1 = *(const u64 *)p1, x2 = *(const u64 *)p2; + + return x1 != x2 ? (x1 < x2 ? -1 : 1) : 0; +} + +static int u32_cmp(const void *p1, const void *p2) +{ + u32 x1 = *(const u32 *)p1, x2 = *(const u32 *)p2; + + return x1 != x2 ? (x1 < x2 ? -1 : 1) : 0; +} + +static int s64_cmp(const void *p1, const void *p2) +{ + s64 x1 = *(const s64 *)p1, x2 = *(const s64 *)p2; + + return x1 != x2 ? (x1 < x2 ? -1 : 1) : 0; +} + +static int s32_cmp(const void *p1, const void *p2) +{ + s32 x1 = *(const s32 *)p1, x2 = *(const s32 *)p2; + + return x1 != x2 ? (x1 < x2 ? -1 : 1) : 0; +} + +/* Generate valid unique constants from seeds, both signed and unsigned */ +static void gen_vals(struct ctx *ctx) +{ + int i, j, cnt = 0; + + for (i = 0; i < ARRAY_SIZE(upper_seeds); i++) { + for (j = 0; j < ARRAY_SIZE(lower_seeds); j++) { + ctx->uvals[cnt++] = (((u64)upper_seeds[i]) << 32) | lower_seeds[j]; + } + } + + /* sort and compact uvals (i.e., it's `sort | uniq`) */ + qsort(ctx->uvals, cnt, sizeof(*ctx->uvals), u64_cmp); + for (i = 1, j = 0; i < cnt; i++) { + if (ctx->uvals[j] == ctx->uvals[i]) + continue; + j++; + ctx->uvals[j] = ctx->uvals[i]; + } + ctx->val_cnt = j + 1; + + /* we have exactly the same number of s64 values, they are just in + * a different order than u64s, so just sort them differently + */ + for (i = 0; i < ctx->val_cnt; i++) + ctx->svals[i] = ctx->uvals[i]; + qsort(ctx->svals, ctx->val_cnt, sizeof(*ctx->svals), s64_cmp); + + if (env.verbosity >= VERBOSE_SUPER) { + DEFINE_STRBUF(sb1, 256); + DEFINE_STRBUF(sb2, 256); + + for (i = 0; i < ctx->val_cnt; i++) { + sb1->pos = sb2->pos = 0; + snprintf_num(U64, sb1, ctx->uvals[i]); + snprintf_num(S64, sb2, ctx->svals[i]); + printf("SEED #%d: u64=%-20s s64=%-20s\n", i, sb1->buf, sb2->buf); + } + } + + /* 32-bit values are generated separately */ + cnt = 0; + for (i = 0; i < ARRAY_SIZE(lower_seeds); i++) { + ctx->usubvals[cnt++] = lower_seeds[i]; + } + + /* sort and compact usubvals (i.e., it's `sort | uniq`) */ + qsort(ctx->usubvals, cnt, sizeof(*ctx->usubvals), u32_cmp); + for (i = 1, j = 0; i < cnt; i++) { + if (ctx->usubvals[j] == ctx->usubvals[i]) + continue; + j++; + ctx->usubvals[j] = ctx->usubvals[i]; + } + ctx->subval_cnt = j + 1; + + for (i = 0; i < ctx->subval_cnt; i++) + ctx->ssubvals[i] = ctx->usubvals[i]; + qsort(ctx->ssubvals, ctx->subval_cnt, sizeof(*ctx->ssubvals), s32_cmp); + + if (env.verbosity >= VERBOSE_SUPER) { + DEFINE_STRBUF(sb1, 256); + DEFINE_STRBUF(sb2, 256); + + for (i = 0; i < ctx->subval_cnt; i++) { + sb1->pos = sb2->pos = 0; + snprintf_num(U32, sb1, ctx->usubvals[i]); + snprintf_num(S32, sb2, ctx->ssubvals[i]); + printf("SUBSEED #%d: u32=%-10s s32=%-10s\n", i, sb1->buf, sb2->buf); + } + } +} + +/* Generate valid ranges from upper/lower seeds */ +static int gen_ranges(struct ctx *ctx) +{ + int i, j, cnt = 0; + + for (i = 0; i < ctx->val_cnt; i++) { + for (j = i; j < ctx->val_cnt; j++) { + if (env.verbosity >= VERBOSE_SUPER) { + DEFINE_STRBUF(sb1, 256); + DEFINE_STRBUF(sb2, 256); + + sb1->pos = sb2->pos = 0; + snprintf_range(U64, sb1, range(U64, ctx->uvals[i], ctx->uvals[j])); + snprintf_range(S64, sb2, range(S64, ctx->svals[i], ctx->svals[j])); + printf("RANGE #%d: u64=%-40s s64=%-40s\n", cnt, sb1->buf, sb2->buf); + } + cnt++; + } + } + ctx->range_cnt = cnt; + + ctx->uranges = calloc(ctx->range_cnt, sizeof(*ctx->uranges)); + if (!ASSERT_OK_PTR(ctx->uranges, "uranges_calloc")) + return -EINVAL; + ctx->sranges = calloc(ctx->range_cnt, sizeof(*ctx->sranges)); + if (!ASSERT_OK_PTR(ctx->sranges, "sranges_calloc")) + return -EINVAL; + + cnt = 0; + for (i = 0; i < ctx->val_cnt; i++) { + for (j = i; j < ctx->val_cnt; j++) { + ctx->uranges[cnt] = range(U64, ctx->uvals[i], ctx->uvals[j]); + ctx->sranges[cnt] = range(S64, ctx->svals[i], ctx->svals[j]); + cnt++; + } + } + + cnt = 0; + for (i = 0; i < ctx->subval_cnt; i++) { + for (j = i; j < ctx->subval_cnt; j++) { + if (env.verbosity >= VERBOSE_SUPER) { + DEFINE_STRBUF(sb1, 256); + DEFINE_STRBUF(sb2, 256); + + sb1->pos = sb2->pos = 0; + snprintf_range(U32, sb1, range(U32, ctx->usubvals[i], ctx->usubvals[j])); + snprintf_range(S32, sb2, range(S32, ctx->ssubvals[i], ctx->ssubvals[j])); + printf("SUBRANGE #%d: u32=%-20s s32=%-20s\n", cnt, sb1->buf, sb2->buf); + } + cnt++; + } + } + ctx->subrange_cnt = cnt; + + ctx->usubranges = calloc(ctx->subrange_cnt, sizeof(*ctx->usubranges)); + if (!ASSERT_OK_PTR(ctx->usubranges, "usubranges_calloc")) + return -EINVAL; + ctx->ssubranges = calloc(ctx->subrange_cnt, sizeof(*ctx->ssubranges)); + if (!ASSERT_OK_PTR(ctx->ssubranges, "ssubranges_calloc")) + return -EINVAL; + + cnt = 0; + for (i = 0; i < ctx->subval_cnt; i++) { + for (j = i; j < ctx->subval_cnt; j++) { + ctx->usubranges[cnt] = range(U32, ctx->usubvals[i], ctx->usubvals[j]); + ctx->ssubranges[cnt] = range(S32, ctx->ssubvals[i], ctx->ssubvals[j]); + cnt++; + } + } + + return 0; +} + +static int parse_env_vars(struct ctx *ctx) +{ + const char *s; + + if (!(s = getenv("SLOW_TESTS")) || strcmp(s, "1") != 0) { + test__skip(); + return -ENOTSUP; + } + + if ((s = getenv("REG_BOUNDS_MAX_FAILURE_CNT"))) { + errno = 0; + ctx->max_failure_cnt = strtol(s, NULL, 10); + if (errno || ctx->max_failure_cnt < 0) { + ASSERT_OK(-errno, "REG_BOUNDS_MAX_FAILURE_CNT"); + return -EINVAL; + } + } + + return 0; +} + +static int prepare_gen_tests(struct ctx *ctx) +{ + int err; + + err = parse_env_vars(ctx); + if (err) + return err; + + gen_vals(ctx); + err = gen_ranges(ctx); + if (err) { + ASSERT_OK(err, "gen_ranges"); + return err; + } + + return 0; +} + +/* Go over generated constants and ranges and validate various supported + * combinations of them + */ +static void validate_gen_range_vs_const_64(enum num_t init_t, enum num_t cond_t) +{ + struct ctx ctx; + struct range rconst; + const struct range *ranges; + const u64 *vals; + int i, j; + + memset(&ctx, 0, sizeof(ctx)); + + if (prepare_gen_tests(&ctx)) + goto cleanup; + + ranges = init_t == U64 ? ctx.uranges : ctx.sranges; + vals = init_t == U64 ? ctx.uvals : (const u64 *)ctx.svals; + + ctx.total_case_cnt = (MAX_OP - MIN_OP + 1) * (2 * ctx.range_cnt * ctx.val_cnt); + ctx.start_ns = get_time_ns(); + snprintf(ctx.progress_ctx, sizeof(ctx.progress_ctx), + "RANGE x CONST, %s -> %s", + t_str(init_t), t_str(cond_t)); + + for (i = 0; i < ctx.val_cnt; i++) { + for (j = 0; j < ctx.range_cnt; j++) { + rconst = range(init_t, vals[i], vals[i]); + + /* (u64|s64)(<range> x <const>) */ + if (verify_case(&ctx, init_t, cond_t, ranges[j], rconst)) + goto cleanup; + /* (u64|s64)(<const> x <range>) */ + if (verify_case(&ctx, init_t, cond_t, rconst, ranges[j])) + goto cleanup; + } + } + +cleanup: + cleanup_ctx(&ctx); +} + +static void validate_gen_range_vs_const_32(enum num_t init_t, enum num_t cond_t) +{ + struct ctx ctx; + struct range rconst; + const struct range *ranges; + const u32 *vals; + int i, j; + + memset(&ctx, 0, sizeof(ctx)); + + if (prepare_gen_tests(&ctx)) + goto cleanup; + + ranges = init_t == U32 ? ctx.usubranges : ctx.ssubranges; + vals = init_t == U32 ? ctx.usubvals : (const u32 *)ctx.ssubvals; + + ctx.total_case_cnt = (MAX_OP - MIN_OP + 1) * (2 * ctx.subrange_cnt * ctx.subval_cnt); + ctx.start_ns = get_time_ns(); + snprintf(ctx.progress_ctx, sizeof(ctx.progress_ctx), + "RANGE x CONST, %s -> %s", + t_str(init_t), t_str(cond_t)); + + for (i = 0; i < ctx.subval_cnt; i++) { + for (j = 0; j < ctx.subrange_cnt; j++) { + rconst = range(init_t, vals[i], vals[i]); + + /* (u32|s32)(<range> x <const>) */ + if (verify_case(&ctx, init_t, cond_t, ranges[j], rconst)) + goto cleanup; + /* (u32|s32)(<const> x <range>) */ + if (verify_case(&ctx, init_t, cond_t, rconst, ranges[j])) + goto cleanup; + } + } + +cleanup: + cleanup_ctx(&ctx); +} + +/* Go over thousands of test cases generated from initial seed values. + * Given this take a long time, guard this begind SLOW_TESTS=1 envvar. If + * envvar is not set, this test is skipped during test_progs testing. + * + * We split this up into smaller subsets based on initialization and + * conditiona numeric domains to get an easy parallelization with test_progs' + * -j argument. + */ + +/* RANGE x CONST, U64 initial range */ +void test_reg_bounds_gen_consts_u64_u64(void) { validate_gen_range_vs_const_64(U64, U64); } +void test_reg_bounds_gen_consts_u64_s64(void) { validate_gen_range_vs_const_64(U64, S64); } +void test_reg_bounds_gen_consts_u64_u32(void) { validate_gen_range_vs_const_64(U64, U32); } +void test_reg_bounds_gen_consts_u64_s32(void) { validate_gen_range_vs_const_64(U64, S32); } +/* RANGE x CONST, S64 initial range */ +void test_reg_bounds_gen_consts_s64_u64(void) { validate_gen_range_vs_const_64(S64, U64); } +void test_reg_bounds_gen_consts_s64_s64(void) { validate_gen_range_vs_const_64(S64, S64); } +void test_reg_bounds_gen_consts_s64_u32(void) { validate_gen_range_vs_const_64(S64, U32); } +void test_reg_bounds_gen_consts_s64_s32(void) { validate_gen_range_vs_const_64(S64, S32); } +/* RANGE x CONST, U32 initial range */ +void test_reg_bounds_gen_consts_u32_u64(void) { validate_gen_range_vs_const_32(U32, U64); } +void test_reg_bounds_gen_consts_u32_s64(void) { validate_gen_range_vs_const_32(U32, S64); } +void test_reg_bounds_gen_consts_u32_u32(void) { validate_gen_range_vs_const_32(U32, U32); } +void test_reg_bounds_gen_consts_u32_s32(void) { validate_gen_range_vs_const_32(U32, S32); } +/* RANGE x CONST, S32 initial range */ +void test_reg_bounds_gen_consts_s32_u64(void) { validate_gen_range_vs_const_32(S32, U64); } +void test_reg_bounds_gen_consts_s32_s64(void) { validate_gen_range_vs_const_32(S32, S64); } +void test_reg_bounds_gen_consts_s32_u32(void) { validate_gen_range_vs_const_32(S32, U32); } +void test_reg_bounds_gen_consts_s32_s32(void) { validate_gen_range_vs_const_32(S32, S32); } + +/* A set of hard-coded "interesting" cases to validate as part of normal + * test_progs test runs + */ +static struct subtest_case crafted_cases[] = { + {U64, U64, {0, 0xffffffff}, {0, 0}}, + {U64, U64, {0, 0x80000000}, {0, 0}}, + {U64, U64, {0x100000000ULL, 0x100000100ULL}, {0, 0}}, + {U64, U64, {0x100000000ULL, 0x180000000ULL}, {0, 0}}, + {U64, U64, {0x100000000ULL, 0x1ffffff00ULL}, {0, 0}}, + {U64, U64, {0x100000000ULL, 0x1ffffff01ULL}, {0, 0}}, + {U64, U64, {0x100000000ULL, 0x1fffffffeULL}, {0, 0}}, + {U64, U64, {0x100000001ULL, 0x1000000ffULL}, {0, 0}}, + + {U64, S64, {0, 0xffffffff00000000ULL}, {0, 0}}, + {U64, S64, {0x7fffffffffffffffULL, 0xffffffff00000000ULL}, {0, 0}}, + {U64, S64, {0x7fffffff00000001ULL, 0xffffffff00000000ULL}, {0, 0}}, + {U64, S64, {0, 0xffffffffULL}, {1, 1}}, + {U64, S64, {0, 0xffffffffULL}, {0x7fffffff, 0x7fffffff}}, + + {U64, U32, {0, 0x100000000}, {0, 0}}, + {U64, U32, {0xfffffffe, 0x100000000}, {0x80000000, 0x80000000}}, + + {U64, S32, {0, 0xffffffff00000000ULL}, {0, 0}}, + /* these are tricky cases where lower 32 bits allow to tighten 64 + * bit boundaries based on tightened lower 32 bit boundaries + */ + {U64, S32, {0, 0x0ffffffffULL}, {0, 0}}, + {U64, S32, {0, 0x100000000ULL}, {0, 0}}, + {U64, S32, {0, 0x100000001ULL}, {0, 0}}, + {U64, S32, {0, 0x180000000ULL}, {0, 0}}, + {U64, S32, {0, 0x17fffffffULL}, {0, 0}}, + {U64, S32, {0, 0x180000001ULL}, {0, 0}}, + + /* verifier knows about [-1, 0] range for s32 for this case already */ + {S64, S64, {0xffffffffffffffffULL, 0}, {0xffffffff00000000ULL, 0xffffffff00000000ULL}}, + /* but didn't know about these cases initially */ + {U64, U64, {0xffffffff, 0x100000000ULL}, {0, 0}}, /* s32: [-1, 0] */ + {U64, U64, {0xffffffff, 0x100000001ULL}, {0, 0}}, /* s32: [-1, 1] */ + + /* longer convergence case: learning from u64 -> s64 -> u64 -> u32, + * arriving at u32: [1, U32_MAX] (instead of more pessimistic [0, U32_MAX]) + */ + {S64, U64, {0xffffffff00000001ULL, 0}, {0xffffffff00000000ULL, 0xffffffff00000000ULL}}, + + {U32, U32, {1, U32_MAX}, {0, 0}}, + + {U32, S32, {0, U32_MAX}, {U32_MAX, U32_MAX}}, +}; + +/* Go over crafted hard-coded cases. This is fast, so we do it as part of + * normal test_progs run. + */ +void test_reg_bounds_crafted(void) +{ + struct ctx ctx; + int i; + + memset(&ctx, 0, sizeof(ctx)); + + for (i = 0; i < ARRAY_SIZE(crafted_cases); i++) { + struct subtest_case *c = &crafted_cases[i]; + + verify_case(&ctx, c->init_t, c->cond_t, c->x, c->y); + verify_case(&ctx, c->init_t, c->cond_t, c->y, c->x); + } + + cleanup_ctx(&ctx); +} -- 2.34.1 ^ permalink raw reply related [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 07/13] selftests/bpf: BPF register range bounds tester 2023-11-03 0:08 ` [PATCH bpf-next 07/13] selftests/bpf: BPF register range bounds tester Andrii Nakryiko @ 2023-11-03 19:19 ` Alexei Starovoitov 2023-11-03 21:12 ` Andrii Nakryiko 0 siblings, 1 reply; 39+ messages in thread From: Alexei Starovoitov @ 2023-11-03 19:19 UTC (permalink / raw) To: Andrii Nakryiko Cc: bpf, Alexei Starovoitov, Daniel Borkmann, Martin KaFai Lau, Kernel Team On Thu, Nov 2, 2023 at 5:08 PM Andrii Nakryiko <andrii@kernel.org> wrote: > > +enum num_t { U64, U32, S64, S32 }; > +#define MIN_T U64 > +#define MAX_T S32 I haven't finished the review of the whole patch yet. Quick thoughts so far. Can you change above to: enum num_t { U64, first_t = U64, U32, S64, S32, last_t = S32 }; 1. min/max names kept confusing me while reading the diff. I read MIN_T is a smaller (minimal) type which is 32-bit. 2. reusing enums without LOUD macro names is easier to read. and similar with _OP macros. ^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 07/13] selftests/bpf: BPF register range bounds tester 2023-11-03 19:19 ` Alexei Starovoitov @ 2023-11-03 21:12 ` Andrii Nakryiko 0 siblings, 0 replies; 39+ messages in thread From: Andrii Nakryiko @ 2023-11-03 21:12 UTC (permalink / raw) To: Alexei Starovoitov Cc: Andrii Nakryiko, bpf, Alexei Starovoitov, Daniel Borkmann, Martin KaFai Lau, Kernel Team On Fri, Nov 3, 2023 at 12:19 PM Alexei Starovoitov <alexei.starovoitov@gmail.com> wrote: > > On Thu, Nov 2, 2023 at 5:08 PM Andrii Nakryiko <andrii@kernel.org> wrote: > > > > +enum num_t { U64, U32, S64, S32 }; > > +#define MIN_T U64 > > +#define MAX_T S32 > > I haven't finished the review of the whole patch yet. > Quick thoughts so far. > Can you change above to: > enum num_t { U64, first_t = U64, U32, S64, S32, last_t = S32 }; > > 1. min/max names kept confusing me while reading the diff. > I read MIN_T is a smaller (minimal) type which is 32-bit. > 2. reusing enums without LOUD macro names is easier to read. > > and similar with _OP macros. sure, will change ^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH bpf-next 08/13] selftests/bpf: adjust OP_EQ/OP_NE handling to use subranges for branch taken 2023-11-03 0:08 [PATCH bpf-next 00/13] BPF register bounds range vs range support Andrii Nakryiko ` (6 preceding siblings ...) 2023-11-03 0:08 ` [PATCH bpf-next 07/13] selftests/bpf: BPF register range bounds tester Andrii Nakryiko @ 2023-11-03 0:08 ` Andrii Nakryiko 2023-11-03 0:08 ` [PATCH bpf-next 09/13] selftests/bpf: add range x range test to reg_bounds Andrii Nakryiko ` (4 subsequent siblings) 12 siblings, 0 replies; 39+ messages in thread From: Andrii Nakryiko @ 2023-11-03 0:08 UTC (permalink / raw) To: bpf, ast, daniel, martin.lau; +Cc: andrii, kernel-team Similar to kernel-side BPF verifier logic enhancements, use 32-bit subrange knowledge for is_branch_taken() logic in reg_bounds selftests. Signed-off-by: Andrii Nakryiko <andrii@kernel.org> --- .../selftests/bpf/prog_tests/reg_bounds.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/reg_bounds.c b/tools/testing/selftests/bpf/prog_tests/reg_bounds.c index ac7354cfe139..330618cc12e7 100644 --- a/tools/testing/selftests/bpf/prog_tests/reg_bounds.c +++ b/tools/testing/selftests/bpf/prog_tests/reg_bounds.c @@ -750,16 +750,27 @@ static int reg_state_branch_taken_op(enum num_t t, struct reg_state *x, struct r /* OP_EQ and OP_NE are sign-agnostic */ enum num_t tu = t_unsigned(t); enum num_t ts = t_signed(t); - int br_u, br_s; + int br_u, br_s, br; br_u = range_branch_taken_op(tu, x->r[tu], y->r[tu], op); br_s = range_branch_taken_op(ts, x->r[ts], y->r[ts], op); if (br_u >= 0 && br_s >= 0 && br_u != br_s) ASSERT_FALSE(true, "branch taken inconsistency!\n"); - if (br_u >= 0) - return br_u; - return br_s; + + /* if 64-bit ranges are indecisive, use 32-bit subranges to + * eliminate always/never taken branches, if possible + */ + if (br_u == -1 && (t == U64 || t == S64)) { + br = range_branch_taken_op(U32, x->r[U32], y->r[U32], op); + if (br != -1) + return br; + br = range_branch_taken_op(S32, x->r[S32], y->r[S32], op); + if (br != -1) + return br; + } + + return br_u >= 0 ? br_u : br_s; } return range_branch_taken_op(t, x->r[t], y->r[t], op); } -- 2.34.1 ^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH bpf-next 09/13] selftests/bpf: add range x range test to reg_bounds 2023-11-03 0:08 [PATCH bpf-next 00/13] BPF register bounds range vs range support Andrii Nakryiko ` (7 preceding siblings ...) 2023-11-03 0:08 ` [PATCH bpf-next 08/13] selftests/bpf: adjust OP_EQ/OP_NE handling to use subranges for branch taken Andrii Nakryiko @ 2023-11-03 0:08 ` Andrii Nakryiko 2023-11-03 0:08 ` [PATCH bpf-next 10/13] selftests/bpf: add randomized reg_bounds tests Andrii Nakryiko ` (3 subsequent siblings) 12 siblings, 0 replies; 39+ messages in thread From: Andrii Nakryiko @ 2023-11-03 0:08 UTC (permalink / raw) To: bpf, ast, daniel, martin.lau; +Cc: andrii, kernel-team Now that verifier supports range vs range bounds adjustments, validate that by checking each generated range against every other generated range, across all supported operators (everything by JSET). We also add few cases that were problematic during development either for verifier or for selftest's range tracking implementation. Note that we utilize the same trick with splitting everything into multiple independent parallelizable tests, but init_t and cond_t. This brings down verification time in parallel mode from more than 8 hours down to less that 1.5 hours. 106 million cases were successfully validate for range vs range logic, in addition to about 7 million range vs const cases, added in earlier patch. Signed-off-by: Andrii Nakryiko <andrii@kernel.org> --- .../selftests/bpf/prog_tests/reg_bounds.c | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/reg_bounds.c b/tools/testing/selftests/bpf/prog_tests/reg_bounds.c index 330618cc12e7..16864c940548 100644 --- a/tools/testing/selftests/bpf/prog_tests/reg_bounds.c +++ b/tools/testing/selftests/bpf/prog_tests/reg_bounds.c @@ -1752,6 +1752,60 @@ static void validate_gen_range_vs_const_32(enum num_t init_t, enum num_t cond_t) cleanup_ctx(&ctx); } +static void validate_gen_range_vs_range(enum num_t init_t, enum num_t cond_t) +{ + struct ctx ctx; + const struct range *ranges; + int i, j, rcnt; + + memset(&ctx, 0, sizeof(ctx)); + + if (prepare_gen_tests(&ctx)) + goto cleanup; + + switch (init_t) + { + case U64: + ranges = ctx.uranges; + rcnt = ctx.range_cnt; + break; + case U32: + ranges = ctx.usubranges; + rcnt = ctx.subrange_cnt; + break; + case S64: + ranges = ctx.sranges; + rcnt = ctx.range_cnt; + break; + case S32: + ranges = ctx.ssubranges; + rcnt = ctx.subrange_cnt; + break; + default: + printf("validate_gen_range_vs_range!\n"); + exit(1); + } + + ctx.total_case_cnt = (MAX_OP - MIN_OP + 1) * (2 * rcnt * (rcnt + 1) / 2); + ctx.start_ns = get_time_ns(); + snprintf(ctx.progress_ctx, sizeof(ctx.progress_ctx), + "RANGE x RANGE, %s -> %s", + t_str(init_t), t_str(cond_t)); + + for (i = 0; i < rcnt; i++) { + for (j = i; j < rcnt; j++) { + /* (<range> x <range>) */ + if (verify_case(&ctx, init_t, cond_t, ranges[i], ranges[j])) + goto cleanup; + if (verify_case(&ctx, init_t, cond_t, ranges[j], ranges[i])) + goto cleanup; + } + } + +cleanup: + cleanup_ctx(&ctx); +} + /* Go over thousands of test cases generated from initial seed values. * Given this take a long time, guard this begind SLOW_TESTS=1 envvar. If * envvar is not set, this test is skipped during test_progs testing. @@ -1782,6 +1836,27 @@ void test_reg_bounds_gen_consts_s32_s64(void) { validate_gen_range_vs_const_32(S void test_reg_bounds_gen_consts_s32_u32(void) { validate_gen_range_vs_const_32(S32, U32); } void test_reg_bounds_gen_consts_s32_s32(void) { validate_gen_range_vs_const_32(S32, S32); } +/* RANGE x RANGE, U64 initial range */ +void test_reg_bounds_gen_ranges_u64_u64(void) { validate_gen_range_vs_range(U64, U64); } +void test_reg_bounds_gen_ranges_u64_s64(void) { validate_gen_range_vs_range(U64, S64); } +void test_reg_bounds_gen_ranges_u64_u32(void) { validate_gen_range_vs_range(U64, U32); } +void test_reg_bounds_gen_ranges_u64_s32(void) { validate_gen_range_vs_range(U64, S32); } +/* RANGE x RANGE, S64 initial range */ +void test_reg_bounds_gen_ranges_s64_u64(void) { validate_gen_range_vs_range(S64, U64); } +void test_reg_bounds_gen_ranges_s64_s64(void) { validate_gen_range_vs_range(S64, S64); } +void test_reg_bounds_gen_ranges_s64_u32(void) { validate_gen_range_vs_range(S64, U32); } +void test_reg_bounds_gen_ranges_s64_s32(void) { validate_gen_range_vs_range(S64, S32); } +/* RANGE x RANGE, U32 initial range */ +void test_reg_bounds_gen_ranges_u32_u64(void) { validate_gen_range_vs_range(U32, U64); } +void test_reg_bounds_gen_ranges_u32_s64(void) { validate_gen_range_vs_range(U32, S64); } +void test_reg_bounds_gen_ranges_u32_u32(void) { validate_gen_range_vs_range(U32, U32); } +void test_reg_bounds_gen_ranges_u32_s32(void) { validate_gen_range_vs_range(U32, S32); } +/* RANGE x RANGE, S32 initial range */ +void test_reg_bounds_gen_ranges_s32_u64(void) { validate_gen_range_vs_range(S32, U64); } +void test_reg_bounds_gen_ranges_s32_s64(void) { validate_gen_range_vs_range(S32, S64); } +void test_reg_bounds_gen_ranges_s32_u32(void) { validate_gen_range_vs_range(S32, U32); } +void test_reg_bounds_gen_ranges_s32_s32(void) { validate_gen_range_vs_range(S32, S32); } + /* A set of hard-coded "interesting" cases to validate as part of normal * test_progs test runs */ @@ -1795,6 +1870,12 @@ static struct subtest_case crafted_cases[] = { {U64, U64, {0x100000000ULL, 0x1fffffffeULL}, {0, 0}}, {U64, U64, {0x100000001ULL, 0x1000000ffULL}, {0, 0}}, + /* single point overlap, interesting BPF_EQ and BPF_NE interactions */ + {U64, U64, {0, 1}, {1, 0x80000000}}, + {U64, S64, {0, 1}, {1, 0x80000000}}, + {U64, U32, {0, 1}, {1, 0x80000000}}, + {U64, S32, {0, 1}, {1, 0x80000000}}, + {U64, S64, {0, 0xffffffff00000000ULL}, {0, 0}}, {U64, S64, {0x7fffffffffffffffULL, 0xffffffff00000000ULL}, {0, 0}}, {U64, S64, {0x7fffffff00000001ULL, 0xffffffff00000000ULL}, {0, 0}}, @@ -1829,6 +1910,11 @@ static struct subtest_case crafted_cases[] = { {U32, U32, {1, U32_MAX}, {0, 0}}, {U32, S32, {0, U32_MAX}, {U32_MAX, U32_MAX}}, + + {S32, U64, {(u32)(s32)S32_MIN, (u32)(s32)S32_MIN}, {(u32)(s32)-255, 0}}, + {S32, S64, {(u32)(s32)S32_MIN, (u32)(s32)-255}, {(u32)(s32)-2, 0}}, + {S32, S64, {0, 1}, {(u32)(s32)S32_MIN, (u32)(s32)S32_MIN}}, + {S32, U32, {(u32)(s32)S32_MIN, (u32)(s32)S32_MIN}, {(u32)(s32)S32_MIN, (u32)(s32)S32_MIN}}, }; /* Go over crafted hard-coded cases. This is fast, so we do it as part of -- 2.34.1 ^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH bpf-next 10/13] selftests/bpf: add randomized reg_bounds tests 2023-11-03 0:08 [PATCH bpf-next 00/13] BPF register bounds range vs range support Andrii Nakryiko ` (8 preceding siblings ...) 2023-11-03 0:08 ` [PATCH bpf-next 09/13] selftests/bpf: add range x range test to reg_bounds Andrii Nakryiko @ 2023-11-03 0:08 ` Andrii Nakryiko 2023-11-03 0:08 ` [PATCH bpf-next 11/13] selftests/bpf: set BPF_F_TEST_SANITY_SCRIPT by default Andrii Nakryiko ` (2 subsequent siblings) 12 siblings, 0 replies; 39+ messages in thread From: Andrii Nakryiko @ 2023-11-03 0:08 UTC (permalink / raw) To: bpf, ast, daniel, martin.lau; +Cc: andrii, kernel-team Add random cases generation to reg_bounds.c and run them without SLOW_TESTS=1 to increase a chance of BPF CI catching latent issues. Suggested-by: Alexei Starovoitov <ast@kernel.org> Signed-off-by: Andrii Nakryiko <andrii@kernel.org> --- .../selftests/bpf/prog_tests/reg_bounds.c | 164 +++++++++++++++++- 1 file changed, 158 insertions(+), 6 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/reg_bounds.c b/tools/testing/selftests/bpf/prog_tests/reg_bounds.c index 16864c940548..fd6401dec0b7 100644 --- a/tools/testing/selftests/bpf/prog_tests/reg_bounds.c +++ b/tools/testing/selftests/bpf/prog_tests/reg_bounds.c @@ -83,6 +83,17 @@ static __always_inline u64 max_t(enum num_t t, u64 x, u64 y) } } +static __always_inline u64 cast_t(enum num_t t, u64 x) +{ + switch (t) { + case U64: return (u64)x; + case U32: return (u32)x; + case S64: return (s64)x; + case S32: return (u32)(s32)x; + default: printf("cast_t!\n"); exit(1); + } +} + static const char *t_str(enum num_t t) { switch (t) { @@ -1304,8 +1315,10 @@ struct ctx { struct range *usubranges, *ssubranges; int max_failure_cnt, cur_failure_cnt; int total_case_cnt, case_cnt; + int rand_case_cnt; + unsigned rand_seed; __u64 start_ns; - char progress_ctx[32]; + char progress_ctx[64]; }; static void cleanup_ctx(struct ctx *ctx) @@ -1636,11 +1649,6 @@ static int parse_env_vars(struct ctx *ctx) { const char *s; - if (!(s = getenv("SLOW_TESTS")) || strcmp(s, "1") != 0) { - test__skip(); - return -ENOTSUP; - } - if ((s = getenv("REG_BOUNDS_MAX_FAILURE_CNT"))) { errno = 0; ctx->max_failure_cnt = strtol(s, NULL, 10); @@ -1650,13 +1658,37 @@ static int parse_env_vars(struct ctx *ctx) } } + if ((s = getenv("REG_BOUNDS_RAND_CASE_CNT"))) { + errno = 0; + ctx->rand_case_cnt = strtol(s, NULL, 10); + if (errno || ctx->rand_case_cnt < 0) { + ASSERT_OK(-errno, "REG_BOUNDS_RAND_CASE_CNT"); + return -EINVAL; + } + } + + if ((s = getenv("REG_BOUNDS_RAND_SEED"))) { + errno = 0; + ctx->rand_seed = strtoul(s, NULL, 10); + if (errno) { + ASSERT_OK(-errno, "REG_BOUNDS_RAND_SEED"); + return -EINVAL; + } + } + return 0; } static int prepare_gen_tests(struct ctx *ctx) { + const char *s; int err; + if (!(s = getenv("SLOW_TESTS")) || strcmp(s, "1") != 0) { + test__skip(); + return -ENOTSUP; + } + err = parse_env_vars(ctx); if (err) return err; @@ -1857,6 +1889,126 @@ void test_reg_bounds_gen_ranges_s32_s64(void) { validate_gen_range_vs_range(S32, void test_reg_bounds_gen_ranges_s32_u32(void) { validate_gen_range_vs_range(S32, U32); } void test_reg_bounds_gen_ranges_s32_s32(void) { validate_gen_range_vs_range(S32, S32); } +#define DEFAULT_RAND_CASE_CNT 25 + +#define RAND_21BIT_MASK ((1 << 22) - 1) + +static u64 rand_u64() +{ + /* RAND_MAX is guaranteed to be at least 1<<15, but in practice it + * seems to be 1<<31, so we need to call it thrice to get full u64; + * we'll use rougly equal split: 22 + 21 + 21 bits + */ + return ((u64)random() << 42) | + (((u64)random() & RAND_21BIT_MASK) << 21) | + (random() & RAND_21BIT_MASK); +} + +static u64 rand_const(enum num_t t) +{ + return cast_t(t, rand_u64()); +} + +static struct range rand_range(enum num_t t) +{ + u64 x = rand_const(t), y = rand_const(t); + + return range(t, min_t(t, x, y), max_t(t, x, y)); +} + +static void validate_rand_ranges(enum num_t init_t, enum num_t cond_t, bool const_range) +{ + struct ctx ctx; + struct range range1, range2; + int err, i; + u64 t; + + memset(&ctx, 0, sizeof(ctx)); + + err = parse_env_vars(&ctx); + if (err) { + ASSERT_OK(err, "parse_env_vars"); + return; + } + + if (ctx.rand_case_cnt == 0) + ctx.rand_case_cnt = DEFAULT_RAND_CASE_CNT; + if (ctx.rand_seed == 0) + ctx.rand_seed = (unsigned)get_time_ns(); + + srandom(ctx.rand_seed); + + ctx.total_case_cnt = (MAX_OP - MIN_OP + 1) * (2 * ctx.rand_case_cnt); + ctx.start_ns = get_time_ns(); + snprintf(ctx.progress_ctx, sizeof(ctx.progress_ctx), + "[RANDOM SEED %u] RANGE x %s, %s -> %s", + ctx.rand_seed, const_range ? "CONST" : "RANGE", + t_str(init_t), t_str(cond_t)); + fprintf(env.stdout, "%s\n", ctx.progress_ctx); + + for (i = 0; i < ctx.rand_case_cnt; i++) { + range1 = rand_range(init_t); + if (const_range) { + t = rand_const(init_t); + range2 = range(init_t, t, t); + } else { + range2 = rand_range(init_t); + } + + /* <range1> x <range2> */ + if (verify_case(&ctx, init_t, cond_t, range1, range2)) + goto cleanup; + /* <range2> x <range1> */ + if (verify_case(&ctx, init_t, cond_t, range2, range1)) + goto cleanup; + } + +cleanup: + cleanup_ctx(&ctx); +} + +/* [RANDOM] RANGE x CONST, U64 initial range */ +void test_reg_bounds_rand_consts_u64_u64(void) { validate_rand_ranges(U64, U64, true /* const */); } +void test_reg_bounds_rand_consts_u64_s64(void) { validate_rand_ranges(U64, S64, true /* const */); } +void test_reg_bounds_rand_consts_u64_u32(void) { validate_rand_ranges(U64, U32, true /* const */); } +void test_reg_bounds_rand_consts_u64_s32(void) { validate_rand_ranges(U64, S32, true /* const */); } +/* [RANDOM] RANGE x CONST, S64 initial range */ +void test_reg_bounds_rand_consts_s64_u64(void) { validate_rand_ranges(S64, U64, true /* const */); } +void test_reg_bounds_rand_consts_s64_s64(void) { validate_rand_ranges(S64, S64, true /* const */); } +void test_reg_bounds_rand_consts_s64_u32(void) { validate_rand_ranges(S64, U32, true /* const */); } +void test_reg_bounds_rand_consts_s64_s32(void) { validate_rand_ranges(S64, S32, true /* const */); } +/* [RANDOM] RANGE x CONST, U32 initial range */ +void test_reg_bounds_rand_consts_u32_u64(void) { validate_rand_ranges(U32, U64, true /* const */); } +void test_reg_bounds_rand_consts_u32_s64(void) { validate_rand_ranges(U32, S64, true /* const */); } +void test_reg_bounds_rand_consts_u32_u32(void) { validate_rand_ranges(U32, U32, true /* const */); } +void test_reg_bounds_rand_consts_u32_s32(void) { validate_rand_ranges(U32, S32, true /* const */); } +/* [RANDOM] RANGE x CONST, S32 initial range */ +void test_reg_bounds_rand_consts_s32_u64(void) { validate_rand_ranges(S32, U64, true /* const */); } +void test_reg_bounds_rand_consts_s32_s64(void) { validate_rand_ranges(S32, S64, true /* const */); } +void test_reg_bounds_rand_consts_s32_u32(void) { validate_rand_ranges(S32, U32, true /* const */); } +void test_reg_bounds_rand_consts_s32_s32(void) { validate_rand_ranges(S32, S32, true /* const */); } + +/* [RANDOM] RANGE x RANGE, U64 initial range */ +void test_reg_bounds_rand_ranges_u64_u64(void) { validate_rand_ranges(U64, U64, false /* range */); } +void test_reg_bounds_rand_ranges_u64_s64(void) { validate_rand_ranges(U64, S64, false /* range */); } +void test_reg_bounds_rand_ranges_u64_u32(void) { validate_rand_ranges(U64, U32, false /* range */); } +void test_reg_bounds_rand_ranges_u64_s32(void) { validate_rand_ranges(U64, S32, false /* range */); } +/* [RANDOM] RANGE x RANGE, S64 initial range */ +void test_reg_bounds_rand_ranges_s64_u64(void) { validate_rand_ranges(S64, U64, false /* range */); } +void test_reg_bounds_rand_ranges_s64_s64(void) { validate_rand_ranges(S64, S64, false /* range */); } +void test_reg_bounds_rand_ranges_s64_u32(void) { validate_rand_ranges(S64, U32, false /* range */); } +void test_reg_bounds_rand_ranges_s64_s32(void) { validate_rand_ranges(S64, S32, false /* range */); } +/* [RANDOM] RANGE x RANGE, U32 initial range */ +void test_reg_bounds_rand_ranges_u32_u64(void) { validate_rand_ranges(U32, U64, false /* range */); } +void test_reg_bounds_rand_ranges_u32_s64(void) { validate_rand_ranges(U32, S64, false /* range */); } +void test_reg_bounds_rand_ranges_u32_u32(void) { validate_rand_ranges(U32, U32, false /* range */); } +void test_reg_bounds_rand_ranges_u32_s32(void) { validate_rand_ranges(U32, S32, false /* range */); } +/* [RANDOM] RANGE x RANGE, S32 initial range */ +void test_reg_bounds_rand_ranges_s32_u64(void) { validate_rand_ranges(S32, U64, false /* range */); } +void test_reg_bounds_rand_ranges_s32_s64(void) { validate_rand_ranges(S32, S64, false /* range */); } +void test_reg_bounds_rand_ranges_s32_u32(void) { validate_rand_ranges(S32, U32, false /* range */); } +void test_reg_bounds_rand_ranges_s32_s32(void) { validate_rand_ranges(S32, S32, false /* range */); } + /* A set of hard-coded "interesting" cases to validate as part of normal * test_progs test runs */ -- 2.34.1 ^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH bpf-next 11/13] selftests/bpf: set BPF_F_TEST_SANITY_SCRIPT by default 2023-11-03 0:08 [PATCH bpf-next 00/13] BPF register bounds range vs range support Andrii Nakryiko ` (9 preceding siblings ...) 2023-11-03 0:08 ` [PATCH bpf-next 10/13] selftests/bpf: add randomized reg_bounds tests Andrii Nakryiko @ 2023-11-03 0:08 ` Andrii Nakryiko 2023-11-03 22:35 ` Eduard Zingerman 2023-11-03 0:08 ` [PATCH bpf-next 12/13] veristat: add ability to set BPF_F_TEST_SANITY_STRICT flag with -r flag Andrii Nakryiko 2023-11-03 0:08 ` [PATCH bpf-next 13/13] selftests/bpf: add iter test requiring range x range logic Andrii Nakryiko 12 siblings, 1 reply; 39+ messages in thread From: Andrii Nakryiko @ 2023-11-03 0:08 UTC (permalink / raw) To: bpf, ast, daniel, martin.lau; +Cc: andrii, kernel-team Make sure to set BPF_F_TEST_SANITY_STRICT program flag by default across most verifier tests (and a bunch of others that set custom prog flags). There are currently two tests that do fail validation, if enforced strictly: verifier_bounds/crossing_64_bit_signed_boundary_2 and verifier_bounds/crossing_32_bit_signed_boundary_2. To accommodate them, we teach test_loader a flag negation: __flag(!<flagname>) will *clear* specified flag, allowing easy opt-out. We apply __flag(!BPF_F_TEST_SANITY_STRICT) to these to tests. Also sprinkle BPF_F_TEST_SANITY_STRICT everywhere where we already set test-only BPF_F_TEST_RND_HI32 flag, for completeness. Signed-off-by: Andrii Nakryiko <andrii@kernel.org> --- .../bpf/prog_tests/bpf_verif_scale.c | 2 +- .../selftests/bpf/progs/verifier_bounds.c | 2 ++ tools/testing/selftests/bpf/test_loader.c | 35 ++++++++++++++----- tools/testing/selftests/bpf/test_sock_addr.c | 1 + tools/testing/selftests/bpf/test_verifier.c | 2 +- tools/testing/selftests/bpf/testing_helpers.c | 4 +-- 6 files changed, 33 insertions(+), 13 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_verif_scale.c b/tools/testing/selftests/bpf/prog_tests/bpf_verif_scale.c index 731c343897d8..3f2d70831873 100644 --- a/tools/testing/selftests/bpf/prog_tests/bpf_verif_scale.c +++ b/tools/testing/selftests/bpf/prog_tests/bpf_verif_scale.c @@ -35,7 +35,7 @@ static int check_load(const char *file, enum bpf_prog_type type) } bpf_program__set_type(prog, type); - bpf_program__set_flags(prog, BPF_F_TEST_RND_HI32); + bpf_program__set_flags(prog, BPF_F_TEST_RND_HI32 | BPF_F_TEST_SANITY_STRICT); bpf_program__set_log_level(prog, 4 | extra_prog_load_log_flags); err = bpf_object__load(obj); diff --git a/tools/testing/selftests/bpf/progs/verifier_bounds.c b/tools/testing/selftests/bpf/progs/verifier_bounds.c index c5588a14fe2e..0c1460936373 100644 --- a/tools/testing/selftests/bpf/progs/verifier_bounds.c +++ b/tools/testing/selftests/bpf/progs/verifier_bounds.c @@ -965,6 +965,7 @@ l0_%=: r0 = 0; \ SEC("xdp") __description("bound check with JMP_JSLT for crossing 64-bit signed boundary") __success __retval(0) +__flag(!BPF_F_TEST_SANITY_STRICT) /* known sanity violation */ __naked void crossing_64_bit_signed_boundary_2(void) { asm volatile (" \ @@ -1046,6 +1047,7 @@ l0_%=: r0 = 0; \ SEC("xdp") __description("bound check with JMP32_JSLT for crossing 32-bit signed boundary") __success __retval(0) +__flag(!BPF_F_TEST_SANITY_STRICT) /* known sanity violation */ __naked void crossing_32_bit_signed_boundary_2(void) { asm volatile (" \ diff --git a/tools/testing/selftests/bpf/test_loader.c b/tools/testing/selftests/bpf/test_loader.c index 37ffa57f28a1..57e27b1a73a6 100644 --- a/tools/testing/selftests/bpf/test_loader.c +++ b/tools/testing/selftests/bpf/test_loader.c @@ -153,6 +153,14 @@ static int parse_retval(const char *str, int *val, const char *name) return parse_int(str, val, name); } +static void update_flags(int *flags, int flag, bool clear) +{ + if (clear) + *flags &= ~flag; + else + *flags |= flag; +} + /* Uses btf_decl_tag attributes to describe the expected test * behavior, see bpf_misc.h for detailed description of each attribute * and attribute combinations. @@ -171,6 +179,7 @@ static int parse_test_spec(struct test_loader *tester, memset(spec, 0, sizeof(*spec)); spec->prog_name = bpf_program__name(prog); + spec->prog_flags = BPF_F_TEST_SANITY_STRICT; /* by default be strict */ btf = bpf_object__btf(obj); if (!btf) { @@ -187,7 +196,8 @@ static int parse_test_spec(struct test_loader *tester, for (i = 1; i < btf__type_cnt(btf); i++) { const char *s, *val, *msg; const struct btf_type *t; - int tmp; + bool clear; + int flags; t = btf__type_by_id(btf, i); if (!btf_is_decl_tag(t)) @@ -253,23 +263,30 @@ static int parse_test_spec(struct test_loader *tester, goto cleanup; } else if (str_has_pfx(s, TEST_TAG_PROG_FLAGS_PFX)) { val = s + sizeof(TEST_TAG_PROG_FLAGS_PFX) - 1; + + clear = val[0] == '!'; + if (clear) + val++; + if (strcmp(val, "BPF_F_STRICT_ALIGNMENT") == 0) { - spec->prog_flags |= BPF_F_STRICT_ALIGNMENT; + update_flags(&spec->prog_flags, BPF_F_STRICT_ALIGNMENT, clear); } else if (strcmp(val, "BPF_F_ANY_ALIGNMENT") == 0) { - spec->prog_flags |= BPF_F_ANY_ALIGNMENT; + update_flags(&spec->prog_flags, BPF_F_ANY_ALIGNMENT, clear); } else if (strcmp(val, "BPF_F_TEST_RND_HI32") == 0) { - spec->prog_flags |= BPF_F_TEST_RND_HI32; + update_flags(&spec->prog_flags, BPF_F_TEST_RND_HI32, clear); } else if (strcmp(val, "BPF_F_TEST_STATE_FREQ") == 0) { - spec->prog_flags |= BPF_F_TEST_STATE_FREQ; + update_flags(&spec->prog_flags, BPF_F_TEST_STATE_FREQ, clear); } else if (strcmp(val, "BPF_F_SLEEPABLE") == 0) { - spec->prog_flags |= BPF_F_SLEEPABLE; + update_flags(&spec->prog_flags, BPF_F_SLEEPABLE, clear); } else if (strcmp(val, "BPF_F_XDP_HAS_FRAGS") == 0) { - spec->prog_flags |= BPF_F_XDP_HAS_FRAGS; + update_flags(&spec->prog_flags, BPF_F_XDP_HAS_FRAGS, clear); + } else if (strcmp(val, "BPF_F_TEST_SANITY_STRICT") == 0) { + update_flags(&spec->prog_flags, BPF_F_TEST_SANITY_STRICT, clear); } else /* assume numeric value */ { - err = parse_int(val, &tmp, "test prog flags"); + err = parse_int(val, &flags, "test prog flags"); if (err) goto cleanup; - spec->prog_flags |= tmp; + update_flags(&spec->prog_flags, flags, clear); } } } diff --git a/tools/testing/selftests/bpf/test_sock_addr.c b/tools/testing/selftests/bpf/test_sock_addr.c index 2c89674fc62c..878c077e0fa7 100644 --- a/tools/testing/selftests/bpf/test_sock_addr.c +++ b/tools/testing/selftests/bpf/test_sock_addr.c @@ -680,6 +680,7 @@ static int load_path(const struct sock_addr_test *test, const char *path) bpf_program__set_type(prog, BPF_PROG_TYPE_CGROUP_SOCK_ADDR); bpf_program__set_expected_attach_type(prog, test->expected_attach_type); bpf_program__set_flags(prog, BPF_F_TEST_RND_HI32); + bpf_program__set_flags(prog, BPF_F_TEST_SANITY_STRICT); err = bpf_object__load(obj); if (err) { diff --git a/tools/testing/selftests/bpf/test_verifier.c b/tools/testing/selftests/bpf/test_verifier.c index 98107e0452d3..4992022f3137 100644 --- a/tools/testing/selftests/bpf/test_verifier.c +++ b/tools/testing/selftests/bpf/test_verifier.c @@ -1588,7 +1588,7 @@ static void do_test_single(struct bpf_test *test, bool unpriv, if (fixup_skips != skips) return; - pflags = BPF_F_TEST_RND_HI32; + pflags = BPF_F_TEST_RND_HI32 | BPF_F_TEST_SANITY_STRICT; if (test->flags & F_LOAD_WITH_STRICT_ALIGNMENT) pflags |= BPF_F_STRICT_ALIGNMENT; if (test->flags & F_NEEDS_EFFICIENT_UNALIGNED_ACCESS) diff --git a/tools/testing/selftests/bpf/testing_helpers.c b/tools/testing/selftests/bpf/testing_helpers.c index 8d994884c7b4..9786a94a666c 100644 --- a/tools/testing/selftests/bpf/testing_helpers.c +++ b/tools/testing/selftests/bpf/testing_helpers.c @@ -276,7 +276,7 @@ int bpf_prog_test_load(const char *file, enum bpf_prog_type type, if (type != BPF_PROG_TYPE_UNSPEC && bpf_program__type(prog) != type) bpf_program__set_type(prog, type); - flags = bpf_program__flags(prog) | BPF_F_TEST_RND_HI32; + flags = bpf_program__flags(prog) | BPF_F_TEST_RND_HI32 | BPF_F_TEST_SANITY_STRICT; bpf_program__set_flags(prog, flags); err = bpf_object__load(obj); @@ -299,7 +299,7 @@ int bpf_test_load_program(enum bpf_prog_type type, const struct bpf_insn *insns, { LIBBPF_OPTS(bpf_prog_load_opts, opts, .kern_version = kern_version, - .prog_flags = BPF_F_TEST_RND_HI32, + .prog_flags = BPF_F_TEST_RND_HI32 | BPF_F_TEST_SANITY_STRICT, .log_level = extra_prog_load_log_flags, .log_buf = log_buf, .log_size = log_buf_sz, -- 2.34.1 ^ permalink raw reply related [flat|nested] 39+ messages in thread
* Re: [PATCH bpf-next 11/13] selftests/bpf: set BPF_F_TEST_SANITY_SCRIPT by default 2023-11-03 0:08 ` [PATCH bpf-next 11/13] selftests/bpf: set BPF_F_TEST_SANITY_SCRIPT by default Andrii Nakryiko @ 2023-11-03 22:35 ` Eduard Zingerman 0 siblings, 0 replies; 39+ messages in thread From: Eduard Zingerman @ 2023-11-03 22:35 UTC (permalink / raw) To: Andrii Nakryiko, bpf, ast, daniel, martin.lau; +Cc: kernel-team On Thu, 2023-11-02 at 17:08 -0700, Andrii Nakryiko wrote: > Make sure to set BPF_F_TEST_SANITY_STRICT program flag by default across > most verifier tests (and a bunch of others that set custom prog flags). > > There are currently two tests that do fail validation, if enforced > strictly: verifier_bounds/crossing_64_bit_signed_boundary_2 and > verifier_bounds/crossing_32_bit_signed_boundary_2. To accommodate them, > we teach test_loader a flag negation: > > __flag(!<flagname>) will *clear* specified flag, allowing easy opt-out. > > We apply __flag(!BPF_F_TEST_SANITY_STRICT) to these to tests. > > Also sprinkle BPF_F_TEST_SANITY_STRICT everywhere where we already set > test-only BPF_F_TEST_RND_HI32 flag, for completeness. > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org> Acked-by: Eduard Zingerman <eddyz87@gmail.com> [...] ^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH bpf-next 12/13] veristat: add ability to set BPF_F_TEST_SANITY_STRICT flag with -r flag 2023-11-03 0:08 [PATCH bpf-next 00/13] BPF register bounds range vs range support Andrii Nakryiko ` (10 preceding siblings ...) 2023-11-03 0:08 ` [PATCH bpf-next 11/13] selftests/bpf: set BPF_F_TEST_SANITY_SCRIPT by default Andrii Nakryiko @ 2023-11-03 0:08 ` Andrii Nakryiko 2023-11-03 0:08 ` [PATCH bpf-next 13/13] selftests/bpf: add iter test requiring range x range logic Andrii Nakryiko 12 siblings, 0 replies; 39+ messages in thread From: Andrii Nakryiko @ 2023-11-03 0:08 UTC (permalink / raw) To: bpf, ast, daniel, martin.lau; +Cc: andrii, kernel-team Add a new flag -r (--test-sanity), similar to -t (--test-states), to add extra BPF program flags when loading BPF programs. This allows to use veristat to easily catch sanity violations in production BPF programs. reg_bounds tests are also enforcing BPF_F_TEST_SANITY_STRICT flag now. Signed-off-by: Andrii Nakryiko <andrii@kernel.org> --- tools/testing/selftests/bpf/prog_tests/reg_bounds.c | 1 + tools/testing/selftests/bpf/veristat.c | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/reg_bounds.c b/tools/testing/selftests/bpf/prog_tests/reg_bounds.c index fd6401dec0b7..f980a2555fc7 100644 --- a/tools/testing/selftests/bpf/prog_tests/reg_bounds.c +++ b/tools/testing/selftests/bpf/prog_tests/reg_bounds.c @@ -829,6 +829,7 @@ static int load_range_cmp_prog(struct range x, struct range y, enum op op, .log_level = 2, .log_buf = log_buf, .log_size = log_sz, + .prog_flags = BPF_F_TEST_SANITY_STRICT, ); /* ; skip exit block below diff --git a/tools/testing/selftests/bpf/veristat.c b/tools/testing/selftests/bpf/veristat.c index 655095810d4a..159e6a97b65a 100644 --- a/tools/testing/selftests/bpf/veristat.c +++ b/tools/testing/selftests/bpf/veristat.c @@ -142,6 +142,7 @@ static struct env { bool debug; bool quiet; bool force_checkpoints; + bool strict_range_sanity; enum resfmt out_fmt; bool show_version; bool comparison_mode; @@ -210,8 +211,6 @@ static const struct argp_option opts[] = { { "log-level", 'l', "LEVEL", 0, "Verifier log level (default 0 for normal mode, 1 for verbose mode)" }, { "log-fixed", OPT_LOG_FIXED, NULL, 0, "Disable verifier log rotation" }, { "log-size", OPT_LOG_SIZE, "BYTES", 0, "Customize verifier log size (default to 16MB)" }, - { "test-states", 't', NULL, 0, - "Force frequent BPF verifier state checkpointing (set BPF_F_TEST_STATE_FREQ program flag)" }, { "quiet", 'q', NULL, 0, "Quiet mode" }, { "emit", 'e', "SPEC", 0, "Specify stats to be emitted" }, { "sort", 's', "SPEC", 0, "Specify sort order" }, @@ -219,6 +218,10 @@ static const struct argp_option opts[] = { { "compare", 'C', NULL, 0, "Comparison mode" }, { "replay", 'R', NULL, 0, "Replay mode" }, { "filter", 'f', "FILTER", 0, "Filter expressions (or @filename for file with expressions)." }, + { "test-states", 't', NULL, 0, + "Force frequent BPF verifier state checkpointing (set BPF_F_TEST_STATE_FREQ program flag)" }, + { "test-sanity", 'r', NULL, 0, + "Force strict BPF verifier register sanity behavior (BPF_F_TEST_SANITY_STRICT program flag)" }, {}, }; @@ -290,6 +293,9 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state) case 't': env.force_checkpoints = true; break; + case 'r': + env.strict_range_sanity = true; + break; case 'C': env.comparison_mode = true; break; @@ -997,6 +1003,8 @@ static int process_prog(const char *filename, struct bpf_object *obj, struct bpf if (env.force_checkpoints) bpf_program__set_flags(prog, bpf_program__flags(prog) | BPF_F_TEST_STATE_FREQ); + if (env.strict_range_sanity) + bpf_program__set_flags(prog, bpf_program__flags(prog) | BPF_F_TEST_SANITY_STRICT); err = bpf_object__load(obj); env.progs_processed++; -- 2.34.1 ^ permalink raw reply related [flat|nested] 39+ messages in thread
* [PATCH bpf-next 13/13] selftests/bpf: add iter test requiring range x range logic 2023-11-03 0:08 [PATCH bpf-next 00/13] BPF register bounds range vs range support Andrii Nakryiko ` (11 preceding siblings ...) 2023-11-03 0:08 ` [PATCH bpf-next 12/13] veristat: add ability to set BPF_F_TEST_SANITY_STRICT flag with -r flag Andrii Nakryiko @ 2023-11-03 0:08 ` Andrii Nakryiko 12 siblings, 0 replies; 39+ messages in thread From: Andrii Nakryiko @ 2023-11-03 0:08 UTC (permalink / raw) To: bpf, ast, daniel, martin.lau; +Cc: andrii, kernel-team Add a simple verifier test that requires deriving reg bounds for one register from another register that's not a constant. This is a realistic example of iterating elements of an array with fixed maximum number of elements, but smaller actual number of elements. This small example was an original motivation for doing this whole patch set in the first place, yes. Signed-off-by: Andrii Nakryiko <andrii@kernel.org> --- tools/testing/selftests/bpf/progs/iters.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/iters.c b/tools/testing/selftests/bpf/progs/iters.c index c20c4e38b71c..b2181f850d3e 100644 --- a/tools/testing/selftests/bpf/progs/iters.c +++ b/tools/testing/selftests/bpf/progs/iters.c @@ -1411,4 +1411,26 @@ __naked int checkpoint_states_deletion(void) ); } +struct { + int data[32]; + int n; +} loop_data; + +SEC("raw_tp") +__success +int iter_arr_with_actual_elem_count(const void *ctx) +{ + int i, n = loop_data.n, sum = 0; + + if (n > ARRAY_SIZE(loop_data.data)) + return 0; + + bpf_for(i, 0, n) { + /* no rechecking of i against ARRAY_SIZE(loop_data.n) */ + sum += loop_data.data[i]; + } + + return sum; +} + char _license[] SEC("license") = "GPL"; -- 2.34.1 ^ permalink raw reply related [flat|nested] 39+ messages in thread
end of thread, other threads:[~2023-11-09 9:03 UTC | newest]
Thread overview: 39+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-11-03 0:08 [PATCH bpf-next 00/13] BPF register bounds range vs range support Andrii Nakryiko
2023-11-03 0:08 ` [PATCH bpf-next 01/13] bpf: generalize reg_set_min_max() to handle non-const register comparisons Andrii Nakryiko
2023-11-03 7:52 ` Shung-Hsi Yu
2023-11-03 8:33 ` Shung-Hsi Yu
2023-11-03 20:39 ` Andrii Nakryiko
2023-11-03 20:48 ` Andrii Nakryiko
2023-11-06 2:22 ` Shung-Hsi Yu
2023-11-03 16:20 ` Eduard Zingerman
2023-11-03 20:39 ` Andrii Nakryiko
2023-11-03 0:08 ` [PATCH bpf-next 02/13] bpf: generalize is_scalar_branch_taken() logic Andrii Nakryiko
2023-11-03 0:13 ` Andrii Nakryiko
2023-11-03 16:47 ` Eduard Zingerman
2023-11-03 20:59 ` Andrii Nakryiko
2023-11-03 21:02 ` Andrii Nakryiko
2023-11-03 0:08 ` [PATCH bpf-next 03/13] bpf: enhance BPF_JEQ/BPF_JNE is_branch_taken logic Andrii Nakryiko
2023-11-03 17:28 ` Eduard Zingerman
2023-11-09 8:39 ` Shung-Hsi Yu
2023-11-03 0:08 ` [PATCH bpf-next 04/13] bpf: add register bounds sanity checks and sanitization Andrii Nakryiko
2023-11-03 2:13 ` Andrii Nakryiko
2023-11-03 17:56 ` Eduard Zingerman
2023-11-03 21:11 ` Andrii Nakryiko
2023-11-03 21:39 ` Eduard Zingerman
2023-11-09 8:30 ` Shung-Hsi Yu
2023-11-03 0:08 ` [PATCH bpf-next 05/13] bpf: remove redundant s{32,64} -> u{32,64} deduction logic Andrii Nakryiko
2023-11-03 22:16 ` Eduard Zingerman
2023-11-09 8:43 ` Shung-Hsi Yu
2023-11-03 0:08 ` [PATCH bpf-next 06/13] bpf: make __reg{32,64}_deduce_bounds logic more robust Andrii Nakryiko
2023-11-03 22:27 ` Eduard Zingerman
2023-11-09 9:02 ` Shung-Hsi Yu
2023-11-03 0:08 ` [PATCH bpf-next 07/13] selftests/bpf: BPF register range bounds tester Andrii Nakryiko
2023-11-03 19:19 ` Alexei Starovoitov
2023-11-03 21:12 ` Andrii Nakryiko
2023-11-03 0:08 ` [PATCH bpf-next 08/13] selftests/bpf: adjust OP_EQ/OP_NE handling to use subranges for branch taken Andrii Nakryiko
2023-11-03 0:08 ` [PATCH bpf-next 09/13] selftests/bpf: add range x range test to reg_bounds Andrii Nakryiko
2023-11-03 0:08 ` [PATCH bpf-next 10/13] selftests/bpf: add randomized reg_bounds tests Andrii Nakryiko
2023-11-03 0:08 ` [PATCH bpf-next 11/13] selftests/bpf: set BPF_F_TEST_SANITY_SCRIPT by default Andrii Nakryiko
2023-11-03 22:35 ` Eduard Zingerman
2023-11-03 0:08 ` [PATCH bpf-next 12/13] veristat: add ability to set BPF_F_TEST_SANITY_STRICT flag with -r flag Andrii Nakryiko
2023-11-03 0:08 ` [PATCH bpf-next 13/13] selftests/bpf: add iter test requiring range x range logic Andrii Nakryiko
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox