Re: [PATCH] bpf: Prevent invalid u32 bounds in __reg32_deduce_bounds()
From: Andrea Righi
Date: Thu Mar 05 2026 - 02:05:16 EST
Hi Eduard,
On Wed, Mar 04, 2026 at 11:23:13AM -0800, Eduard Zingerman wrote:
> On Fri, 2026-02-20 at 20:07 +0100, Andrea Righi wrote:
> > When refining register bounds, __reg32_deduce_bounds can derive u32
> > min/max from 64-bit or s32 bounds and assign them with max_t/min_t.
> >
> > If the existing u32 range and the derived range do not overlap (e.g.,
> > u64 says [0, 1] while u32 was [2, 2] from an earlier path), the
> > intersection is empty and the new u32_min_value can end up greater than
> > u32_max_value, triggering the following warning:
> >
> > verifier bug: REG INVARIANTS VIOLATION (false_reg1): range bounds violation
> > u64=[0x0, 0x1] s64=[0x0, 0x1] u32=[0x3, 0x1] s32=[0x0, 0x1] var_off=(0x0, 0x1)
> > WARNING: kernel/bpf/verifier.c:2742 at reg_bounds_sanity_check
> > Call Trace:
> > reg_bounds_sanity_check+0xbc/0x1e0
> > reg_set_min_max+0x1a2/0x1f0
> > check_cond_jmp_op+0x5d2/0x1980
> > do_check_common+0x2b0f/0x3410
> > do_check_subprogs+0xcd/0x180
> > bpf_check+0x33fe/0x3850
> > bpf_prog_load+0x7d7/0xee0
> > __sys_bpf+0xea2/0x2e30
> >
> > This was triggered by the scx CI while loading the scx_layered sched_ext
> > scheduler [1].
> >
> > Fix by only applying the derived u32 bounds when the resulting range is
> > valid (u32_min <= u32_max).
> >
> > [1] https://github.com/sched-ext/scx/pull/3349
> >
> > Fixes: c1efab6468fd5 ("bpf: derive subreg bounds from full bounds when upper 32 bits are constant")
> > Signed-off-by: Andrea Righi <arighi@xxxxxxxxxx>
> > ---
>
> Hi Andrea,
>
> Sorry for delayed response.
>
> Currently the assumption is that all ranges/tnum tracked as a part of
> a register state are valid, and I don't think we'd like to sidestep
> this assumption. Eventually is_scalar_branch_taken() and
> adjust_scalar_min_max_vals() should be unified to avoid possibility
> for mismatch between is_scalar_branch_taken() predictions (when it
> assumes that some branch can be taken by failing to predict jump)
> and adjust_scalar_min_max_vals() adjustments (when it infers that in
> fact register state is empty for the branch).
>
> As it turns out, the problem you report is a bit easier to fix.
> After minimizing the failing file with Emil's help I derived the
> following test case that captures the problem:
>
> SEC("socket")
> __success
> __flag(BPF_F_TEST_REG_INVARIANTS)
> __naked void signed_unsined_intersection32(void *ctx)
> {
> asm volatile(" \
> call %[bpf_get_prandom_u32]; \
> w0 &= 0xffffffff; \
> if w0 < 0x3 goto 1f; \ /* on fall-through u32 range [3..U32_MAX] */
> if w0 s> 0x1 goto 1f; \ /* on fall-through s32 range [S32_MIN..1] *
> if w0 s< 0x0 goto 1f; \ /* the above ranges can be narrowed to [S32_MIN..-1] */
> r10 = 0; \ /* meaning that condition can be predicted, */
> 1: exit; \ /* but verifier is not smart enough at the moment. */
> " :
> : __imm(bpf_get_prandom_u32)
> : __clobber_all);
> }
>
> This branch has verifier modifications making it smart enough to
> process this case: https://github.com/eddyz87/bpf/tree/cnum-sync-bounds .
> With some additional verification: https://github.com/eddyz87/cnum-verif .
> I'm working towards submitting the fix, need to decide on what to do
> with reg_bounds.c, as this test is now not smart enough.
I see, yeah, my approach is more of a defensive fix, the right
architectural fix is to make the verifier smart enough to never produce
this inconsistent state, which is what your branch is doing IIUC. So feel
free to ignore this patch once you land the proper fix.
Thanks,
-Andrea