All of lore.kernel.org
 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 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.