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: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Alexei Starovoitov 2026-03-21 13:12:17 -07:00
commit 1abd3feb36
2 changed files with 72 additions and 8 deletions

View File

@ -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 @@ select_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 @@ select_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 @@ select_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 @@ select_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

View File

@ -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")