Merge branch 'bpf-skip-bounds-adjustment-for-conditional-jumps-on-same-scalar-register'

KaFai Wan says:

====================
bpf: Skip bounds adjustment for conditional jumps on same scalar register

This small patchset is about avoid verifier bug warning when conditional
 jumps on same register when the register holds a scalar with range.

v4:
  - make code better. (Alexei)

v3:
  https://lore.kernel.org/bpf/20251031154107.403054-1-kafai.wan@linux.dev/
  - Enhance is_scalar_branch_taken() to handle scalar case. (Eduard)
  - Update the selftest to cover all conditional jump opcodes. (Eduard)

v2:
  https://lore.kernel.org/bpf/20251025053017.2308823-1-kafai.wan@linux.dev/
 - Enhance is_branch_taken() and is_scalar_branch_taken() to handle
   branch direction computation for same register. (Eduard and Alexei)
 - Update the selftest.

v1:
  https://lore.kernel.org/bpf/20251022164457.1203756-1-kafai.wan@linux.dev/
---
====================

Link: https://patch.msgid.link/20251103063108.1111764-1-kafai.wan@linux.dev
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Alexei Starovoitov 2025-11-03 17:43:28 -08:00
commit 11369e6e33
2 changed files with 185 additions and 0 deletions

View File

@ -15993,6 +15993,30 @@ static int is_scalar_branch_taken(struct bpf_reg_state *reg1, struct bpf_reg_sta
s64 smin2 = is_jmp32 ? (s64)reg2->s32_min_value : reg2->smin_value;
s64 smax2 = is_jmp32 ? (s64)reg2->s32_max_value : reg2->smax_value;
if (reg1 == reg2) {
switch (opcode) {
case BPF_JGE:
case BPF_JLE:
case BPF_JSGE:
case BPF_JSLE:
case BPF_JEQ:
return 1;
case BPF_JGT:
case BPF_JLT:
case BPF_JSGT:
case BPF_JSLT:
case BPF_JNE:
return 0;
case BPF_JSET:
if (tnum_is_const(t1))
return t1.value != 0;
else
return (smin1 <= 0 && smax1 >= 0) ? -1 : 1;
default:
return -1;
}
}
switch (opcode) {
case BPF_JEQ:
/* constants, umin/umax and smin/smax checks would be
@ -16439,6 +16463,13 @@ static int reg_set_min_max(struct bpf_verifier_env *env,
if (false_reg1->type != SCALAR_VALUE || false_reg2->type != SCALAR_VALUE)
return 0;
/* We compute branch direction for same SCALAR_VALUE registers in
* is_scalar_branch_taken(). For unknown branch directions (e.g., BPF_JSET)
* on the same registers, we don't need to adjust the min/max values.
*/
if (false_reg1 == false_reg2)
return 0;
/* fallthrough (FALSE) branch */
regs_refine_cond_op(false_reg1, false_reg2, rev_opcode(opcode), is_jmp32);
reg_bounds_sync(false_reg1);

View File

@ -1709,4 +1709,158 @@ __naked void jeq_disagreeing_tnums(void *ctx)
: __clobber_all);
}
SEC("socket")
__description("conditional jump on same register, branch taken")
__not_msg("20: (b7) r0 = 1 {{.*}} R0=1")
__success __log_level(2)
__retval(0) __flag(BPF_F_TEST_REG_INVARIANTS)
__naked void condition_jump_on_same_register(void *ctx)
{
asm volatile(" \
call %[bpf_get_prandom_u32]; \
w8 = 0x80000000; \
r0 &= r8; \
if r0 == r0 goto +1; \
goto l1_%=; \
if r0 >= r0 goto +1; \
goto l1_%=; \
if r0 s>= r0 goto +1; \
goto l1_%=; \
if r0 <= r0 goto +1; \
goto l1_%=; \
if r0 s<= r0 goto +1; \
goto l1_%=; \
if r0 != r0 goto l1_%=; \
if r0 > r0 goto l1_%=; \
if r0 s> r0 goto l1_%=; \
if r0 < r0 goto l1_%=; \
if r0 s< r0 goto l1_%=; \
l0_%=: r0 = 0; \
exit; \
l1_%=: r0 = 1; \
exit; \
" :
: __imm(bpf_get_prandom_u32)
: __clobber_all);
}
SEC("socket")
__description("jset on same register, constant value branch taken")
__not_msg("7: (b7) r0 = 1 {{.*}} R0=1")
__success __log_level(2)
__retval(0) __flag(BPF_F_TEST_REG_INVARIANTS)
__naked void jset_on_same_register_1(void *ctx)
{
asm volatile(" \
r0 = 0; \
if r0 & r0 goto l1_%=; \
r0 = 1; \
if r0 & r0 goto +1; \
goto l1_%=; \
l0_%=: r0 = 0; \
exit; \
l1_%=: r0 = 1; \
exit; \
" :
: __imm(bpf_get_prandom_u32)
: __clobber_all);
}
SEC("socket")
__description("jset on same register, scalar value branch taken")
__not_msg("12: (b7) r0 = 1 {{.*}} R0=1")
__success __log_level(2)
__retval(0) __flag(BPF_F_TEST_REG_INVARIANTS)
__naked void jset_on_same_register_2(void *ctx)
{
asm volatile(" \
/* range [1;2] */ \
call %[bpf_get_prandom_u32]; \
r0 &= 0x1; \
r0 += 1; \
if r0 & r0 goto +1; \
goto l1_%=; \
/* range [-2;-1] */ \
call %[bpf_get_prandom_u32]; \
r0 &= 0x1; \
r0 -= 2; \
if r0 & r0 goto +1; \
goto l1_%=; \
l0_%=: r0 = 0; \
exit; \
l1_%=: r0 = 1; \
exit; \
" :
: __imm(bpf_get_prandom_u32)
: __clobber_all);
}
SEC("socket")
__description("jset on same register, scalar value unknown branch 1")
__msg("3: (b7) r0 = 0 {{.*}} R0=0")
__msg("5: (b7) r0 = 1 {{.*}} R0=1")
__success __log_level(2)
__flag(BPF_F_TEST_REG_INVARIANTS)
__naked void jset_on_same_register_3(void *ctx)
{
asm volatile(" \
/* range [0;1] */ \
call %[bpf_get_prandom_u32]; \
r0 &= 0x1; \
if r0 & r0 goto l1_%=; \
l0_%=: r0 = 0; \
exit; \
l1_%=: r0 = 1; \
exit; \
" :
: __imm(bpf_get_prandom_u32)
: __clobber_all);
}
SEC("socket")
__description("jset on same register, scalar value unknown branch 2")
__msg("4: (b7) r0 = 0 {{.*}} R0=0")
__msg("6: (b7) r0 = 1 {{.*}} R0=1")
__success __log_level(2)
__flag(BPF_F_TEST_REG_INVARIANTS)
__naked void jset_on_same_register_4(void *ctx)
{
asm volatile(" \
/* range [-1;0] */ \
call %[bpf_get_prandom_u32]; \
r0 &= 0x1; \
r0 -= 1; \
if r0 & r0 goto l1_%=; \
l0_%=: r0 = 0; \
exit; \
l1_%=: r0 = 1; \
exit; \
" :
: __imm(bpf_get_prandom_u32)
: __clobber_all);
}
SEC("socket")
__description("jset on same register, scalar value unknown branch 3")
__msg("4: (b7) r0 = 0 {{.*}} R0=0")
__msg("6: (b7) r0 = 1 {{.*}} R0=1")
__success __log_level(2)
__flag(BPF_F_TEST_REG_INVARIANTS)
__naked void jset_on_same_register_5(void *ctx)
{
asm volatile(" \
/* range [-1;1] */ \
call %[bpf_get_prandom_u32]; \
r0 &= 0x2; \
r0 -= 1; \
if r0 & r0 goto l1_%=; \
l0_%=: r0 = 0; \
exit; \
l1_%=: r0 = 1; \
exit; \
" :
: __imm(bpf_get_prandom_u32)
: __clobber_all);
}
char _license[] SEC("license") = "GPL";