diff options
| author | Eduard Zingerman <eddyz87@gmail.com> | 2026-10-09 03:48:05 -0700 |
|---|---|---|
| committer | Alexei Starovoitov <ast@kernel.org> | 2026-10-09 20:29:15 +0000 |
| commit | 8c5f6dfefdf17f42979e2ca0fe13f80dd80b34df (patch) | |
| tree | 1d8b6e48a9887a22035df3adf83aaaee8bf2d8b8 /tools/testing | |
| parent | 2db5837f9adafbf7bd68aac4666400296b3bb8c7 (diff) | |
| download | linux-next-8c5f6dfefdf17f42979e2ca0fe13f80dd80b34df.tar.gz linux-next-8c5f6dfefdf17f42979e2ca0fe13f80dd80b34df.zip | |
selftests/bpf: Tests for register base/step state pruning
Tests for the base/step reasoning in states.c:range_within():
- step_prune_hit_multiple: prune a step-4 range contained in a cached
step-2 range when the scalar is used to address the stack.
- step_prune_miss_non_multiple: do not let a cached step-2 range hide a
step-3 path reaching division by zero.
- step_prune_hit_const_on_line: prune constant 6 against a cached
base-0, step-3 range.
- step_prune_miss_const_off_line: do not prune constant 7 against that
range even though its bounds and tnum admit the value.
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://patch.msgid.link/20261009-scev-minimal-rebase-v3-47-bcba9bc53ac9@gmail.com
Diffstat (limited to 'tools/testing')
| -rw-r--r-- | tools/testing/selftests/bpf/progs/verifier_bounds_step.c | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/tools/testing/selftests/bpf/progs/verifier_bounds_step.c b/tools/testing/selftests/bpf/progs/verifier_bounds_step.c index 3a0d22aa4fb8..c9ee1c7efcbb 100644 --- a/tools/testing/selftests/bpf/progs/verifier_bounds_step.c +++ b/tools/testing/selftests/bpf/progs/verifier_bounds_step.c @@ -360,4 +360,185 @@ __naked void step_movsx_bounds_on_line(void) : __clobber_all); } +struct step_val { + __u8 data[1024]; +}; + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 1); + __type(key, __u32); + __type(value, struct step_val); +} step_map SEC(".maps"); + +/* Old register [4..130, step 2] should prune cur register [8..68, step 4]. */ +SEC("socket") +__success __log_level(2) +__msg("7: (27) r6 *= 4 ; R6=scalar({{.*}}umin32=8,{{.*}}umax32=68,{{.*}},step=0+4)") +__msg("10: (27) r7 *= 2 ; R7=scalar({{.*}}umin32=4,{{.*}}umax32=130,{{.*}},step=0+2)") +__msg("11: (25) if r0 > 0x2a goto pc+1") +__msg("from 11 to 13: safe") +__flag(BPF_F_TEST_STATE_FREQ) +__naked void step_prune_hit_multiple(void) +{ + asm volatile (" \ + call %[bpf_get_prandom_u32]; \ + r6 = r0; \ + call %[bpf_get_prandom_u32]; \ + r7 = r0; \ + call %[bpf_get_prandom_u32]; \ + r6 &= 0x0f; \ + r6 += 2; \ + r6 *= 4; \ + r7 &= 0x3f; \ + r7 += 2; \ + r7 *= 2; \ + if r0 > 42 goto 1f; /* can't predict */ \ + r6 = r7; /* step=2 explored first, step=4 explored next */ \ +1: r0 = r10; \ + r6 = -r6; \ + r0 += r6; \ + *(u8 *)(r0 + 0) = 7; /* force r6 precise */ \ + exit; \ +" : + : __imm(bpf_get_prandom_u32) + : __clobber_all); +} + +/* + * Old register [0..189, step 3] should not prune cur register [0..30, step 2]. + * Bounds and tnum containment allow pruning; step divisibility must prevent it. + */ +SEC("socket") +__success __log_level(2) +__msg("6: (27) r6 *= 2 ; R6=scalar({{.*}}smin32=0,{{.*}}umax32=30,{{.*}},step=0+2)") +__msg("8: (27) r7 *= 3 ; R7=scalar({{.*}}smin32=0,{{.*}}umax32=189,{{.*}},step=0+3)") +__msg("9: (25) if r0 > 0x2a goto pc+1") +__msg("17: (95) exit") +__msg("from 9 to 11: R6=scalar({{.*}},step=0+2)") +__flag(BPF_F_TEST_STATE_FREQ) +__naked void step_prune_miss_non_multiple(void) +{ + asm volatile (" \ + call %[bpf_get_prandom_u32]; \ + r6 = r0; \ + call %[bpf_get_prandom_u32]; \ + r7 = r0; \ + call %[bpf_get_prandom_u32]; \ + r6 &= 0x0f; \ + r6 *= 2; \ + r7 &= 0x3f; \ + r7 *= 3; \ + if r0 > 42 goto 1f; /* can't predict */ \ + r6 = r7; /* step=3 explored first, step=2 explored next */ \ +1: \ + r2 = r10; \ + r6 += 1; \ + r6 = -r6; \ + r2 += r6; \ + *(u8 *)(r2 + 0) = 7; /* force r6 precise */ \ + r0 = 0; \ + exit; \ +" : + : __imm(bpf_get_prandom_u32) + : __clobber_all); +} + +/* + * Constant current register lying on the cached line: cached is {0,3,6,...} + * (step 3, base 0), current is the constant 6. range_within() takes the + * constant branch: imod(6, 3) == base 0, so cur is on the line and the + * (precise) state is pruned -> "safe". + */ +SEC("socket") +__success __log_level(2) +__flag(BPF_F_TEST_STATE_FREQ) +__msg("14: (27) r1 *= 3") /* cached path: step 3 line */ +__msg("16: (b7) r1 = 6") /* current path: const 6, on the line */ +__msg("17: safe") /* pruned at join: imod(6, 3) == 0 */ +__naked void step_prune_hit_const_on_line(void) +{ + asm volatile (" \ + call %[bpf_get_prandom_u32]; \ + r6 = r0; \ + r1 = 0; \ + *(u32*)(r10 - 4) = r1; \ + r2 = r10; \ + r2 += -4; \ + r1 = %[step_map] ll; \ + call %[bpf_map_lookup_elem]; \ + if r0 == 0 goto l_out_%=; \ + r7 = r0; \ + r1 = r6; \ + r1 &= 0xff; \ + if r6 > 0 goto l_cur_%=; \ + r1 *= 3; /* old: step 3 */ \ + goto l_join_%=; \ +l_cur_%=: \ + r1 = 6; /* cur: const on line */ \ +l_join_%=: \ + r0 = r7; \ + r0 += r1; /* r1 forced precise */ \ + r2 = *(u8 *)(r0 + 0); \ +l_out_%=: \ + r0 = 0; \ + exit; \ +" : + : __imm(bpf_get_prandom_u32), + __imm(bpf_map_lookup_elem), + __imm_addr(step_map) + : __clobber_all); +} + +/* + * Constant current register NOT on the cached line: cached is {0,3,6,...} + * (step 3, base 0), current is the constant 7. imod(7, 3) == 1 != base 0, + * so range_within() fails and the join is traversed again. A power-of-two + * step is avoided on purpose: with step 3 the tnum is loose enough to admit + * 7, so imod() is + * the sole check that rejects it. No failure shape is possible here: 7 is + * within the line's bounds and tnum, and the cached line path already + * verifies the whole outro, so an eager prune could not miss an error. + */ +SEC("socket") +__success __log_level(2) +__flag(BPF_F_TEST_STATE_FREQ) +__msg("14: (27) r1 *= 3") /* cached path: step 3 line */ +__msg("16: (b7) r1 = 7") /* current path: const 7, off the line */ +/* not pruned: current continues past the join with the constant offset 7 */ +__msg("19: R0=map_value({{.*}}imm=7) R1=7") +__naked void step_prune_miss_const_off_line(void) +{ + asm volatile (" \ + call %[bpf_get_prandom_u32]; \ + r6 = r0; \ + r1 = 0; \ + *(u32*)(r10 - 4) = r1; \ + r2 = r10; \ + r2 += -4; \ + r1 = %[step_map] ll; \ + call %[bpf_map_lookup_elem]; \ + if r0 == 0 goto l_out_%=; \ + r7 = r0; \ + r1 = r6; \ + r1 &= 0xff; \ + if r6 > 0 goto l_cur_%=; \ + r1 *= 3; /* old: step 3 */ \ + goto l_join_%=; \ +l_cur_%=: \ + r1 = 7; /* cur: const off line */ \ +l_join_%=: \ + r0 = r7; \ + r0 += r1; /* r1 forced precise */ \ + r2 = *(u8 *)(r0 + 0); \ +l_out_%=: \ + r0 = 0; \ + exit; \ +" : + : __imm(bpf_get_prandom_u32), + __imm(bpf_map_lookup_elem), + __imm_addr(step_map) + : __clobber_all); +} + char _license[] SEC("license") = "GPL"; |
