Commit 11369e6e authored by Alexei Starovoitov's avatar Alexei Starovoitov
Browse files

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: default avatarAlexei Starovoitov <ast@kernel.org>
parents 5dae7453 9f32bfec
Loading
Loading
Loading
Loading
+31 −0
Original line number Diff line number Diff line
@@ -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);
+154 −0
Original line number Diff line number Diff line
@@ -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";