Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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>

authored by

Daniel Borkmann and committed by
Alexei Starovoitov
4a04d135 bc308be3

+108
+108
tools/testing/selftests/bpf/progs/verifier_linked_scalars.c
··· 348 348 : __clobber_all); 349 349 } 350 350 351 + /* 352 + * Test that sync_linked_regs() checks reg->id (the linked target register) 353 + * for BPF_ADD_CONST32 rather than known_reg->id (the branch register). 354 + */ 355 + SEC("socket") 356 + __success 357 + __naked void scalars_alu32_zext_linked_reg(void) 358 + { 359 + asm volatile (" \ 360 + call %[bpf_get_prandom_u32]; \ 361 + w6 = w0; /* r6 in [0, 0xFFFFFFFF] */ \ 362 + r7 = r6; /* linked: same id as r6 */ \ 363 + w7 += 1; /* alu32: r7.id |= BPF_ADD_CONST32 */ \ 364 + r8 = 0xFFFFffff ll; \ 365 + if r6 < r8 goto l0_%=; \ 366 + /* r6 in [0xFFFFFFFF, 0xFFFFFFFF] */ \ 367 + /* sync_linked_regs: known_reg=r6, reg=r7 */ \ 368 + /* CPU: w7 = (u32)(0xFFFFFFFF + 1) = 0, zext -> r7 = 0 */ \ 369 + /* With fix: r7 64-bit = [0, 0] (zext applied) */ \ 370 + /* Without fix: r7 64-bit = [0x100000000] (no zext) */ \ 371 + r7 >>= 32; \ 372 + if r7 == 0 goto l0_%=; \ 373 + r0 /= 0; /* unreachable with fix */ \ 374 + l0_%=: \ 375 + r0 = 0; \ 376 + exit; \ 377 + " : 378 + : __imm(bpf_get_prandom_u32) 379 + : __clobber_all); 380 + } 381 + 382 + /* 383 + * Test that sync_linked_regs() skips propagation when one register used 384 + * alu32 (BPF_ADD_CONST32) and the other used alu64 (BPF_ADD_CONST64). 385 + * The delta relationship doesn't hold across different ALU widths. 386 + */ 387 + SEC("socket") 388 + __failure __msg("div by zero") 389 + __naked void scalars_alu32_alu64_cross_type(void) 390 + { 391 + asm volatile (" \ 392 + call %[bpf_get_prandom_u32]; \ 393 + w6 = w0; /* r6 in [0, 0xFFFFFFFF] */ \ 394 + r7 = r6; /* linked: same id as r6 */ \ 395 + w7 += 1; /* alu32: BPF_ADD_CONST32, delta = 1 */ \ 396 + r8 = r6; /* linked: same id as r6 */ \ 397 + r8 += 2; /* alu64: BPF_ADD_CONST64, delta = 2 */ \ 398 + r9 = 0xFFFFffff ll; \ 399 + if r7 < r9 goto l0_%=; \ 400 + /* r7 = 0xFFFFFFFF */ \ 401 + /* sync: known_reg=r7 (ADD_CONST32), reg=r8 (ADD_CONST64) */ \ 402 + /* Without fix: r8 = zext(0xFFFFFFFF + 1) = 0 */ \ 403 + /* With fix: r8 stays [2, 0x100000001] (r8 >= 2) */ \ 404 + if r8 > 0 goto l1_%=; \ 405 + goto l0_%=; \ 406 + l1_%=: \ 407 + r0 /= 0; /* div by zero */ \ 408 + l0_%=: \ 409 + r0 = 0; \ 410 + exit; \ 411 + " : 412 + : __imm(bpf_get_prandom_u32) 413 + : __clobber_all); 414 + } 415 + 416 + /* 417 + * Test that regsafe() prevents pruning when two paths reach the same program 418 + * point with linked registers carrying different ADD_CONST flags (one 419 + * BPF_ADD_CONST32 from alu32, another BPF_ADD_CONST64 from alu64). 420 + */ 421 + SEC("socket") 422 + __failure __msg("div by zero") 423 + __flag(BPF_F_TEST_STATE_FREQ) 424 + __naked void scalars_alu32_alu64_regsafe_pruning(void) 425 + { 426 + asm volatile (" \ 427 + call %[bpf_get_prandom_u32]; \ 428 + w6 = w0; /* r6 in [0, 0xFFFFFFFF] */ \ 429 + r7 = r6; /* linked: same id as r6 */ \ 430 + /* Get another random value for the path branch */ \ 431 + call %[bpf_get_prandom_u32]; \ 432 + if r0 > 0 goto l_pathb_%=; \ 433 + /* Path A: alu32 */ \ 434 + w7 += 1; /* BPF_ADD_CONST32, delta = 1 */\ 435 + goto l_merge_%=; \ 436 + l_pathb_%=: \ 437 + /* Path B: alu64 */ \ 438 + r7 += 1; /* BPF_ADD_CONST64, delta = 1 */\ 439 + l_merge_%=: \ 440 + /* Merge point: regsafe() compares path B against cached path A. */ \ 441 + /* Narrow r6 to trigger sync_linked_regs for r7 */ \ 442 + r9 = 0xFFFFffff ll; \ 443 + if r6 < r9 goto l0_%=; \ 444 + /* r6 = 0xFFFFFFFF */ \ 445 + /* sync: r7 = 0xFFFFFFFF + 1 = 0x100000000 */ \ 446 + /* Path A: zext -> r7 = 0 */ \ 447 + /* Path B: no zext -> r7 = 0x100000000 */ \ 448 + r7 >>= 32; \ 449 + if r7 == 0 goto l0_%=; \ 450 + r0 /= 0; /* div by zero on path B */ \ 451 + l0_%=: \ 452 + r0 = 0; \ 453 + exit; \ 454 + " : 455 + : __imm(bpf_get_prandom_u32) 456 + : __clobber_all); 457 + } 458 + 351 459 SEC("socket") 352 460 __success 353 461 void alu32_negative_offset(void)