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: [RFC bpf-next 0/6] bpf: track scalar equality across the low 32 bits
Date: Fri, 14 Aug 2026 16:19:39 -0700 [thread overview]
Message-ID: <20260814231945.3884596-1-vineet.gupta@linux.dev> (raw)
The verifier's linked-scalar machinery tracks register equality only for the
full 64 bits (optionally with a constant delta). There is no way to record
"these two registers share just their low 32 bits", so a 32-bit mov from a
source with unknown high bits has to drop the relationship entirely, and a
later narrowing of the source never reaches the destination:
| r6 = ... /* full 64-bit unknown */
| w7 = w6 /* 32-bit zero-extending mov */
| if w6 != 0 goto .Lxx /* not taken: r6's low 32 bits are 0 */
| if w7 == 0 goto .Lok <-- not deduced today
The same gap exists for the 32-bit sign extension and was the
motivation for this patchset.
| 0: (61) r2 = *(u32 *)(r1 +24)
| 1: (bf) r0 = (s32)r2
| 2: (56) if w2 != 0x0 goto pc+10 ; R2=0 (branch taken)
| ...
| 12: (95) exit
This matters more with bpf-gcc than clang: gcc emits far more sign extensions,
and the resulting rejections are real which show up in three ways
- callback exit codes rejected with "R0 ... should have been in [0, 1]"
- the errno-or-zero return pattern (verify_pkcs7_sig and many lsm.s progs)
where a value clamped to [-4095, 0] is re-widened to [S32_MIN, S32_MAX]
- and loops whose induction variable is sign-extended each iteration failing
with "The sequence of 8193 jumps is too complex".
This series adds a low-32-only equality link in two flavours: the high half
is either zero (zero-extending mov) or the sign-extension of the low field
(32-bit sign extension) and teaches sync_linked_regs() to rebuild the
destination accordingly.
1-2 NFC prep. Turn ->precise into a u8 flags field, then move the existing
linked-scalar flags out of the top bits of ->id into it. ->id becomes a
plain 32-bit identifier with no masking anywhere, which collapses
check_scalar_ids() to a single check_ids(). Because ->flags sits past
every memcmp() window used for state comparison, patch 2 also adds
link_flags_match() and calls it from regs_exact(), restoring the
discrimination the compound-id key used to provide for free.
3-4 The zero-extending flavour, BPF_FLAG_SUBREG_ZEXT, plus tests.
5-6 The sign-extending flavour, BPF_FLAG_SUBREG_SEXT, plus tests.
Sent as RFC mainly for the design questions below; the code is in shape.
Notes and open questions
========================
- An earlier version detected loop headers to decide when forming an in-loop
link was safe, but using Eduard's in works SCEV loop-analysis. However the
conclusion was that was not the right problem/solution as the ensuing issue
could be triggered in current codebase (w/o this series with a little tweak
of the test:
- regsafe() now checks the low-32 link flavour *above* the explore_alu_limits
and !precise short-circuits, while the pre-existing BPF_FLAG_ADD_CONST check
sits below them. The argument for checking early applies to both. Moving the
ADD_CONST one makes regsafe() stricter on a path that predates this series --
a pruning change worth measuring separately -- so it is left alone and the
asymmetry is called out in a comment. Opinions welcome.
- reconstruct_sext32() rebuilds var_off with tnum_range() over the low-32
signed bounds, so known bits that the range does not imply are lost. The
zero-extending arm keeps the base's exact low-32 tnum. Sound but asymmetric;
a follow-up could derive the tnum from tnum_subreg(src->var_off) instead.
- Only the 32-bit sign extension forms a link. (s8)/(s16) do not, so the flag
alone carries the width and no extra field is needed. They are not seen in
codegen so far.
Testing
=======
Baseline: bpf-next:
2026-08-14 f2aaa6215910 riscv, bpf: Fix missing sign-ext for signed 1-byte and 2-byte kfunc args
Full selftest runs with bpf-gcc and clang (-mcpu=v4).
- clang: no regressions (no improvements either)
- bpf-gcc:
Before: Summary: 635/5362 PASSED, 7786 SKIPPED, 115/412 FAILED
After : Summary: 644/5439 PASSED, 8506 SKIPPED, 106/350 FAILED
The measurable pass improvements come with the sign-extension patch;
the zero-extension one is groundwork plus its own test.
+ "... should have been in [0, 1], [-4095, 0]"
All 21 instances gone
+ of "The sequence of 8193 jumps is too complex."
Down from 7 to 3
+ One regression vs. baseline: iters/iter_obfuscate_counter:FAIL
It is now runaway to 1M, root-cause has been analysed and something
to do with with bpf-gcc's branch codegen and costing.
For completeness, full diff of bpf-gcc run vs. bpf-next baseline
1. Existing tests OK now
> #25/1 bpf_ip_check_defrag/v4:OK
> #25/2 bpf_ip_check_defrag/v6:OK
> #25 bpf_ip_check_defrag:OK
> #26/5 bpf_iter/task_tid:OK
> #26/6 bpf_iter/task_pid:OK
> #26/7 bpf_iter/task_pidfd:OK
> #26/8 bpf_iter/task_sleepable:OK
> #26/14 bpf_iter/tcp4:OK
> #26/15 bpf_iter/tcp6:OK
> #26/16 bpf_iter/udp4:OK
> #26/17 bpf_iter/udp6:OK
> #26/25 bpf_iter/bpf_hash_map:OK
> #26/37 bpf_iter/ksym:OK
> #26/38 bpf_iter/bpf_sockmap_map_iter_fd:OK
> #36 bpf_sockmap_map_iter_fd:OK
> #82/8 cgroup_xattr/read_cgroupfs_xattr:OK
> #82 cgroup_xattr:OK
> #112/2 dynptr/test_dynptr_data:OK
> #112/3 dynptr/test_dynptr_copy:OK
> #112/4 dynptr/test_dynptr_copy_xdp:OK
> #112/5 dynptr/test_dynptr_memset_zero:OK
> #112/6 dynptr/test_dynptr_memset_notzero:OK
> #112/7 dynptr/test_dynptr_memset_zero_offset:OK
> #112/8 dynptr/test_dynptr_memset_zero_adjusted:OK
> #112/9 dynptr/test_dynptr_memset_overflow:OK
> #112/10 dynptr/test_dynptr_memset_overflow_offset:OK
> #112/11 dynptr/test_dynptr_memset_readonly:OK
> #112/12 dynptr/test_dynptr_memset_xdp_chunks:OK
> #112/13 dynptr/test_ringbuf:OK
> #112/14 dynptr/test_skb_readonly:OK
> #112/15 dynptr/test_dynptr_skb_data:OK
> #112/16 dynptr/test_dynptr_skb_meta_data:OK
> #112/17 dynptr/test_dynptr_skb_meta_flags:OK
> #112/18 dynptr/test_adjust:OK
> #112/19 dynptr/test_adjust_err:OK
> #112/20 dynptr/test_zero_size_dynptr:OK
> #112/21 dynptr/test_dynptr_is_null:OK
> #112/22 dynptr/test_dynptr_is_rdonly:OK
> #112/23 dynptr/test_dynptr_clone:OK
> #112/25 dynptr/test_dynptr_skb_strcmp:OK
> #112/27 dynptr/test_probe_read_user_dynptr:OK
> #112/28 dynptr/test_probe_read_kernel_dynptr:OK
> #112/29 dynptr/test_probe_read_user_str_dynptr:OK
> #112/30 dynptr/test_probe_read_kernel_str_dynptr:OK
> #112/31 dynptr/test_copy_from_user_dynptr:OK
> #112/32 dynptr/test_copy_from_user_str_dynptr:OK
> #112/33 dynptr/test_copy_from_user_task_dynptr:OK
> #112/34 dynptr/test_copy_from_user_task_str_dynptr:OK
> #117 exe_ctx:OK
> #239/9 mem_rdonly_untrusted/mixed_mem_type:OK
> #239 mem_rdonly_untrusted:OK
> #418 setget_sockopt:OK
> #441/7 sockmap_basic/sockmap copy:OK
> #441/8 sockmap_basic/sockhash copy:OK
> #441 sockmap_basic:OK
> #605/16 verifier_global_ptr_args/anything_to_untrusted_mem:OK
> #605 verifier_global_ptr_args:OK
> #614/1 verifier_iterating_callbacks/unsafe_on_2nd_iter:OK
> #721/1 verify_pkcs7_sig/pkcs7_sig_from_map:OK
2. New tests added pass
> #622/25 verifier_linked_scalars/subreg_eq_zext_mov_narrow:OK
> #622/26 verifier_linked_scalars/zext_mov_keeps_add_const_src:OK
> #622/27 verifier_linked_scalars/zext_dest_driven_does_not_narrow_base:OK
> #622/28 verifier_linked_scalars/sext_linked_low_narrow_to_zero:OK
> #622/29 verifier_linked_scalars/sext_linked_separate_dest_narrow_to_zero:OK
> #622/30 verifier_linked_scalars/sext_narrow_branch_on_source:OK
> #622/31 verifier_linked_scalars/sext_narrow_copied_back:OK
> #622/32 verifier_linked_scalars/sext_narrow_inplace_pre_copy:OK
> #622/33 verifier_linked_scalars/sext_narrow_spill_fill:OK
> #622/34 verifier_linked_scalars/sext_resext_preserves_range:OK
> #622/35 verifier_linked_scalars/sext_in_loop_converges:OK
> #622/36 verifier_linked_scalars/sext_in_loop_separate_dest_index:OK
> #622/37 verifier_linked_scalars/zext_mov_from_sext_src_zero_extends:OK
> #622/38 verifier_linked_scalars/sext_mov_keeps_add_const_src:OK
> #622/39 verifier_linked_scalars/sext_dest_driven_does_not_narrow_base:OK
3. Regression
< #171/47 iters/iter_obfuscate_counter:OK
Vineet Gupta (6):
bpf: turn bpf_reg_state->precise into a flags field [NFC]
bpf: move the linked-scalar flags into bpf_reg_state->flags [NFC]
bpf: support low-32 subreg scalar linking for zero-extending movs
selftests/bpf: cover low-32 subreg-equal link for zero-extending movs
bpf: support low-32 subreg scalar linking for sign-extending movs
selftests/bpf: cover 32-bit sign-extension low-32 links
include/linux/bpf_verifier.h | 51 +-
kernel/bpf/backtrack.c | 22 +-
kernel/bpf/log.c | 6 +-
kernel/bpf/states.c | 106 +++-
kernel/bpf/verifier.c | 226 ++++++-
.../selftests/bpf/progs/verifier_bounds.c | 10 +-
.../bpf/progs/verifier_linked_scalars.c | 550 +++++++++++++++++-
.../selftests/bpf/progs/verifier_reg_equal.c | 16 +-
8 files changed, 890 insertions(+), 97 deletions(-)
--
2.53.0-Meta
next reply other threads:[~2026-08-14 23:20 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-14 23:19 Vineet Gupta [this message]
2026-08-14 23:19 ` [RFC bpf-next 1/6] bpf: turn bpf_reg_state->precise into a flags field [NFC] Vineet Gupta
2026-08-14 23:19 ` [RFC bpf-next 2/6] bpf: move the linked-scalar flags into bpf_reg_state->flags [NFC] Vineet Gupta
2026-08-14 23:34 ` sashiko-bot
2026-08-14 23:19 ` [RFC bpf-next 3/6] bpf: support low-32 subreg scalar linking for zero-extending movs Vineet Gupta
2026-08-14 23:19 ` [RFC bpf-next 4/6] selftests/bpf: cover low-32 subreg-equal link " Vineet Gupta
2026-08-14 23:27 ` sashiko-bot
2026-08-14 23:19 ` [RFC bpf-next 5/6] bpf: support low-32 subreg scalar linking for sign-extending movs Vineet Gupta
2026-08-14 23:19 ` [RFC bpf-next 6/6] selftests/bpf: cover 32-bit sign-extension low-32 links Vineet Gupta
2026-08-14 23:27 ` 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=20260814231945.3884596-1-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 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.