From: sashiko-bot@kernel.org
To: "Vineet Gupta" <vineet.gupta@linux.dev>
Cc: bpf@vger.kernel.org
Subject: Re: [PATCH bpf-next v3 12/12] [do not merge] selftests/bpf: Cover the low-32 link for narrowing stack spills
Date: Tue, 20 Oct 2026 16:32:07 +0000 [thread overview]
Message-ID: <sashiko-outbox-179354@kernel.org> (raw)
In-Reply-To: <20261020161907.2115379-13-vineet.gupta@linux.dev>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] selftests/bpf: fill before narrow in zext_narrowing_spill_byte_no_link()
- [Medium] selftests/bpf: tautological test in sext_fill_of_narrowing_spill_drops_zext()
- [Low] selftests/bpf: stale inline comment in zext_narrowing_spill_drops_link()
--
commit a341902044eb0b749136bbae6f269759e4c0ffcd
Author: Vineet Gupta <vineet.gupta@linux.dev>
selftests/bpf: Cover the low-32 link for narrowing stack spills
This patch adds six verifier selftests to verifier_linked_scalars.c to test
low-32 register linking behavior across narrowing stack spills and subsequent
fills.
> diff --git a/tools/testing/selftests/bpf/progs/verifier_linked_scalars.c b/tools/testing/selftests/bpf/progs/verifier_linked_scalars.c
> index ab78285153fb1..8ade4836e1b2f 100644
> --- a/tools/testing/selftests/bpf/progs/verifier_linked_scalars.c
> +++ b/tools/testing/selftests/bpf/progs/verifier_linked_scalars.c
> @@ -1217,6 +1217,128 @@ __naked void zext_fill_byte_forms_no_link(void)
> : __clobber_all);
> }
>
[ ... ]
> +/*
> + * A narrowing spill stores only the source's low 32 bits, so the slot is their
> + * zero-extension. Filling it back is a mov, though, and that drops the link
> + * for the same reason zext_chain_drops_link does, so a later narrowing of the
> + * source does not reach the filled register.
> + */
> +SEC("socket")
> +__failure
> +__msg("div by zero")
> +__naked void zext_narrowing_spill_drops_link(void)
> +{
> + asm volatile (" \
> + call %[bpf_get_prandom_u32]; \
> + r6 = r0; \
> + call %[bpf_get_prandom_u32]; \
> + r0 <<= 32; \
> + r6 |= r0; /* r6 = full 64-bit unknown */ \
> + *(u32 *)(r10 - 8) = r6; /* narrowing spill, forms the link */ \
> + r2 = *(u32 *)(r10 - 8); /* fill it back */ \
> + if w6 != 0 goto 1f; /* narrows r6, propagates to r2 */ \
[Severity: Low]
This isn't a bug, but is this inline comment in
zext_narrowing_spill_drops_link() left over from the earlier version of the
test? Since the preceding fill into r2 drops BPF_REG_LINK_ZEXT, narrowing w6
here does not propagate to r2.
> + if r2 == 0 goto 1f; \
> + r0 /= 0; \
> +1: \
> + r0 = 0; \
> + exit; \
> +" :
> + : __imm(bpf_get_prandom_u32)
> + : __clobber_all);
> +}
> +
[ ... ]
> +/*
> + * A sub-word spill is below the low-32 model, so no link is formed there.
> + * The fill is a byte load too, so this pins the spill-side and fill-side
> + * size gates together rather than either on its own.
> + */
> +SEC("socket")
> +__failure __msg("div by zero")
> +__naked void zext_narrowing_spill_byte_no_link(void)
> +{
> + asm volatile (" \
> + call %[bpf_get_prandom_u32]; \
> + r6 = r0; \
> + call %[bpf_get_prandom_u32]; \
> + r0 <<= 32; \
> + r6 |= r0; \
> + *(u8 *)(r10 - 8) = r6; /* 1-byte spill: no link */ \
> + r2 = *(u8 *)(r10 - 8); \
> + if w6 != 0 goto 1f; \
> + if r2 == 0 goto 1f; /* not deduced */ \
[Severity: Medium]
Does zext_narrowing_spill_byte_no_link() actually test the size gate in
check_stack_write_fixed_off()?
Here, r2 is filled from fp-8 before w6 is narrowed:
*(u8 *)(r10 - 8) = r6; /* 1-byte spill: no link */
r2 = *(u8 *)(r10 - 8);
if w6 != 0 goto 1f;
As demonstrated by zext_narrowing_spill_drops_link() above, filling a
BPF_REG_LINK_ZEXT slot into r2 before narrowing w6 drops the zero-extending
link on the fill, so narrowing w6 afterwards would not propagate to r2 even if
the 1-byte spill had formed a link.
Should the narrowing branch "if w6 != 0 goto 1f" happen while the value is
still on the stack before filling r2, matching the instruction order in
zext_narrowing_spill_reaches_fill()?
> + r0 /= 0; \
> +1: \
> + r0 = 0; \
> + exit; \
> +" :
> + : __imm(bpf_get_prandom_u32)
> + : __clobber_all);
> +}
> +
> #ifdef CAN_USE_MOVSX
[ ... ]
> @@ -1800,6 +1952,35 @@ __naked void sext_byte_fill_drops_sext_link(void)
> }
> #endif /* CAN_USE_MOVSX */
>
> +/*
> + * The same, with the zero-extending link coming from a narrowing spill rather
> + * than a mov. The spill truncated the slot to 32 bits, so the fill is not
> + * narrowing and assigns no link kind of its own; the slot's zero-extending
> + * one must not survive the sign-extending load.
> + */
> +SEC("socket")
> +__failure __msg("div by zero")
> +__naked void sext_fill_of_narrowing_spill_drops_zext(void)
> +{
> + asm volatile (" \
> + call %[bpf_get_prandom_u32]; \
> + r6 = r0; \
> + call %[bpf_get_prandom_u32]; \
> + r0 <<= 32; \
> + r6 |= r0; /* r6 = full 64-bit unknown */ \
> + *(u32 *)(r10 - 8) = r6; /* narrowing spill: slot is ZEXT */ \
> + r2 = *(s32 *)(r10 - 8); /* sign-extending, not narrowing */ \
> + if w6 != -1 goto 1f; /* slot low 32 bits are all ones */ \
> + if r2 s>= 0 goto 1f; /* taken only if r2 was rebuilt zext */ \
> + r0 /= 0; \
[Severity: Medium]
Can sext_fill_of_narrowing_spill_drops_zext() ever reach the is_ldsx link
clearing in check_mem_access() with BPF_REG_LINK_ZEXT still intact?
In check_stack_write_fixed_off(), the 4-byte narrowing spill
"*(u32 *)(r10 - 8) = r6" truncates fp-8 to 32 bits via
zext_32_to_64(&ss->spilled_ptr) and sets BPF_REG_LINK_ZEXT.
When "r2 = *(s32 *)(r10 - 8)" then runs in check_stack_read_fixed_off(),
get_reg_width(reg) is at most 32, so narrowing is false. Because size == 4,
spill_size == 4, and !narrowing all hold, check_stack_read_fixed_off() calls
assign_scalar_id_before_mov(env, reg) before copying *reg to
state->regs[dst_regno]:
kernel/bpf/verifier.c:check_stack_read_fixed_off() {
...
if (env->bpf_capable && size == 4 &&
(subreg_link || (spill_size == 4 && !narrowing)))
assign_scalar_id_before_mov(env, reg);
state->regs[dst_regno] = *reg;
...
}
And assign_scalar_id_before_mov() immediately clears BPF_REG_LINK_ZEXT and
assigns a fresh scalar ID unlinked from r6:
kernel/bpf/verifier.c:assign_scalar_id_before_mov() {
if (src_reg->link_kind && src_reg->link_kind != BPF_REG_LINK_SEXT)
bpf_clear_scalar_id(src_reg);
if (!src_reg->id && !tnum_is_const(src_reg->var_off))
src_reg->id = ++env->id_gen;
}
So BPF_REG_LINK_ZEXT is already cleared by the normal 4-byte fill path (the
same path tested by zext_narrowing_spill_drops_link()) before the is_ldsx
handling in check_mem_access() runs.
Additionally, even if r2 kept r6's ID and BPF_REG_LINK_ZEXT past
check_mem_access(), coerce_reg_to_size_sx() restricts r2's 64-bit bounds to
[S32_MIN, S32_MAX]. When "if w6 != -1 goto 1f" calls sync_linked_regs(),
rebuilding r2 with zext_32_to_64() yields 0xffffffff, which is disjoint from
[S32_MIN, S32_MAX], causing range_bounds_violation() to revert r2 to its prior
bounds and still hit "r0 /= 0".
> +1: \
> + r0 = 0; \
> + exit; \
> +" :
> + : __imm(bpf_get_prandom_u32)
> + : __clobber_all);
> +}
> +
> #endif /* CAN_USE_LDSX */
>
> char _license[] SEC("license") = "GPL";
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261020161907.2115379-1-vineet.gupta@linux.dev?part=12
prev parent reply other threads:[~2026-10-20 16:32 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
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 [this message]
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-179354@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 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.