summaryrefslogtreecommitdiff
path: root/tools/testing
diff options
context:
space:
mode:
authorEduard Zingerman <eddyz87@gmail.com>2026-10-09 03:48:05 -0700
committerAlexei Starovoitov <ast@kernel.org>2026-10-09 20:29:15 +0000
commit8c5f6dfefdf17f42979e2ca0fe13f80dd80b34df (patch)
tree1d8b6e48a9887a22035df3adf83aaaee8bf2d8b8 /tools/testing
parent2db5837f9adafbf7bd68aac4666400296b3bb8c7 (diff)
downloadlinux-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.c181
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";