BPF List
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Alexei Starovoitov" <alexei.starovoitov@gmail.com>, bpf@vger.kernel.org
Subject: Re: [PATCH bpf-next 29/37] bpf: Count '!=' across 0 in 32 bits and loops that go down
Date: Wed, 04 Nov 2026 00:26:29 +0000	[thread overview]
Message-ID: <sashiko-outbox-196311@kernel.org> (raw)
In-Reply-To: <20261103234104.208239-30-alexei.starovoitov@gmail.com>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] bpf: 64-bit JLT/JLE on 32-bit counter in compute_max_iters() lacks jmp32
- [Medium] bpf: compute_max_iters() zeroes bound_val before checking konst
- [Low] bpf: compute_max_iters() misses downward BPF_JLE when initial equals bound
--

commit 08a1779026bb125181a7c2d2c0167baa7768249b
Author: Alexei Starovoitov <ast@kernel.org>

bpf: Count '!=' across 0 in 32 bits and loops that go down

This commit teaches BPF scalar evolution to compute iteration counts for
32-bitBPF_JNE loops that cross zero and for downward loops tested with
unsigned BPF_JLT or BPF_JLE latches by rewriting the latter to signed
BPF_JSGE comparisons against zero.

> diff --git a/kernel/bpf/scev.c b/kernel/bpf/scev.c
> index 9ddaae03ac351..70d15449eebe1 100644
> --- a/kernel/bpf/scev.c
> +++ b/kernel/bpf/scev.c
> @@ -2738,8 +2738,9 @@ static bool compute_max_iters(struct bpf_verifier_env *env,
>  	struct scev *scev = env->scev;
>  	struct scev_value initial_val, step_val, bound_val;
>  	u64 step, bound, initial, total_change, backedge_taken_count;
> -	bool konst;
> +	struct linear_latch counted;
>  	u8 op = latch->op;
> +	bool konst, sign32 = latch->jmp32 && is_signed_op(op);
>  
>  	iters->iv_reg = -1;
>  
> @@ -2761,6 +2762,23 @@ static bool compute_max_iters(struct bpf_verifier_env *env,
>  
>  	if (!scev_value_is_const(&step_val))
>  		return false;
> +	/*
> +	 * 'i >= 0 && i < 128' is one unsigned test. A counter that goes down stays below 128.
> +	 * The loop ends when it gets below 0. A 64-bit jump compares all 64 bits of the bound.
> +	 * The latch of the caller stays as it is: same_latch() compares what the prog tests.
> +	 */
> +	if ((op == BPF_JLT || op == BPF_JLE) && initial_val.ptr_reg < 0 &&
> +	    (latch->jmp32 || latch->zext32 ?
> +	     (s32)step_val.umin < 0 && initial_val.u32_min < bound_val.u32_max &&
> +	     (latch->jmp32 ? bound_val.u32_max : bound_val.umax) <= S32_MAX :
> +	     (s64)step_val.umin < 0 && initial_val.umin < bound_val.umax &&
> +	     bound_val.umax <= S64_MAX)) {

[Severity: Low]
In compute_max_iters() in kernel/bpf/scev.c, both BPF_JLT and BPF_JLE are
checked with strict inequality between the minimum initial value and maximum
bound (initial_val.u32_min < bound_val.u32_max or
initial_val.umin < bound_val.umax).

When op == BPF_JLE and a downward counter starts at a value equal to the
bound (initial_val.u32_min == bound_val.u32_max or
initial_val.umin == bound_val.umax), this condition evaluates to false and
the latch is not rewritten to BPF_JSGE. Downward BPF_JLE is then rejected by
compute_backedge_taken_count() and compute_backedge_taken_max().

Should the comparison allow equality when op == BPF_JLE?

> +		counted = *latch;
> +		latch = &counted;
> +		op = latch->op = BPF_JSGE;
> +		bound_val = (struct scev_value){ .ptr_reg = -1 };
> +		sign32 = true;
> +	}

[Severity: Medium]
When a 64-bit unsigned BPF_JLT or BPF_JLE jump tests a 32-bit downward
counter (latch->zext32 is true and latch->jmp32 is false) with
bound_val.umax <= S32_MAX, compute_max_iters() in kernel/bpf/scev.c rewrites
counted.op to BPF_JSGE with bound_val = 0 and sets sign32 = true, but leaves
counted.jmp32 as false.

When compute_max_iters() subsequently calls compute_backedge_taken_max() or
latch_false_until(), both pass latch to latch_domain() with sign = true,
latch->jmp32 = false, and latch->zext32 = true:

kernel/bpf/scev.c:latch_domain() {
	...
	bias = sign ? 1ULL << 63 : 0;
	d->bmin = (sign ? (u64)bound->smin : bound->umin) ^ bias;
	d->bmax = (sign ? (u64)bound->smax : bound->umax) ^ bias;
	if (latch->zext32) {
		/* zero extended, so it is not negative */
		d->imin = initial->u32_min ^ bias;
		d->imax = initial->u32_max ^ bias;
		d->lo = bias;
		d->hi = bias + U32_MAX;
		return;
	}
}

Because bound is 0, latch_domain() sets d->bmin = bias and d->lo = bias, and
uses u32_min/u32_max instead of s32_min/s32_max.

In the non-constant case (!konst), compute_backedge_taken_max() then checks:

	case BPF_JGE:
	case BPF_JSGE:
		if (!down ||
		    check_add_overflow(div64_u64(d.imax - d.bmin, step), 1, &cnt) ||
		    d.bmin < d.lo || step > d.bmin - d.lo)
			return false;

Since d.bmin - d.lo is 0 and step >= 1, step > d.bmin - d.lo is always true
and compute_backedge_taken_max() unconditionally returns false.

In the constant case (konst), latch_false_until() computes
div64_u64(d.imin - d.lo, -step), which evaluates to initial_val.u32_min / -step
(i.e. backedge_taken_count - 1, the distance to 0 rather than the distance to
S32_MIN), causing bpf_compute_loop_iters() to reject multi-backedge loops
when another backedge has a different latch.

Should counted.jmp32 also be set to latch->jmp32 || latch->zext32 when
rewriting the latch to BPF_JSGE?

>  	konst = scev_value_is_const(&initial_val) && scev_value_is_const(&bound_val);

[Severity: Medium]
In compute_max_iters() in kernel/bpf/scev.c, bound_val is overwritten with
zero before konst is computed from scev_value_is_const(&initial_val) &&
scev_value_is_const(&bound_val).

If a downward BPF_JLT or BPF_JLE loop has a constant initial_val and a
non-constant range bound_val (for example initial_val = 10 and
bound_val in [5, 20]), scev_value_is_const(&bound_val) checks the overwritten
zero bound_val and evaluates konst to true.

As a result, compute_max_iters() skips compute_backedge_taken_max() (leaving
iters->iv_reg as -1) and sets:

	iters->min_header_count = konst && loop->exits_cnt == 1 ? iters->max_header_count : 0;

This sets min_header_count to max_header_count even when the original
bound_val range includes values at or below initial_val that cause the loop
to exit on the first iteration. It also forces mixed-width latches
(!latch->zext32 && latch->jmp32) with non-zero upper 32 bits in initial_val
onto the constant path where they are rejected.

Should konst be evaluated before bound_val is overwritten?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261103234104.208239-1-alexei.starovoitov@gmail.com?part=29

  reply	other threads:[~2026-11-04  0:26 UTC|newest]

Thread overview: 49+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-03 23:40 [PATCH bpf-next 00/37] bpf: More precision, more counted loops, larger limits Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 01/37] bpf: Keep the range of a packet pointer when a number >= 0 is added Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 02/37] selftests/bpf: Test range of a packet pointer after a number " Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 03/37] bpf: Refuse fexit on an unreliable static subprog that returns in R0:R2 Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 04/37] bpf: Don't trust BTF of a static subprog that reads more registers Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 05/37] selftests/bpf: Test trampolines on subprogs that take more than BTF says Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 06/37] bpf: Count stack arguments of a static subprog from its code Alexei Starovoitov
2026-11-04  0:14   ` sashiko-bot
2026-11-04  1:05     ` Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 07/37] selftests/bpf: Test static subprogs that don't take what BTF says Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 08/37] bpf: Don't treat a partial spill as widened in scalar evolution Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 09/37] selftests/bpf: Test loops that change a partial spill Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 10/37] bpf: Let scalar evolution see loads from read-only maps Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 11/37] selftests/bpf: Test scalar evolution with a loop bound in .rodata Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 12/37] bpf: Helpers don't write through ARG_ANYTHING Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 13/37] selftests/bpf: Test scalar evolution with stack address as ARG_ANYTHING Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 14/37] bpf: Fold sums of constants and two shifts by 32 in scalar evolution Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 15/37] selftests/bpf: Test sums of constants and " Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 16/37] bpf: Count loops whose first value or bound is a range Alexei Starovoitov
2026-11-04  0:21   ` sashiko-bot
2026-11-04  1:05     ` Alexei Starovoitov
2026-11-04  7:26   ` Eduard Zingerman
2026-11-04 18:25     ` Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 17/37] selftests/bpf: Test " Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 18/37] bpf: Describe more registers of a counted loop Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 19/37] selftests/bpf: Test " Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 20/37] bpf: Compute scev for a loop that has more than 256 exits Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 21/37] selftests/bpf: Test a loop with 301 exits Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 22/37] bpf: Describe a 32-bit counter that lives on the stack Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 23/37] selftests/bpf: Test " Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 24/37] bpf: Count a loop with several back edges Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 25/37] selftests/bpf: Test loops " Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 26/37] bpf: Compute bounds for a shift, a division and a remainder by a range Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 27/37] selftests/bpf: Test " Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 28/37] bpf: Count a loop whose latch is false in the first iteration Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 29/37] bpf: Count '!=' across 0 in 32 bits and loops that go down Alexei Starovoitov
2026-11-04  0:26   ` sashiko-bot [this message]
2026-11-04  1:06     ` Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 30/37] bpf: Count a loop whose latch tests a flag that a jump sets Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 31/37] selftests/bpf: Test more latches of counted loops Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 32/37] bpf: Allow BPF_MAX_SUBPROGS targets of a read from a table of functions Alexei Starovoitov
2026-11-04  0:12   ` sashiko-bot
2026-11-04  1:06     ` Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 33/37] bpf: Keep the frame of analyze_subprog() small Alexei Starovoitov
2026-11-03 23:40 ` [PATCH bpf-next 34/37] bpf: Allow 24 arguments of static subprogs Alexei Starovoitov
2026-11-03 23:41 ` [PATCH bpf-next 35/37] selftests/bpf: Test a static subprog with 24 arguments Alexei Starovoitov
2026-11-03 23:41 ` [PATCH bpf-next 36/37] bpf: Bump BPF_MAX_SUBPROGS to 1024 Alexei Starovoitov
2026-11-03 23:41 ` [PATCH bpf-next 37/37] selftests/bpf: Make room in veristat for the stats of 1024 subprogs Alexei Starovoitov
2026-11-04 18:40 ` [PATCH bpf-next 00/37] bpf: More precision, more counted loops, larger limits patchwork-bot+netdevbpf

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-196311@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=alexei.starovoitov@gmail.com \
    --cc=bpf@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox