From: Vineet Gupta <vineet.gupta@linux.dev>
To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org,
eddyz87@gmail.com, memxor@gmail.com
Cc: martin.lau@linux.dev, song@kernel.org, yonghong.song@linux.dev,
jolsa@kernel.org, emil@etsalapatis.com, ihor.solodrai@linux.dev,
john.fastabend@gmail.com, shuah@kernel.org, bpf@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org,
Vineet Gupta <vineet.gupta@linux.dev>
Subject: [PATCH bpf-next v2 04/13] selftests/bpf: cover the low-32 link for zero-extending movs
Date: Thu, 10 Sep 2026 22:16:26 +0530 [thread overview]
Message-ID: <20260910164635.459558-5-vineet.gupta@linux.dev> (raw)
In-Reply-To: <20260910164635.459558-1-vineet.gupta@linux.dev>
Exercise each decision the previous patch adds:
- zext_mov_narrow_src: narrowing the source reaches the destination
- zext_narrow_dst_keeps_base: the reverse must not happen, the base's
high bits are still unknown
- zext_u32_src_is_full_link: a provably-u32 source keeps taking the
full-equality path
- zext_self_mov_no_link: a self-mov mints no id
- zext_chain_keeps_link: a linked source survives a further 32-bit mov
- zext_no_sync_when_base_has_delta, zext_no_sync_from_subreg_base: a
delta on either side stops propagation, in both directions
- zext_sync_between_two_subregs: two low-32 links on one base do reach
each other
- zext_unlinked_path_stays_reachable: only one path links r7 to r8, so
the guarded div stays reachable
- zext_mov_breaks_add_const_src: forming the link drops a delta link on
the source, as the narrow-source path has always done
Written in asm so the bytecode is the same whichever compiler built the
suite.
These three check an outcome rather than a mechanism: the states they
compare differ in ids, contents or ranges, so regsafe() keeps them apart
for reasons of its own and no single check can be disabled to make them
fail.
Signed-off-by: Vineet Gupta <vineet.gupta@linux.dev>
---
v2: was RFC 4/6.
- all tests live in verifier_linked_scalars.c (Eduard)
- numeric labels, and the verifier_bounds regex left as-is bar the id
(Eduard)
- dropped the LLM-written commentary and the redundant second
bpf_get_prandom_u32() (Eduard)
- covers the seven cases listed on RFC 4/6
- the dest-driven test had the polarity flaw sashiko found on 6/6; its
== guard now discriminates, verified by disabling the guard it targets
- three tests renamed for what they check, see the cover letter
.../bpf/progs/verifier_linked_scalars.c | 256 ++++++++++++++++++
1 file changed, 256 insertions(+)
diff --git a/tools/testing/selftests/bpf/progs/verifier_linked_scalars.c b/tools/testing/selftests/bpf/progs/verifier_linked_scalars.c
index da6cb961a520..65cb0efd268f 100644
--- a/tools/testing/selftests/bpf/progs/verifier_linked_scalars.c
+++ b/tools/testing/selftests/bpf/progs/verifier_linked_scalars.c
@@ -710,4 +710,260 @@ l_exit_%=: \
: __clobber_all);
}
+/*
+ * A 32-bit mov from a wide source shares only the low 32 bits. Narrowing the
+ * source must reach the destination through that link.
+ */
+SEC("socket")
+__success
+__naked void zext_mov_narrow_src(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r6 = r0; \
+ w7 = w6; /* forms the link */ \
+ if w6 != 0 goto 1f; /* narrows r6, propagates to r7 */ \
+ if w7 == 0 goto 1f; \
+ r0 /= 0; \
+1: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * The reverse does not hold: narrowing the low-32 link says nothing about the
+ * base's high bits, so r6 must stay unknown and the div stays reachable.
+ */
+SEC("socket")
+__failure __msg("div by zero")
+__naked void zext_narrow_dst_keeps_base(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r6 = r0; \
+ w7 = w6; /* forms the link */ \
+ if r7 != 0 goto 1f; /* narrows r7, must not propagate to r6 */ \
+ if r6 == 0 goto 1f; /* taken only if r6 wrongly narrowed */ \
+ r0 /= 0; \
+1: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * A provably-u32 source takes the full-equality path, not the low-32 one:
+ * narrowing the destination must reach the source.
+ */
+SEC("socket")
+__success
+__naked void zext_u32_src_is_full_link(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ w6 = w0; /* r6 provably u32 */ \
+ w7 = w6; /* full link, not low-32 */ \
+ if r7 > 10 goto 1f; /* narrows r7, propagates to r6 */ \
+ if r6 > 10 goto 2f; \
+ goto 1f; \
+2: \
+ r0 /= 0; \
+1: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * A self-mov has nothing to link, so it must not mint an id for r6.
+ */
+SEC("socket")
+__success __log_level(2)
+/* an id would print as R6=scalar(id=N.lo32,smin=... */
+__msg("(bc) w6 = w6 {{.*}} R6=scalar(smin=0,")
+__naked void zext_self_mov_no_link(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r6 = r0; \
+ r6 ^= 0; /* drop the id */ \
+ w6 = w6; /* forms no link */ \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * A low-32-linked source keeps its id and flag across a further 32-bit mov,
+ * so narrowing the base still reaches the end of the chain.
+ */
+SEC("socket")
+__success
+__naked void zext_chain_keeps_link(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r6 = r0; \
+ w7 = w6; /* forms the link */ \
+ w8 = w7; /* link survives the 2nd mov */ \
+ if w6 != 0 goto 1f; /* narrows r6, propagates to r8 */ \
+ if w8 == 0 goto 1f; \
+ r0 /= 0; \
+1: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * A delta on either side is not modelled together with a low-32 link, so no
+ * range propagates: here the branch register carries the delta.
+ */
+SEC("socket")
+__failure __msg("div by zero")
+__naked void zext_no_sync_when_base_has_delta(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r6 = r0; \
+ w7 = w6; /* forms the link */ \
+ r8 = r6; \
+ r8 += 3; /* delta on the branch reg */ \
+ if r8 != 3 goto 1f; /* must not propagate to r7 */ \
+ if w7 == 0 goto 1f; \
+ r0 /= 0; \
+1: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * ... and here the low-32 link is the branch register, so the register
+ * carrying the delta must not be narrowed either.
+ */
+SEC("socket")
+__failure __msg("div by zero")
+__naked void zext_no_sync_from_subreg_base(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r6 = r0; \
+ w7 = w6; /* forms the link */ \
+ r8 = r6; \
+ r8 += 3; /* delta on r8 */ \
+ if w7 != 0 goto 1f; /* must not propagate to r8 */ \
+ if r8 == 3 goto 1f; /* taken only if r8 wrongly narrowed */ \
+ r0 /= 0; \
+1: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * Two low-32 links on the same base do propagate to each other: both are the
+ * zero-extension of the same low 32 bits.
+ */
+SEC("socket")
+__success
+__naked void zext_sync_between_two_subregs(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r6 = r0; \
+ w7 = w6; /* two links on one base */ \
+ w8 = w6; \
+ if w7 != 0 goto 1f; /* narrows r7, propagates to r8 */ \
+ if w8 == 0 goto 1f; \
+ r0 /= 0; \
+1: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * Only one of the two paths links r7 to r8, so the narrowing of w8 reaches r7
+ * on one and not the other and the div stays reachable. This checks the
+ * outcome, not the mechanism: the two states differ in their ids and contents,
+ * so regsafe() has many reasons to keep them apart and disabling any single
+ * one of its checks does not make this fail.
+ */
+SEC("socket")
+__failure __msg("div by zero")
+__flag(BPF_F_TEST_STATE_FREQ)
+__naked void zext_unlinked_path_stays_reachable(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r6 = r0; \
+ r6 &= 1; \
+ if r6 >= 1 goto 2f; \
+ /* explored first: r7 is a low-32 link of r8 */ \
+ call %[bpf_get_prandom_u32]; \
+ r8 = r0; \
+ w7 = w8; /* forms the link */ \
+ goto 1f; \
+2: \
+ /* runtime path: r7 unrelated to r8 */ \
+ call %[bpf_get_prandom_u32]; \
+ r8 = r0; \
+ call %[bpf_get_prandom_u32]; \
+ w7 = w0; /* no link here */ \
+1: \
+ if w8 != 0 goto 3f; /* propagates to r7 only if linked */ \
+ if w7 == 0 goto 3f; \
+ r0 /= 0; \
+3: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+/*
+ * Forming the link calls assign_scalar_id_before_mov(), which drops a delta
+ * link on the source. That is what the narrow-source path has always done,
+ * so a wide source behaves the same: r5 stops tracking r6.
+ */
+SEC("socket")
+__failure __msg("div by zero")
+__naked void zext_mov_breaks_add_const_src(void)
+{
+ asm volatile (" \
+ call %[bpf_get_prandom_u32]; \
+ r6 = r0; \
+ r5 = r6; /* r5, r6 linked */ \
+ r5 += 3; /* r5 = base + 3 */ \
+ w7 = w5; /* breaks r5's delta link */ \
+ if r6 > 9 goto 1f; /* r6 in [0, 9] */ \
+ if r5 < 13 goto 1f; /* taken only if r5 still linked */ \
+ r0 /= 0; \
+1: \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
--
2.53.0-Meta
next prev parent reply other threads:[~2026-09-10 16:47 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-10 16:46 [PATCH bpf-next v2 00/13] bpf: track scalar equality across the low 32 bits Vineet Gupta
2026-09-10 16:46 ` [PATCH bpf-next v2 01/13] bpf: move linked-scalar flags out of bpf_reg_state->id [NFC] Vineet Gupta
2026-09-10 17:52 ` bot+bpf-ci
2026-09-10 16:46 ` [PATCH bpf-next v2 02/13] bpf: compare linked-scalar kinds in regs_exact() Vineet Gupta
2026-09-10 16:46 ` [PATCH bpf-next v2 03/13] bpf: track low-32 scalar equality across zero-extending movs Vineet Gupta
2026-09-10 17:52 ` bot+bpf-ci
2026-09-11 9:29 ` Vineet Gupta
2026-09-10 16:46 ` Vineet Gupta [this message]
2026-09-10 16:46 ` [PATCH bpf-next v2 05/13] bpf: keep the range across a sign extension that cannot change it Vineet Gupta
2026-09-10 17:52 ` bot+bpf-ci
2026-09-11 10:37 ` Vineet Gupta
2026-09-10 16:46 ` [PATCH bpf-next v2 06/13] selftests/bpf: cover sign extensions that cannot change the range Vineet Gupta
2026-09-10 16:46 ` [PATCH bpf-next v2 07/13] bpf: track low-32 scalar equality across sign-extending movs Vineet Gupta
2026-09-10 17:52 ` bot+bpf-ci
2026-09-11 10:00 ` Vineet Gupta
2026-09-10 16:46 ` [PATCH bpf-next v2 08/13] selftests/bpf: cover the low-32 link for " Vineet Gupta
2026-09-10 17:52 ` bot+bpf-ci
2026-09-11 8:00 ` Vineet Gupta
2026-09-10 16:46 ` [PATCH bpf-next v2 09/13] bpf: track low-32 scalar equality across narrowing stack fills Vineet Gupta
2026-09-10 16:46 ` [PATCH bpf-next v2 10/13] selftests/bpf: cover the low-32 link for " Vineet Gupta
2026-09-10 17:31 ` bot+bpf-ci
2026-09-11 5:07 ` Vineet Gupta
2026-09-10 16:46 ` [PATCH bpf-next v2 11/13] bpf: record what a narrowing spill actually stores Vineet Gupta
2026-09-10 16:46 ` [PATCH bpf-next v2 12/13] bpf: track low-32 scalar equality across narrowing stack spills Vineet Gupta
2026-09-10 16:46 ` [PATCH bpf-next v2 13/13] selftests/bpf: cover the low-32 link for " Vineet Gupta
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=20260910164635.459558-5-vineet.gupta@linux.dev \
--to=vineet.gupta@linux.dev \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=emil@etsalapatis.com \
--cc=ihor.solodrai@linux.dev \
--cc=john.fastabend@gmail.com \
--cc=jolsa@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=martin.lau@linux.dev \
--cc=memxor@gmail.com \
--cc=shuah@kernel.org \
--cc=song@kernel.org \
--cc=yonghong.song@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
all inboxes | Powered by JetHome®