Skip to content

Commit 1abd3fe

Browse files
author
Alexei Starovoitov
committed
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>
2 parents a1e5c46 + 4ac95c6 commit 1abd3fe

2 files changed

Lines changed: 72 additions & 8 deletions

File tree

kernel/bpf/core.c

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1757,6 +1757,12 @@ bool bpf_opcode_in_insntable(u8 code)
17571757
}
17581758

17591759
#ifndef CONFIG_BPF_JIT_ALWAYS_ON
1760+
/* Absolute value of s32 without undefined behavior for S32_MIN */
1761+
static u32 abs_s32(s32 x)
1762+
{
1763+
return x >= 0 ? (u32)x : -(u32)x;
1764+
}
1765+
17601766
/**
17611767
* ___bpf_prog_run - run eBPF program on a given context
17621768
* @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)
19211927
DST = do_div(AX, (u32) SRC);
19221928
break;
19231929
case 1:
1924-
AX = abs((s32)DST);
1925-
AX = do_div(AX, abs((s32)SRC));
1930+
AX = abs_s32((s32)DST);
1931+
AX = do_div(AX, abs_s32((s32)SRC));
19261932
if ((s32)DST < 0)
19271933
DST = (u32)-AX;
19281934
else
@@ -1949,8 +1955,8 @@ static u64 ___bpf_prog_run(u64 *regs, const struct bpf_insn *insn)
19491955
DST = do_div(AX, (u32) IMM);
19501956
break;
19511957
case 1:
1952-
AX = abs((s32)DST);
1953-
AX = do_div(AX, abs((s32)IMM));
1958+
AX = abs_s32((s32)DST);
1959+
AX = do_div(AX, abs_s32((s32)IMM));
19541960
if ((s32)DST < 0)
19551961
DST = (u32)-AX;
19561962
else
@@ -1976,8 +1982,8 @@ static u64 ___bpf_prog_run(u64 *regs, const struct bpf_insn *insn)
19761982
DST = (u32) AX;
19771983
break;
19781984
case 1:
1979-
AX = abs((s32)DST);
1980-
do_div(AX, abs((s32)SRC));
1985+
AX = abs_s32((s32)DST);
1986+
do_div(AX, abs_s32((s32)SRC));
19811987
if (((s32)DST < 0) == ((s32)SRC < 0))
19821988
DST = (u32)AX;
19831989
else
@@ -2003,8 +2009,8 @@ static u64 ___bpf_prog_run(u64 *regs, const struct bpf_insn *insn)
20032009
DST = (u32) AX;
20042010
break;
20052011
case 1:
2006-
AX = abs((s32)DST);
2007-
do_div(AX, abs((s32)IMM));
2012+
AX = abs_s32((s32)DST);
2013+
do_div(AX, abs_s32((s32)IMM));
20082014
if (((s32)DST < 0) == ((s32)IMM < 0))
20092015
DST = (u32)AX;
20102016
else

tools/testing/selftests/bpf/progs/verifier_sdiv.c

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1209,6 +1209,64 @@ __naked void smod32_ri_divisor_neg_1(void)
12091209
: __clobber_all);
12101210
}
12111211

1212+
SEC("socket")
1213+
__description("SDIV32, INT_MIN divided by 2, imm")
1214+
__success __success_unpriv __retval(-1073741824)
1215+
__naked void sdiv32_int_min_div_2_imm(void)
1216+
{
1217+
asm volatile (" \
1218+
w0 = %[int_min]; \
1219+
w0 s/= 2; \
1220+
exit; \
1221+
" :
1222+
: __imm_const(int_min, INT_MIN)
1223+
: __clobber_all);
1224+
}
1225+
1226+
SEC("socket")
1227+
__description("SDIV32, INT_MIN divided by 2, reg")
1228+
__success __success_unpriv __retval(-1073741824)
1229+
__naked void sdiv32_int_min_div_2_reg(void)
1230+
{
1231+
asm volatile (" \
1232+
w0 = %[int_min]; \
1233+
w1 = 2; \
1234+
w0 s/= w1; \
1235+
exit; \
1236+
" :
1237+
: __imm_const(int_min, INT_MIN)
1238+
: __clobber_all);
1239+
}
1240+
1241+
SEC("socket")
1242+
__description("SMOD32, INT_MIN modulo 2, imm")
1243+
__success __success_unpriv __retval(0)
1244+
__naked void smod32_int_min_mod_2_imm(void)
1245+
{
1246+
asm volatile (" \
1247+
w0 = %[int_min]; \
1248+
w0 s%%= 2; \
1249+
exit; \
1250+
" :
1251+
: __imm_const(int_min, INT_MIN)
1252+
: __clobber_all);
1253+
}
1254+
1255+
SEC("socket")
1256+
__description("SMOD32, INT_MIN modulo -2, imm")
1257+
__success __success_unpriv __retval(0)
1258+
__naked void smod32_int_min_mod_neg2_imm(void)
1259+
{
1260+
asm volatile (" \
1261+
w0 = %[int_min]; \
1262+
w0 s%%= -2; \
1263+
exit; \
1264+
" :
1265+
: __imm_const(int_min, INT_MIN)
1266+
: __clobber_all);
1267+
}
1268+
1269+
12121270
#else
12131271

12141272
SEC("socket")

0 commit comments

Comments
 (0)