All of lore.kernel.org
 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: 51+ 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-05  0:26       ` Eduard Zingerman
2026-11-05  0:30         ` 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.