From: sashiko-bot@kernel.org
To: "Vineet Gupta" <vineet.gupta@linux.dev>
Cc: bpf@vger.kernel.org
Subject: Re: [PATCH bpf-next v3 06/12] bpf: Track low-32 scalar equality across sign-extending movs
Date: Tue, 20 Oct 2026 16:57:21 +0000 [thread overview]
Message-ID: <sashiko-outbox-179390@kernel.org> (raw)
In-Reply-To: <20261020161907.2115379-7-vineet.gupta@linux.dev>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] bpf: empty range after reg_bounds_sync() in sync_linked_regs()
--
commit ad01f18c15eb8cfa9a97b984758db9dd254aace2
Author: Vineet Gupta <vineet.gupta@linux.dev>
bpf: Track low-32 scalar equality across sign-extending movs
This commit adds BPF_REG_LINK_SEXT to track low-32 scalar equality across
32-bit sign-extending moves, using sext_32_to_64() and tnum_sext() to
propagate bounds in sync_linked_regs().
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 6afd718ede647..4621b476b206e 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
[ ... ]
> @@ -6090,6 +6090,25 @@ static void zext_32_to_64(struct bpf_reg_state *reg)
> reg_set_urange64(reg, reg_u32_min(reg), reg_u32_max(reg));
> }
>
> +/*
> + * The sign-extending counterpart. Signed bounds carry over directly because
> + * sign extension is monotonic over the signed 32-bit range.
> + */
> +static void sext_32_to_64(struct bpf_reg_state *reg)
> +{
> + /*
> + * See comment at reg_step_check_unsigned(). Sign extension subtracts
> + * 2^32 from every value with bit 31 set, which preserves a residue
> + * mod step only when step divides 2^32, so the line cannot be kept.
> + * The zero-extending twin calls reg_step_check_unsigned(reg, 32) for
> + * the same reason; the unsigned form does not apply here because the
> + * result is not the low 32 bits but the low 32 bits moved.
> + */
> + reg_step_reset(reg);
> + reg->var_off = tnum_sext(reg->var_off, 4);
> + reg_set_srange64(reg, reg_s32_min(reg), reg_s32_max(reg));
> +}
[ ... ]
> @@ -18795,9 +18843,26 @@ static void sync_linked_regs(struct bpf_verifier_env *env, struct bpf_verifier_s
> scalar32_min_max_add(reg, &fake_reg);
> scalar_min_max_add(reg, &fake_reg);
> reg->var_off = tnum_add(reg->var_off, fake_reg.var_off);
> - if (reg->link_kind == BPF_REG_LINK_ZEXT ||
> - known_reg->link_kind == BPF_REG_LINK_ZEXT)
> + if (reg->link_kind == BPF_REG_LINK_SEXT) {
> + sext_32_to_64(reg);
> + } else if (reg->link_kind == BPF_REG_LINK_ZEXT ||
> + known_reg->link_kind == BPF_REG_LINK_ZEXT ||
> + known_reg->link_kind == BPF_REG_LINK_SEXT) {
> + /*
> + * Either @reg is itself the zero extension, or
> + * @reg has no 32-bit kind and @known_reg does
> + * -- the reverse direction, where the guard
> + * above has already proved @reg fits 32 bits.
> + * Then @reg is @known_reg's low half with
> + * nothing above it, whichever way @known_reg
> + * filled its own high half, so the zero
> + * extension is the reconstruction for both
> + * kinds. BPF_REG_LINK_FULL is not here on
> + * purpose: it relates all 64 bits and must not
> + * be truncated.
> + */
> zext_32_to_64(reg);
> + }
> /*
> * Intersect rather than overwrite. A BPF_REG_LINK_ZEXT
> * link over a wide base does not propagate back, so the
[Severity: Medium]
Can sync_linked_regs() leave reg with CNUM64_EMPTY and CNUM32_EMPTY after
reg_bounds_sync(), triggering a register invariants violation on the next
instruction that inspects the register?
At the end of the differencing block in sync_linked_regs():
cnum64_intersect_with(®->r64, prev.r64);
cnum32_intersect_with(®->r32, prev.r32);
reg->var_off = tnum_intersect(reg->var_off, prev.var_off);
if (range_bounds_violation(reg))
*reg = prev;
reg_bounds_sync(reg);
range_bounds_violation(reg) is only checked before reg_bounds_sync(reg),
and tnum_intersect() is called without checking tnum_overlap(). When a
contradiction occurs across domains (between r64, r32, and var_off) rather
than within r64 or r32 individually, the pre-sync range_bounds_violation()
check passes, and reg_bounds_sync(reg) then turns the cross-domain
disjointness into CNUM64_EMPTY / CNUM32_EMPTY without restoring prev.
This can be triggered in two ways with BPF_REG_LINK_SEXT:
1. Cross-domain contradiction between r64 and r32 via sext_32_to_64():
Suppose r6 is a 64-bit unknown scalar and r7 = (s32)r6 links r7 to r6
with BPF_REG_LINK_SEXT.
- Narrow r7 with signed 64-bit jumps to [-0x7ffffff0, 0x7ffffff0], so
r7.r64 is [-0x7ffffff0, 0x7ffffff0] and r7.r32 is the arc
[0x80000010, 0x7ffffff0].
- Execute if w6 < 0x7ffffff0 goto out. Because [0x7ffffff0, 0xffffffff]
and [0x80000010, 0x7ffffff0] overlap in two disjoint arcs,
cnum32_intersect() over-approximates by keeping the smaller arc
[0x7ffffff0, 0xffffffff] for r6.r32 and r7.r32, while r7.r64 remains
[-0x7ffffff0, 0x7ffffff0].
- Execute if w6 != w8 goto out where w8 has r32 = [0x7ffffff8, 0x80000007].
- In sync_linked_regs() for r7, reg->r32 is [0x7ffffff8, 0x80000007],
which straddles S32_MAX / S32_MIN. sext_32_to_64(reg) sets reg->r64 to
[S32_MIN, S32_MAX].
- Intersecting reg->r64 with prev.r64 gives [-0x7ffffff0, 0x7ffffff0]
(non-empty) and intersecting reg->r32 with prev.r32 gives
[0x7ffffff8, 0x80000007] (non-empty).
- range_bounds_violation(reg) is false, so *reg = prev is skipped.
- reg_bounds_sync(reg) calls deduce_bounds_32_from_64(reg), which
intersects [0x7ffffff8, 0x80000007] with [0x80000010, 0x7ffffff0] and
leaves reg->r32 = CNUM32_EMPTY and reg->r64 = CNUM64_EMPTY.
2. Contradiction between var_off and r64/r32 via tnum_intersect():
Suppose r6 is a 64-bit unknown scalar, r7 = (s32)r6, and r7 is narrowed
to [-4, 3].
- Execute if w6 & 8 goto out. On the fallthrough branch,
regs_refine_cond_op() calls __mark_reg_unbounded(&r6) and clears bit 3
in var_off; sync_linked_regs() narrows r7 to [0, 3] with
var_off = TNUM(0, 3).
- Execute if w6 & 4 goto target. On the taken branch, r6 gets low-32
var_off = TNUM(4, 0xfffffff3) and r32 = [-0x7ffffffc, 0x7ffffff7].
- In sync_linked_regs() on r7, cnum64_intersect_with() and
cnum32_intersect_with() both produce [0, 3] (non-empty), while
tnum_intersect() without a tnum_overlap() check combines
TNUM(4, 0xfffffffffffffff3) and TNUM(0, 3) into TNUM(4, 3) ([4, 7]).
- range_bounds_violation(reg) is false before reg_bounds_sync(reg), and
__update_reg_bounds() inside reg_bounds_sync(reg) then intersects
[0, 3] with [4, 7], leaving r7 with CNUM32_EMPTY and CNUM64_EMPTY.
Should sync_linked_regs() check !tnum_overlap(reg->var_off, prev.var_off)
and also check range_bounds_violation(reg) after reg_bounds_sync(reg) before
committing the updated state?
> /*
> * Intersect rather than overwrite. A BPF_REG_LINK_ZEXT
> * link over a wide base does not propagate back, so the
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261020161907.2115379-1-vineet.gupta@linux.dev?part=6
next prev parent reply other threads:[~2026-10-20 16:57 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-20 16:18 [PATCH bpf-next v3 00/12] bpf: track scalar equality across the low 32 bits Vineet Gupta
2026-10-20 16:18 ` [PATCH bpf-next v3 01/12] bpf: Keep the 32-bit range across a sign extension that cannot change it Vineet Gupta
2026-10-20 16:18 ` [PATCH bpf-next v3 02/12] selftests/bpf: Cover sign extensions that cannot change the range Vineet Gupta
2026-10-20 16:18 ` [PATCH bpf-next v3 03/12] bpf: Prepare linked scalars for relations narrower than 64 bits Vineet Gupta
2026-10-20 17:08 ` bot+bpf-ci
2026-10-20 16:18 ` [PATCH bpf-next v3 04/12] bpf: Track low-32 scalar equality across zero-extending movs Vineet Gupta
2026-10-20 16:39 ` sashiko-bot
2026-10-20 17:09 ` bot+bpf-ci
2026-10-20 16:19 ` [PATCH bpf-next v3 05/12] selftests/bpf: Cover the low-32 link for " Vineet Gupta
2026-10-20 16:53 ` bot+bpf-ci
2026-10-20 16:19 ` [PATCH bpf-next v3 06/12] bpf: Track low-32 scalar equality across sign-extending movs Vineet Gupta
2026-10-20 16:57 ` sashiko-bot [this message]
2026-10-20 16:19 ` [PATCH bpf-next v3 07/12] selftests/bpf: Cover the low-32 link for " Vineet Gupta
2026-10-20 16:19 ` [PATCH bpf-next v3 08/12] bpf: Recover the low 32 bits on the reverse link direction Vineet Gupta
2026-10-20 16:53 ` bot+bpf-ci
2026-10-20 17:20 ` sashiko-bot
2026-10-20 16:19 ` [PATCH bpf-next v3 09/12] bpf: Track low-32 scalar equality across narrowing stack fills Vineet Gupta
2026-10-20 16:19 ` [PATCH bpf-next v3 10/12] selftests/bpf: Cover the low-32 link for " Vineet Gupta
2026-10-20 16:29 ` sashiko-bot
2026-10-20 17:15 ` Vineet Gupta
2026-10-20 16:52 ` bot+bpf-ci
2026-10-20 16:19 ` [PATCH bpf-next v3 11/12] [do not merge] bpf: Track low-32 scalar equality across narrowing stack spills Vineet Gupta
2026-10-20 16:42 ` sashiko-bot
2026-10-20 16:19 ` [PATCH bpf-next v3 12/12] [do not merge] selftests/bpf: Cover the low-32 link for " Vineet Gupta
2026-10-20 16:32 ` sashiko-bot
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-179390@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=vineet.gupta@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