Add a test equivalent to the following pseudo-code: void scrub_slot_callee(u32 *slot) { slot[1] = 1; /* turn the caller's spill into STACK_MISC */ ... checkpoint on first iteration ... } int caller(void) { u32 slot[2]; slot[0] = 1; /* 32-bit spill */ for (int i = 0; i < 10; i++) { scrub_slot_callee(slot); } return slot[0]; } Verification proceeds as follows: - First iteration: the stack slot is a spill at the call site and STACK_MISC at the callee's return. - Backedge: the counter is clamped to 0..9, but the stack slot is now STACK_MISC, so the state does not match the header checkpoint. - Second iteration: the stack difference prevents a match at the call site, but the states match exactly at the callee's return. - The callee frame has no active loop. If current_loop() checks only that frame, bpf_is_state_visited() reports an infinite loop. Signed-off-by: Eduard Zingerman --- tools/testing/selftests/bpf/progs/verifier_scev.c | 40 +++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_scev.c b/tools/testing/selftests/bpf/progs/verifier_scev.c index 213a2b6a5027..078803e71197 100644 --- a/tools/testing/selftests/bpf/progs/verifier_scev.c +++ b/tools/testing/selftests/bpf/progs/verifier_scev.c @@ -2567,6 +2567,46 @@ __naked void no_widen_any_stack_origin(void) " ::: __clobber_all); } +__naked __noinline __used static int scrub_slot_callee(void) +{ + asm volatile (" \ + *(u32 *)(r1 + 4) = 1; \ + call %[bpf_get_prandom_u32]; \ + exit; \ +" : + : __imm(bpf_get_prandom_u32) + : __clobber_all); +} + +/* + * The callee turns the caller's fp-8 into misc, so the states at the call site + * differ between the first and the second iteration, while the states inside + * the callee are identical. Verification fails with an infinite loop error if + * states.c:current_loop() does not search upper frames for active loops. + */ +SEC("socket") +__success +__log_level(2) +__flag(BPF_F_TEST_STATE_FREQ) +__msg("loop header at 3, widening r6 to 0..9 step 1") +__msg("loop header at 3, clamping r6 to 0..9 step 1") +__msg("12: safe") +__naked void clamp_callee_state_pruning(void) +{ + asm volatile (" \ + w1 = 1; \ + *(u32 *)(r10 - 8) = r1; \ + r6 = 0; \ +1: r1 = r10; \ + r1 += -8; \ + call scrub_slot_callee; \ + r6 += 1; \ + if r6 < 10 goto 1b; \ + r0 = *(u32 *)(r10 - 8); \ + exit; \ +" ::: __clobber_all); +} + /* * r7 starts as 1 + 6*k and invariant r8 as 1 + 9*k (0 <= k <= 3). * The (any r7 r8) union must preserve base 1 and step gcd(6, 9) = 3. -- 2.53.0