Re: [PATCH bpf-next v3 04/12] bpf: Track low-32 scalar equality across zero-extending movs

From: Vineet Gupta

Date: Mon Oct 26 2026 - 16:30:58 EST


On 10/20/26 10:09 AM, bot+bpf-ci@xxxxxxxxxx wrote:
bpf: Track low-32 scalar equality across zero-extending movs
TL;DR: The comment and changelog claim a zero delta from an add is refused
composition, but a 32-bit add of known zero records the same link as the mov
and composes. Behaviour is sound; the explanation looks inaccurate.
@@ -17594,11 +17584,28 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,

if (dst_reg->link_kind) {
/*
- * If the register already went through rX += val
- * we cannot accumulate another val into rx->off.
+ * Only the zero-extending link the mov above records,
+ * BPF_REG_LINK_ZEXT with delta 0, can take a delta:
+ * (u32)((u32)(base + 0) + off) is (u32)(base + off).
+ * The add has to be 32-bit as well, since a 64-bit one
+ * parts company with the link once the low half wraps.
+ *
+ * Everything else still drops the link. A zero delta
+ * reached by an add rather than by the mov, say "r1 +=
+ * r3" with r3 known to be 0, has already spent its
+ * delta; composing onto it keeps a link alive across a
+ * loop's increments, and since the delta is compared
+ * for equality, every value the loop reaches becomes a
+ * state of its own and stops pruning.
*/
+ if (dst_reg->link_kind != BPF_REG_LINK_ZEXT || !alu32 ||
+ dst_reg->delta) {
+ /* also the target of the two gotos above */
clear_id:
- bpf_clear_scalar_id(dst_reg);
+ bpf_clear_scalar_id(dst_reg);
+ } else {
+ dst_reg->delta = off;
+ }
} else {
if (alu32)
dst_reg->link_kind = BPF_REG_LINK_ZEXT;
The comment says a zero delta "reached by an add rather than by the mov"
has already spent its delta, and the commit message says allowing
composition onto "a zero delta that an add produced rather than the mov
still costs 1176 -> 1196". Can this test actually tell the two apart?

Comment is wrong, fixed in v4.

Walking it through to confirm your reading: a 32-bit add on a register that has an id but no kind takes the else arm,

    } else {
        if (alu32)
            dst_reg->link_kind = BPF_REG_LINK_ZEXT;
        else
            dst_reg->link_kind = BPF_REG_LINK_FULL;
        dst_reg->delta = off;
    }

so "w1 += 0" records exactly what the mov records, ZEXT with delta 0, and a following "w1 += 1" composes onto it.

The example the comment gives, "r1 += r3", is 64-bit, records BPF_REG_LINK_FULL, and is refused for being full link rather than for having spent a delta.

The behaviour is sound: (u32)((u32)(base + 0) + off) is (u32)(base + off) whichever instruction produced the zero. What is actually refused is a *second* delta, and that is what the comment now says.


A 32-bit add or sub of a known zero ("w1 += 0", "w1 -= 0", or "w1 += w3"
with w3 known to be 0) on a register that has an id but no link_kind goes
through the else arm quoted above and records:

dst_reg->link_kind = BPF_REG_LINK_ZEXT;
dst_reg->delta = off; /* off == 0 */

That is the same state the mov records, so a following "w1 += 1" passes the
"dst_reg->link_kind != BPF_REG_LINK_ZEXT || !alu32 || dst_reg->delta" test
and composes.

Only the 64-bit example from the comment ("r1 += r3", which records
BPF_REG_LINK_FULL) is actually refused.

The result is still sound, since (u32)((u32)(base + 0) + off) is
(u32)(base + off). Should the comment and the changelog limit the claim to
64-bit adds, or does the mov-produced link need to be made distinguishable
from the add-produced one?

Former. Composing is already capped at one so comment says second delta is refused.

Thx,
-Vineet



---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/39458500836