From: Eduard Zingerman 1. u64 range where the lo32 of each endpoint falls outside the u32 range within its 2^32 block, requiring umin/umax to advance to an adjacent block. Three variants: - range entirely below S64_MAX; - s64 range spanning negative and positive values to exercise smin/smax advance; - u64 range crossing the sign boundary: smin/smax stay conservative at S64_MIN/S64_MAX. 2. 32-bit range crosses the U32_MAX/0 boundary, represented as s32 range crossing sign boundary. 3. s32-bit range wraps, but u32 has a tighter lower bound from an unsigned comparison. Co-developed-by: Helen Koike Signed-off-by: Helen Koike Signed-off-by: Eduard Zingerman --- This patch was cherry-picked from: https://lore.kernel.org/bpf/20260318-cnum-sync-bounds-v1-4-1f2e455ea711@gmail.com/ I added three other test cases and renamed a test to follow a pattern with the others (and updated commit message). Some of these extra tests were added due to cases I found while implementing the version without the circular range logic[1], so I kept them here (I guess some extra tests won't hurt). [1] https://github.com/helen-fornazier/linux/commits/bpf-min-max-if-else-solution/ --- .../selftests/bpf/progs/verifier_bounds.c | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_bounds.c b/tools/testing/selftests/bpf/progs/verifier_bounds.c index bb20f0f06f05..a0178207c186 100644 --- a/tools/testing/selftests/bpf/progs/verifier_bounds.c +++ b/tools/testing/selftests/bpf/progs/verifier_bounds.c @@ -2165,4 +2165,181 @@ l0_%=: r0 = 0; \ : __clobber_all); } +/* + * 64-bit range is outside the 32-bit range in each 2^32 block. + * + * This test triggers updates on umin/umax and smin/smax. + * + * N*2^32 (N+1)*2^32 (N+2)*2^32 (N+3)*2^32 + * ||----|=====|--|----------||----|=====|-------------||--|-|=====|-------------|| + * |< b >| | |< b >| | |< b >| + * | | | | + * |<---------------+- a -+---------------->| + * | | + * |< t >| refined r0 range + * + * a = u64 [0x1'00000008, 0x3'00000001] + * b = u32 [2, 5] + * t = u64 [0x2'00000002, 0x2'00000005] + */ +SEC("socket") +__success +__flag(BPF_F_TEST_REG_INVARIANTS) +__naked void deduce64_from_32_block_change(void *ctx) +{ + asm volatile (" \ + call %[bpf_get_prandom_u32]; \ + r1 = 0x100000008 ll; \ + if r0 < r1 goto 2f; \ + r1 = 0x300000001 ll; \ + if r0 > r1 goto 2f; /* u64: [0x1'00000008, 0x3'00000001] */ \ + if w0 < 2 goto 2f; \ + if w0 > 5 goto 2f; /* u32: [2, 5] */ \ + r2 = 0x200000002 ll; \ + r3 = 0x200000005 ll; \ + if r0 >= r2 goto 1f; /* should be always true */ \ + r10 = 0; /* dead code */ \ +1: if r0 <= r3 goto 2f; /* should be always true */ \ + r10 = 0; /* dead code */ \ +2: exit; \ + " : + : __imm(bpf_get_prandom_u32) + : __clobber_all); +} + +/* + * Similar to the deduce64_from_32_block_change test for smin/smax boundaries. + * + * a = s64 [0x8000000100000008, 0x0000000300000001] (crosses sign boundary) + * b = u32 [2, 5] + * t = s64 [0x8000000200000002, 0x0000000200000005] + */ +SEC("socket") +__success +__flag(BPF_F_TEST_REG_INVARIANTS) +__naked void deduce64_from_32_block_change_signed(void *ctx) +{ + asm volatile (" \ + call %[bpf_get_prandom_u32]; \ + r1 = 0x8000000100000008 ll; \ + if r0 s< r1 goto 2f; \ + r1 = 0x300000001 ll; \ + if r0 s> r1 goto 2f; /* s64: [0x8000000100000008, 0x3'00000001] */ \ + if w0 < 2 goto 2f; \ + if w0 > 5 goto 2f; /* u32: [2, 5] */ \ + r2 = 0x8000000200000002 ll; \ + r3 = 0x200000005 ll; \ + if r0 s>= r2 goto 1f; /* should be always true */ \ + r10 = 0; /* dead code */ \ +1: if r0 s<= r3 goto 2f; /* should be always true */ \ + r10 = 0; /* dead code */ \ +2: exit; \ + " : + : __imm(bpf_get_prandom_u32) + : __clobber_all); +} + +/* + * Similar to the deduce64_from_32_block_change test, with conservative signed boundaries. + * + * a = u64 [0x1'00000008, 0x80000003'00000001] + * = s64 [S64_MIN, S64_MAX] (since (s64)umin > (s64)umax) + * b = u32 [2, 5] + * t = u64 [0x2'00000002, 0x80000002'00000005] + */ +SEC("socket") +__success +__flag(BPF_F_TEST_REG_INVARIANTS) +__naked void deduce64_from_32_block_change_conservative_signed(void *ctx) +{ + asm volatile (" \ + call %[bpf_get_prandom_u32]; \ + r1 = 0x100000008 ll; \ + if r0 < r1 goto 2f; \ + r1 = 0x8000000300000001 ll; \ + if r0 > r1 goto 2f; /* u64: [0x100000008, 0x8000000300000001] */ \ + if w0 < 2 goto 2f; \ + if w0 > 5 goto 2f; /* u32: [2, 5] */ \ + r2 = 0x200000002 ll; \ + r3 = 0x8000000200000005 ll; \ + if r0 >= r2 goto 1f; /* should be always true */ \ + r10 = 0; /* dead code */ \ +1: if r0 <= r3 goto 2f; /* should be always true */ \ + r10 = 0; /* dead code */ \ +2: exit; \ + " : + : __imm(bpf_get_prandom_u32) + : __clobber_all); +} + +/* + * 32-bit range crossing U32_MAX / 0 boundary. + * + * N*2^32 (N+1)*2^32 (N+2)*2^32 (N+3)*2^32 + * ||===|---------|------|===||===|----------------|===||===|---------|------|===|| + * |b >| | |< b||b >| |< b||b >| | |< b| + * | | | | + * |<-----+----------------- a --------------+-------->| + * | | + * |<---------------- t ------------->| refined r0 range + * + * a = u64 [0x1'00000006, 0x2'FFFFFFEF] + * b = s32 [-16, 5] (u32 wrapping [0xFFFFFFF0, 0x00000005]) + * t = u64 [0x1'FFFFFFF0, 0x2'00000005] + */ +SEC("socket") +__success +__flag(BPF_F_TEST_REG_INVARIANTS) +__naked void deduce64_from_32_wrapping_32bit(void *ctx) +{ + asm volatile (" \ + call %[bpf_get_prandom_u32]; \ + r1 = 0x100000006 ll; \ + if r0 < r1 goto 2f; \ + r1 = 0x2ffffffef ll; \ + if r0 > r1 goto 2f; /* u64: [0x1'00000006, 0x2'FFFFFFEF] */ \ + if w0 s< -16 goto 2f; \ + if w0 s> 5 goto 2f; /* s32: [-16, 5] */ \ + r1 = 0x1fffffff0 ll; \ + r2 = 0x200000005 ll; \ + if r0 >= r1 goto 1f; /* should be always true */ \ + r10 = 0; /* dead code */ \ +1: if r0 <= r2 goto 2f; /* should be always true */ \ + r10 = 0; /* dead code */ \ +2: exit; \ + " : + : __imm(bpf_get_prandom_u32) + : __clobber_all); +} + +/* + * s32 range wraps, but u32 has a tighter lower bound from an unsigned + * comparison. + * + * a = u64 [0x7FFFFFFF'00000001, 0x80000002'00000010] + * b = s32 [-5, 5] + w0 u>= 2 => u32: [2, U32_MAX] + * t = u64 [0x7FFFFFFF'00000002, ...] + */ +SEC("socket") +__success __flag(BPF_F_TEST_REG_INVARIANTS) +__naked void deduce64_from_32_u32_tighter_than_s32(void *ctx) +{ + asm volatile (" \ + call %[bpf_get_prandom_u32]; \ + r1 = 0x7fffffff00000001 ll; \ + if r0 < r1 goto 2f; \ + r1 = 0x8000000200000010 ll; \ + if r0 > r1 goto 2f; /* u64: [0x7FFFFFFF'00000001, 0x80000002'00000010] */ \ + if w0 s< -5 goto 2f; \ + if w0 s> 5 goto 2f; /* s32: [-5, 5] */ \ + if w0 < 2 goto 2f; /* u32_min=2; s32 still wraps */ \ + r2 = 0x7fffffff00000002 ll; \ + if r0 >= r2 goto 2f; /* should be always true */ \ + r10 = 0; /* dead code */ \ +2: exit; \ + " : + : __imm(bpf_get_prandom_u32) + : __clobber_all); +} + char _license[] SEC("license") = "GPL"; -- 2.53.0