Commit 4a04d13576fd for kernel
commit 4a04d13576fd69149b91672b5f1dc62eca272fa5
Author: Daniel Borkmann <daniel@iogearbox.net>
Date: Thu Mar 19 22:15:07 2026 +0100
selftests/bpf: Add a test cases for sync_linked_regs regarding zext propagation
Add multiple test cases for linked register tracking with alu32 ops:
- Add a test that checks sync_linked_regs() regarding reg->id (the linked
target register) for BPF_ADD_CONST32 rather than known_reg->id (the
branch register).
- Add a test case for linked register tracking that exposes the cross-type
sync_linked_regs() bug. One register uses alu32 (w7 += 1, BPF_ADD_CONST32)
and another uses alu64 (r8 += 2, BPF_ADD_CONST64), both linked to the
same base register.
- Add a test case that exercises regsafe() path pruning when two execution
paths reach the same program point with linked registers carrying
different ADD_CONST flags (BPF_ADD_CONST32 from alu32 vs BPF_ADD_CONST64
from alu64). This particular test passes with and without the fix since
the pruning will fail due to different ranges, but it would still be
useful to carry this one as a regression test for the unreachable div
by zero.
With the fix applied all the tests pass:
# LDLIBS=-static PKG_CONFIG='pkg-config --static' ./vmtest.sh -- ./test_progs -t verifier_linked_scalars
[...]
./test_progs -t verifier_linked_scalars
#602/1 verifier_linked_scalars/scalars: find linked scalars:OK
#602/2 verifier_linked_scalars/sync_linked_regs_preserves_id:OK
#602/3 verifier_linked_scalars/scalars_neg:OK
#602/4 verifier_linked_scalars/scalars_neg_sub:OK
#602/5 verifier_linked_scalars/scalars_neg_alu32_add:OK
#602/6 verifier_linked_scalars/scalars_neg_alu32_sub:OK
#602/7 verifier_linked_scalars/scalars_pos:OK
#602/8 verifier_linked_scalars/scalars_sub_neg_imm:OK
#602/9 verifier_linked_scalars/scalars_double_add:OK
#602/10 verifier_linked_scalars/scalars_sync_delta_overflow:OK
#602/11 verifier_linked_scalars/scalars_sync_delta_overflow_large_range:OK
#602/12 verifier_linked_scalars/scalars_alu32_big_offset:OK
#602/13 verifier_linked_scalars/scalars_alu32_basic:OK
#602/14 verifier_linked_scalars/scalars_alu32_wrap:OK
#602/15 verifier_linked_scalars/scalars_alu32_zext_linked_reg:OK
#602/16 verifier_linked_scalars/scalars_alu32_alu64_cross_type:OK
#602/17 verifier_linked_scalars/scalars_alu32_alu64_regsafe_pruning:OK
#602/18 verifier_linked_scalars/alu32_negative_offset:OK
#602/19 verifier_linked_scalars/spurious_precision_marks:OK
#602 verifier_linked_scalars:OK
Summary: 1/19 PASSED, 0 SKIPPED, 0 FAILED
Co-developed-by: Puranjay Mohan <puranjay@kernel.org>
Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://lore.kernel.org/r/20260319211507.213816-2-daniel@iogearbox.net
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
diff --git a/tools/testing/selftests/bpf/progs/verifier_linked_scalars.c b/tools/testing/selftests/bpf/progs/verifier_linked_scalars.c
index 7bf7dbfd237d..f4f8a055af8a 100644
--- a/tools/testing/selftests/bpf/progs/verifier_linked_scalars.c
+++ b/tools/testing/selftests/bpf/progs/verifier_linked_scalars.c
@@ -348,6 +348,114 @@ l0_%=: \
: __clobber_all);
}
+/*
+ * Test that sync_linked_regs() checks reg->id (the linked target register)
+ * for BPF_ADD_CONST32 rather than known_reg->id (the branch register).
+ */
+SEC("socket")
+__success
+__naked void scalars_alu32_zext_linked_reg(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w6 = w0; /* r6 in [0, 0xFFFFFFFF] */ \
+ r7 = r6; /* linked: same id as r6 */ \
+ w7 += 1; /* alu32: r7.id |= BPF_ADD_CONST32 */ \
+ r8 = 0xFFFFffff ll; \
+ if r6 < r8 goto l0_%=; \
+ /* r6 in [0xFFFFFFFF, 0xFFFFFFFF] */ \
+ /* sync_linked_regs: known_reg=r6, reg=r7 */ \
+ /* CPU: w7 = (u32)(0xFFFFFFFF + 1) = 0, zext -> r7 = 0 */ \
+ /* With fix: r7 64-bit = [0, 0] (zext applied) */ \
+ /* Without fix: r7 64-bit = [0x100000000] (no zext) */ \
+ r7 >>= 32; \
+ if r7 == 0 goto l0_%=; \
+ r0 /= 0; /* unreachable with fix */ \
+l0_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * Test that sync_linked_regs() skips propagation when one register used
+ * alu32 (BPF_ADD_CONST32) and the other used alu64 (BPF_ADD_CONST64).
+ * The delta relationship doesn't hold across different ALU widths.
+ */
+SEC("socket")
+__failure __msg("div by zero")
+__naked void scalars_alu32_alu64_cross_type(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w6 = w0; /* r6 in [0, 0xFFFFFFFF] */ \
+ r7 = r6; /* linked: same id as r6 */ \
+ w7 += 1; /* alu32: BPF_ADD_CONST32, delta = 1 */ \
+ r8 = r6; /* linked: same id as r6 */ \
+ r8 += 2; /* alu64: BPF_ADD_CONST64, delta = 2 */ \
+ r9 = 0xFFFFffff ll; \
+ if r7 < r9 goto l0_%=; \
+ /* r7 = 0xFFFFFFFF */ \
+ /* sync: known_reg=r7 (ADD_CONST32), reg=r8 (ADD_CONST64) */ \
+ /* Without fix: r8 = zext(0xFFFFFFFF + 1) = 0 */ \
+ /* With fix: r8 stays [2, 0x100000001] (r8 >= 2) */ \
+ if r8 > 0 goto l1_%=; \
+ goto l0_%=; \
+l1_%=: \
+ r0 /= 0; /* div by zero */ \
+l0_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * Test that regsafe() prevents pruning when two paths reach the same program
+ * point with linked registers carrying different ADD_CONST flags (one
+ * BPF_ADD_CONST32 from alu32, another BPF_ADD_CONST64 from alu64).
+ */
+SEC("socket")
+__failure __msg("div by zero")
+__flag(BPF_F_TEST_STATE_FREQ)
+__naked void scalars_alu32_alu64_regsafe_pruning(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w6 = w0; /* r6 in [0, 0xFFFFFFFF] */ \
+ r7 = r6; /* linked: same id as r6 */ \
+ /* Get another random value for the path branch */ \
+ call %[bpf_get_prandom_u32]; \
+ if r0 > 0 goto l_pathb_%=; \
+ /* Path A: alu32 */ \
+ w7 += 1; /* BPF_ADD_CONST32, delta = 1 */\
+ goto l_merge_%=; \
+l_pathb_%=: \
+ /* Path B: alu64 */ \
+ r7 += 1; /* BPF_ADD_CONST64, delta = 1 */\
+l_merge_%=: \
+ /* Merge point: regsafe() compares path B against cached path A. */ \
+ /* Narrow r6 to trigger sync_linked_regs for r7 */ \
+ r9 = 0xFFFFffff ll; \
+ if r6 < r9 goto l0_%=; \
+ /* r6 = 0xFFFFFFFF */ \
+ /* sync: r7 = 0xFFFFFFFF + 1 = 0x100000000 */ \
+ /* Path A: zext -> r7 = 0 */ \
+ /* Path B: no zext -> r7 = 0x100000000 */ \
+ r7 >>= 32; \
+ if r7 == 0 goto l0_%=; \
+ r0 /= 0; /* div by zero on path B */ \
+l0_%=: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
SEC("socket")
__success
void alu32_negative_offset(void)