summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexei Starovoitov <ast@kernel.org>2026-03-21 13:12:17 -0700
committerAlexei Starovoitov <ast@kernel.org>2026-03-21 13:12:17 -0700
commit1abd3feb36636b28d7722851fc0c8d392a87e12d (patch)
treed084cd92f13b4fe6f846dbf832b889d84e49996d
parenta1e5c46eaed3151be93e1aec9af0d8f8db79b8f6 (diff)
parent4ac95c65efeaf0c010199b2b2f5f78b06f28dab6 (diff)
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>
-rw-r--r--kernel/bpf/core.c22
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_sdiv.c58
2 files changed, 72 insertions, 8 deletions
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 9e126be33755..7b675a451ec8 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -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
diff --git a/tools/testing/selftests/bpf/progs/verifier_sdiv.c b/tools/testing/selftests/bpf/progs/verifier_sdiv.c
index 148d2299e5b4..fd59d57e8e37 100644
--- a/tools/testing/selftests/bpf/progs/verifier_sdiv.c
+++ b/tools/testing/selftests/bpf/progs/verifier_sdiv.c
@@ -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")