]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
selftests/bpf: Add tests for improved linked register tracking
authorPuranjay Mohan <puranjay@kernel.org>
Wed, 4 Feb 2026 15:17:38 +0000 (07:17 -0800)
committerAlexei Starovoitov <ast@kernel.org>
Wed, 4 Feb 2026 21:35:29 +0000 (13:35 -0800)
Add tests for linked register tracking with negative offsets, BPF_SUB,
and alu32. These test for all edge cases like overflows, etc.

Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://lore.kernel.org/r/20260204151741.2678118-3-puranjay@kernel.org
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
tools/testing/selftests/bpf/progs/verifier_linked_scalars.c

index 5f41bbb730a7136657b13352714ddd2c3f74819a..2ef346c827c25b96ae86096323dfbfbd5512d2db 100644 (file)
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
 
 #include <linux/bpf.h>
+#include <limits.h>
 #include <bpf/bpf_helpers.h>
 #include "bpf_misc.h"
 
@@ -18,9 +19,9 @@ __naked void scalars(void)
        r4 = r1;                                \
        w2 += 0x7FFFFFFF;                       \
        w4 += 0;                                \
-       if r2 == 0 goto l1;                     \
+       if r2 == 0 goto l0_%=;                  \
        exit;                                   \
-l1:                                            \
+l0_%=:                                         \
        r4 >>= 63;                              \
        r3 = 1;                                 \
        r3 -= r4;                               \
@@ -64,4 +65,302 @@ l0_%=:                                                              \
        : __clobber_all);
 }
 
+SEC("socket")
+__success
+__naked void scalars_neg(void)
+{
+       asm volatile ("                                 \
+       call %[bpf_get_prandom_u32];                    \
+       r0 &= 0xff;                                     \
+       r1 = r0;                                        \
+       r1 += -4;                                       \
+       if r1 s< 0 goto l0_%=;                          \
+       if r0 != 0 goto l0_%=;                          \
+       r0 /= 0;                                        \
+l0_%=:                                                 \
+       r0 = 0;                                         \
+       exit;                                           \
+"      :
+       : __imm(bpf_get_prandom_u32)
+       : __clobber_all);
+}
+
+/* Same test but using BPF_SUB instead of BPF_ADD with negative immediate */
+SEC("socket")
+__success
+__naked void scalars_neg_sub(void)
+{
+       asm volatile ("                                 \
+       call %[bpf_get_prandom_u32];                    \
+       r0 &= 0xff;                                     \
+       r1 = r0;                                        \
+       r1 -= 4;                                        \
+       if r1 s< 0 goto l0_%=;                          \
+       if r0 != 0 goto l0_%=;                          \
+       r0 /= 0;                                        \
+l0_%=:                                                 \
+       r0 = 0;                                         \
+       exit;                                           \
+"      :
+       : __imm(bpf_get_prandom_u32)
+       : __clobber_all);
+}
+
+/* alu32 with negative offset */
+SEC("socket")
+__success
+__naked void scalars_neg_alu32_add(void)
+{
+       asm volatile ("                                 \
+       call %[bpf_get_prandom_u32];                    \
+       w0 &= 0xff;                                     \
+       w1 = w0;                                        \
+       w1 += -4;                                       \
+       if w1 s< 0 goto l0_%=;                          \
+       if w0 != 0 goto l0_%=;                          \
+       r0 /= 0;                                        \
+l0_%=:                                                 \
+       r0 = 0;                                         \
+       exit;                                           \
+"      :
+       : __imm(bpf_get_prandom_u32)
+       : __clobber_all);
+}
+
+/* alu32 with negative offset using SUB */
+SEC("socket")
+__success
+__naked void scalars_neg_alu32_sub(void)
+{
+       asm volatile ("                                 \
+       call %[bpf_get_prandom_u32];                    \
+       w0 &= 0xff;                                     \
+       w1 = w0;                                        \
+       w1 -= 4;                                        \
+       if w1 s< 0 goto l0_%=;                          \
+       if w0 != 0 goto l0_%=;                          \
+       r0 /= 0;                                        \
+l0_%=:                                                 \
+       r0 = 0;                                         \
+       exit;                                           \
+"      :
+       : __imm(bpf_get_prandom_u32)
+       : __clobber_all);
+}
+
+/* Positive offset: r1 = r0 + 4, then if r1 >= 6, r0 >= 2, so r0 != 0 */
+SEC("socket")
+__success
+__naked void scalars_pos(void)
+{
+       asm volatile ("                                 \
+       call %[bpf_get_prandom_u32];                    \
+       r0 &= 0xff;                                     \
+       r1 = r0;                                        \
+       r1 += 4;                                        \
+       if r1 < 6 goto l0_%=;                           \
+       if r0 != 0 goto l0_%=;                          \
+       r0 /= 0;                                        \
+l0_%=:                                                 \
+       r0 = 0;                                         \
+       exit;                                           \
+"      :
+       : __imm(bpf_get_prandom_u32)
+       : __clobber_all);
+}
+
+/* SUB with negative immediate: r1 -= -4 is equivalent to r1 += 4 */
+SEC("socket")
+__success
+__naked void scalars_sub_neg_imm(void)
+{
+       asm volatile ("                                 \
+       call %[bpf_get_prandom_u32];                    \
+       r0 &= 0xff;                                     \
+       r1 = r0;                                        \
+       r1 -= -4;                                       \
+       if r1 < 6 goto l0_%=;                           \
+       if r0 != 0 goto l0_%=;                          \
+       r0 /= 0;                                        \
+l0_%=:                                                 \
+       r0 = 0;                                         \
+       exit;                                           \
+"      :
+       : __imm(bpf_get_prandom_u32)
+       : __clobber_all);
+}
+
+/* Double ADD clears the ID (can't accumulate offsets) */
+SEC("socket")
+__failure
+__msg("div by zero")
+__naked void scalars_double_add(void)
+{
+       asm volatile ("                                 \
+       call %[bpf_get_prandom_u32];                    \
+       r0 &= 0xff;                                     \
+       r1 = r0;                                        \
+       r1 += 2;                                        \
+       r1 += 2;                                        \
+       if r1 < 6 goto l0_%=;                           \
+       if r0 != 0 goto l0_%=;                          \
+       r0 /= 0;                                        \
+l0_%=:                                                 \
+       r0 = 0;                                         \
+       exit;                                           \
+"      :
+       : __imm(bpf_get_prandom_u32)
+       : __clobber_all);
+}
+
+/*
+ * Test that sync_linked_regs() correctly handles large offset differences.
+ * r1.off = S32_MIN, r2.off = 1, delta = S32_MIN - 1 requires 64-bit math.
+ */
+SEC("socket")
+__success
+__naked void scalars_sync_delta_overflow(void)
+{
+       asm volatile ("                                 \
+       call %[bpf_get_prandom_u32];                    \
+       r0 &= 0xff;                                     \
+       r1 = r0;                                        \
+       r2 = r0;                                        \
+       r1 += %[s32_min];                               \
+       r2 += 1;                                        \
+       if r2 s< 100 goto l0_%=;                        \
+       if r1 s< 0 goto l0_%=;                          \
+       r0 /= 0;                                        \
+l0_%=:                                                 \
+       r0 = 0;                                         \
+       exit;                                           \
+"      :
+       : __imm(bpf_get_prandom_u32),
+         [s32_min]"i"(INT_MIN)
+       : __clobber_all);
+}
+
+/*
+ * Another large delta case: r1.off = S32_MAX, r2.off = -1.
+ * delta = S32_MAX - (-1) = S32_MAX + 1 requires 64-bit math.
+ */
+SEC("socket")
+__success
+__naked void scalars_sync_delta_overflow_large_range(void)
+{
+       asm volatile ("                                 \
+       call %[bpf_get_prandom_u32];                    \
+       r0 &= 0xff;                                     \
+       r1 = r0;                                        \
+       r2 = r0;                                        \
+       r1 += %[s32_max];                               \
+       r2 += -1;                                       \
+       if r2 s< 0 goto l0_%=;                          \
+       if r1 s>= 0 goto l0_%=;                         \
+       r0 /= 0;                                        \
+l0_%=:                                                 \
+       r0 = 0;                                         \
+       exit;                                           \
+"      :
+       : __imm(bpf_get_prandom_u32),
+         [s32_max]"i"(INT_MAX)
+       : __clobber_all);
+}
+
+/*
+ * Test linked scalar tracking with alu32 and large positive offset (0x7FFFFFFF).
+ * After w1 += 0x7FFFFFFF, w1 wraps to negative for any r0 >= 1.
+ * If w1 is signed-negative, then r0 >= 1, so r0 != 0.
+ */
+SEC("socket")
+__success
+__naked void scalars_alu32_big_offset(void)
+{
+       asm volatile ("                                 \
+       call %[bpf_get_prandom_u32];                    \
+       w0 &= 0xff;                                     \
+       w1 = w0;                                        \
+       w1 += 0x7FFFFFFF;                               \
+       if w1 s>= 0 goto l0_%=;                         \
+       if w0 != 0 goto l0_%=;                          \
+       r0 /= 0;                                        \
+l0_%=:                                                 \
+       r0 = 0;                                         \
+       exit;                                           \
+"      :
+       : __imm(bpf_get_prandom_u32)
+       : __clobber_all);
+}
+
+SEC("socket")
+__failure
+__msg("div by zero")
+__naked void scalars_alu32_basic(void)
+{
+       asm volatile ("                                 \
+       call %[bpf_get_prandom_u32];                    \
+       r1 = r0;                                        \
+       w1 += 1;                                        \
+       if r1 > 10 goto 1f;                             \
+       r0 >>= 32;                                      \
+       if r0 == 0 goto 1f;                             \
+       r0 /= 0;                                        \
+1:                                                     \
+       r0 = 0;                                         \
+       exit;                                           \
+"      :
+       : __imm(bpf_get_prandom_u32)
+       : __clobber_all);
+}
+
+/*
+ * Test alu32 linked register tracking with wrapping.
+ * R0 is bounded to [0xffffff00, 0xffffffff] (high 32-bit values)
+ * w1 += 0x100 causes R1 to wrap to [0, 0xff]
+ *
+ * After sync_linked_regs, if bounds are computed correctly:
+ *   R0 should be [0x00000000_ffffff00, 0x00000000_ffffff80]
+ *   R0 >> 32 == 0, so div by zero is unreachable
+ *
+ * If bounds are computed incorrectly (64-bit underflow):
+ *   R0 becomes [0xffffffff_ffffff00, 0xffffffff_ffffff80]
+ *   R0 >> 32 == 0xffffffff != 0, so div by zero is reachable
+ */
+SEC("socket")
+__success
+__naked void scalars_alu32_wrap(void)
+{
+       asm volatile ("                                 \
+       call %[bpf_get_prandom_u32];                    \
+       w0 |= 0xffffff00;                               \
+       r1 = r0;                                        \
+       w1 += 0x100;                                    \
+       if r1 > 0x80 goto l0_%=;                        \
+       r2 = r0;                                        \
+       r2 >>= 32;                                      \
+       if r2 == 0 goto l0_%=;                          \
+       r0 /= 0;                                        \
+l0_%=:                                                 \
+       r0 = 0;                                         \
+       exit;                                           \
+"      :
+       : __imm(bpf_get_prandom_u32)
+       : __clobber_all);
+}
+
+SEC("socket")
+__success
+void alu32_negative_offset(void)
+{
+       volatile char path[5];
+       volatile int offset = bpf_get_prandom_u32();
+       int off = offset;
+
+       if (off >= 5 && off < 10)
+               path[off - 5] = '.';
+
+       /* So compiler doesn't say: error: variable 'path' set but not used */
+       __sink(path[0]);
+}
+
 char _license[] SEC("license") = "GPL";