BPF List
 help / color / mirror / Atom feed
From: Vineet Gupta <vineet.gupta@linux.dev>
To: sashiko-reviews@lists.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: Mon, 26 Oct 2026 15:01:28 -0700	[thread overview]
Message-ID: <c6955dac-49bd-4b9a-9313-49f7b9753eb1@linux.dev> (raw)
In-Reply-To: <sashiko-outbox-179354@kernel.org>

On 10/20/26 9:32 AM, sashiko-bot@kernel.org wrote:
> 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.

Yes. It said the narrowing propagates to r2, which is the opposite of what
the test is named for. Fixed.


>> +	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()?

No, fixed in v4.

The narrowing now happens while the value is still on the stack, before 
the fill, as you suggest and as zext_narrowing_spill_reaches_fill 
already does. As written, the fill had already dropped any link, so the 
test would have passed even if the 1-byte spill had formed one.


> 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?

nope it can't: v4 drops the test.

Thx,
-Vineet

> 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";


      reply	other threads:[~2026-10-26 22:01 UTC|newest]

Thread overview: 35+ 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-26 22:52     ` Vineet Gupta
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-26 23:06     ` Vineet Gupta
2026-10-20 17:09   ` bot+bpf-ci
2026-10-26 20:30     ` Vineet Gupta
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-26 18:36     ` Vineet Gupta
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-26 20:46     ` Vineet Gupta
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-26 19:57     ` Vineet Gupta
2026-10-20 17:20   ` sashiko-bot
2026-10-26 21:28     ` Vineet Gupta
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-26 20:21     ` Vineet Gupta
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-26 21:51     ` Vineet Gupta
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
2026-10-26 22:01     ` Vineet Gupta [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=c6955dac-49bd-4b9a-9313-49f7b9753eb1@linux.dev \
    --to=vineet.gupta@linux.dev \
    --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