Commit 1abd3feb authored by Alexei Starovoitov's avatar Alexei Starovoitov
Browse files

Merge branch 'bpf-fix-abs-int_min-undefined-behavior-in-interpreter-sdiv-smod'

Jenny Guanni Qu says:

====================
bpf: Fix abs(INT_MIN) undefined behavior in interpreter sdiv/smod

The BPF interpreter's signed 32-bit division and modulo handlers use
abs() on s32 operands, which is undefined for S32_MIN. This causes
the interpreter to compute wrong results, creating a mismatch with
the verifier's range tracking.

For example, INT_MIN / 2 returns 0x40000000 instead of the correct
0xC0000000. The verifier tracks the correct range, so a crafted BPF
program can exploit the mismatch for out-of-bounds map value access
(confirmed by KASAN).

Patch 1 introduces abs_s32() which handles S32_MIN correctly and
replaces all 8 abs((s32)...) call sites. s32 is the only affected
case -- the s64 handlers do not use abs().

Patch 2 adds selftests covering sdiv32 and smod32 with INT_MIN
dividend to prevent regression.

Changes since v4:
  - Renamed __safe_abs32() to abs_s32() and dropped inline keyword
    per Alexei Starovoitov's feedback

Changes since v3:
  - Fixed stray blank line deletion in the file header
  - Improved comment per Yonghong Song's suggestion
  - Added JIT vs interpreter context to selftest commit message

Changes since v2:
  - Simplified to use -(u32)x per Mykyta Yatsenko's suggestion

Changes since v1:
  - Moved helper above kerneldoc comment block to fix build warnings
====================

Link: https://patch.msgid.link/20260311011116.2108005-1-qguanni@gmail.com


Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parents a1e5c46e 4ac95c65
Loading
Loading
Loading
Loading
+14 −8
Original line number Diff line number Diff line
@@ -1757,6 +1757,12 @@ bool bpf_opcode_in_insntable(u8 code)
}

#ifndef CONFIG_BPF_JIT_ALWAYS_ON
/* Absolute value of s32 without undefined behavior for S32_MIN */
static u32 abs_s32(s32 x)
{
	return x >= 0 ? (u32)x : -(u32)x;
}

/**
 *	___bpf_prog_run - run eBPF program on a given context
 *	@regs: is the array of MAX_BPF_EXT_REG eBPF pseudo-registers
@@ -1921,8 +1927,8 @@ static u64 ___bpf_prog_run(u64 *regs, const struct bpf_insn *insn)
			DST = do_div(AX, (u32) SRC);
			break;
		case 1:
			AX = abs((s32)DST);
			AX = do_div(AX, abs((s32)SRC));
			AX = abs_s32((s32)DST);
			AX = do_div(AX, abs_s32((s32)SRC));
			if ((s32)DST < 0)
				DST = (u32)-AX;
			else
@@ -1949,8 +1955,8 @@ static u64 ___bpf_prog_run(u64 *regs, const struct bpf_insn *insn)
			DST = do_div(AX, (u32) IMM);
			break;
		case 1:
			AX = abs((s32)DST);
			AX = do_div(AX, abs((s32)IMM));
			AX = abs_s32((s32)DST);
			AX = do_div(AX, abs_s32((s32)IMM));
			if ((s32)DST < 0)
				DST = (u32)-AX;
			else
@@ -1976,8 +1982,8 @@ static u64 ___bpf_prog_run(u64 *regs, const struct bpf_insn *insn)
			DST = (u32) AX;
			break;
		case 1:
			AX = abs((s32)DST);
			do_div(AX, abs((s32)SRC));
			AX = abs_s32((s32)DST);
			do_div(AX, abs_s32((s32)SRC));
			if (((s32)DST < 0) == ((s32)SRC < 0))
				DST = (u32)AX;
			else
@@ -2003,8 +2009,8 @@ static u64 ___bpf_prog_run(u64 *regs, const struct bpf_insn *insn)
			DST = (u32) AX;
			break;
		case 1:
			AX = abs((s32)DST);
			do_div(AX, abs((s32)IMM));
			AX = abs_s32((s32)DST);
			do_div(AX, abs_s32((s32)IMM));
			if (((s32)DST < 0) == ((s32)IMM < 0))
				DST = (u32)AX;
			else
+58 −0
Original line number Diff line number Diff line
@@ -1209,6 +1209,64 @@ __naked void smod32_ri_divisor_neg_1(void)
	: __clobber_all);
}

SEC("socket")
__description("SDIV32, INT_MIN divided by 2, imm")
__success __success_unpriv __retval(-1073741824)
__naked void sdiv32_int_min_div_2_imm(void)
{
	asm volatile ("					\
	w0 = %[int_min];				\
	w0 s/= 2;					\
	exit;						\
"	:
	: __imm_const(int_min, INT_MIN)
	: __clobber_all);
}

SEC("socket")
__description("SDIV32, INT_MIN divided by 2, reg")
__success __success_unpriv __retval(-1073741824)
__naked void sdiv32_int_min_div_2_reg(void)
{
	asm volatile ("					\
	w0 = %[int_min];				\
	w1 = 2;						\
	w0 s/= w1;					\
	exit;						\
"	:
	: __imm_const(int_min, INT_MIN)
	: __clobber_all);
}

SEC("socket")
__description("SMOD32, INT_MIN modulo 2, imm")
__success __success_unpriv __retval(0)
__naked void smod32_int_min_mod_2_imm(void)
{
	asm volatile ("					\
	w0 = %[int_min];				\
	w0 s%%= 2;					\
	exit;						\
"	:
	: __imm_const(int_min, INT_MIN)
	: __clobber_all);
}

SEC("socket")
__description("SMOD32, INT_MIN modulo -2, imm")
__success __success_unpriv __retval(0)
__naked void smod32_int_min_mod_neg2_imm(void)
{
	asm volatile ("					\
	w0 = %[int_min];				\
	w0 s%%= -2;					\
	exit;						\
"	:
	: __imm_const(int_min, INT_MIN)
	: __clobber_all);
}


#else

SEC("socket")