bpf: Avoid false infinite loop reports after changes in SCEV clamping

Clamping now reproduces the widened range at every back edge,
so induction variable ranges no longer vary between iterations.
This can produce identical checkpoint states in terminating programs,
for example:

  int main(...) {
    for (int i = 0; i < 10; i++) {
      foo(...);
      for (int j = 0; j < 3; j++)
        ...;
    }
  }

  void foo(...) {
    ...;
  }

Handle two cases in states.c:bpf_is_state_visited():
- For checkpoints inside foo(), look for active loops in upper frames
  as well. Otherwise current_loop() returns NULL and an identical
  callee state can trigger an infinite loop report.
- For checkpoints inside the inner loop, skip the infinite loop check
  when the current state is inside a terminating loop in any frame.
  That loop bounds how many times its body is re-executed, so an
  identical state on a later iteration does not imply nontermination.
  This covers the inner loop above, re-entered with a fresh entry
  state on each outer iteration, and inner loops SCEV cannot analyze.

The nested_terminating_prune test still rejects such an outer loop,
but now reaches the jump complexity limit instead of reporting an
infinite loop. Update its expected message accordingly.

Fixes: 6ccf49f793 ("bpf: Use SCEV to widen bounded loops")
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://patch.msgid.link/20261026-scev-simpler-clamp-v2-3-f373aec9825f@gmail.com
This commit is contained in:
Eduard Zingerman 2026-10-26 16:38:23 -07:00 committed by Alexei Starovoitov
parent 0c33d612bd
commit 5dd32c8c10
No known key found for this signature in database
2 changed files with 29 additions and 9 deletions

View File

@ -1358,13 +1358,33 @@ static bool need_loop_checkpoint(struct bpf_verifier_env *env, int insn_idx)
return top->loop_id == insn_idx && top->terminates;
}
/* Searches for an innermost loop entry starting from an innermost frame. */
static struct loop_stack_entry *current_loop(struct bpf_verifier_state *st)
{
struct bpf_func_state *frame = st->frame[st->curframe];
struct bpf_func_state *frame;
int fr;
if (frame->loop_stack_cnt == 0)
return NULL;
return &frame->loop_stack[frame->loop_stack_cnt - 1];
for (fr = st->curframe; fr >= 0; fr--) {
frame = st->frame[fr];
if (frame->loop_stack_cnt)
return &frame->loop_stack[frame->loop_stack_cnt - 1];
}
return NULL;
}
/* True if any of the loops call chain is currently in terminates. */
static bool in_a_terminating_loop(struct bpf_verifier_state *st)
{
struct bpf_func_state *frame;
int l, fr;
for (fr = st->curframe; fr >= 0; fr--) {
frame = st->frame[fr];
for (l = frame->loop_stack_cnt - 1; l >= 0; l--)
if (frame->loop_stack[l].terminates)
return true;
}
return false;
}
int bpf_is_state_visited(struct bpf_verifier_env *env, int insn_idx)
@ -1528,10 +1548,10 @@ int bpf_is_state_visited(struct bpf_verifier_env *env, int insn_idx)
*/
old_loop = current_loop(&sl->state);
cur_loop = current_loop(cur);
if (old_loop && cur_loop &&
old_loop->terminates && cur_loop->terminates &&
old_loop->entry_state == cur_loop->entry_state) {
if (states_equal(env, &sl->state, cur, RANGE_WITHIN)) {
if (old_loop && cur_loop && in_a_terminating_loop(cur)) {
if (old_loop->terminates && cur_loop->terminates &&
old_loop->entry_state == cur_loop->entry_state &&
states_equal(env, &sl->state, cur, RANGE_WITHIN)) {
loop = true;
goto hit;
}

View File

@ -3351,7 +3351,7 @@ SEC("xdp")
__failure
__log_level(2)
__flag(BPF_F_TEST_STATE_FREQ)
__msg("infinite loop detected at insn 6")
__msg("The sequence of 8193 jumps is too complex.")
__naked void nested_terminating_prune(void)
{
asm volatile (" \