* [PATCH v3 0/2] bpf: Fix abs(INT_MIN) undefined behavior in interpreter sdiv/smod
@ 2026-03-09 18:57 Jenny Guanni Qu
2026-03-09 18:57 ` [PATCH v3 1/2] bpf: Fix undefined behavior in interpreter sdiv/smod for INT_MIN Jenny Guanni Qu
2026-03-09 18:57 ` [PATCH v3 2/2] selftests/bpf: Add tests for sdiv32/smod32 with INT_MIN dividend Jenny Guanni Qu
0 siblings, 2 replies; 5+ messages in thread
From: Jenny Guanni Qu @ 2026-03-09 18:57 UTC (permalink / raw)
To: bpf; +Cc: daniel, ast, andrii, mykyta.yatsenko5, lkp, Jenny Guanni Qu
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 __safe_abs32() which handles S32_MIN correctly
and replaces all 8 abs((s32)...) call sites.
Patch 2 adds selftests covering sdiv32 and smod32 with INT_MIN
dividend to prevent regression.
Changes since v2:
- Simplified __safe_abs32() to use -(u32)x instead of special-casing
S32_MIN, per Mykyta Yatsenko's suggestion. Casting to u32 before
negating avoids signed overflow entirely.
Changes since v1:
- Moved __safe_abs32() helper above the kerneldoc comment block
for ___bpf_prog_run() to fix build warnings reported by
kernel test robot (W=1 doc warnings)
Jenny Guanni Qu (2):
bpf: Fix undefined behavior in interpreter sdiv/smod for INT_MIN
selftests/bpf: Add tests for sdiv32/smod32 with INT_MIN dividend
kernel/bpf/core.c | 23 ++++---
.../selftests/bpf/progs/verifier_sdiv.c | 58 +++++++++++++++++++
2 files changed, 72 insertions(+), 9 deletions(-)
--
2.34.1
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH v3 1/2] bpf: Fix undefined behavior in interpreter sdiv/smod for INT_MIN 2026-03-09 18:57 [PATCH v3 0/2] bpf: Fix abs(INT_MIN) undefined behavior in interpreter sdiv/smod Jenny Guanni Qu @ 2026-03-09 18:57 ` Jenny Guanni Qu 2026-03-09 21:30 ` Yonghong Song 2026-03-09 18:57 ` [PATCH v3 2/2] selftests/bpf: Add tests for sdiv32/smod32 with INT_MIN dividend Jenny Guanni Qu 1 sibling, 1 reply; 5+ messages in thread From: Jenny Guanni Qu @ 2026-03-09 18:57 UTC (permalink / raw) To: bpf; +Cc: daniel, ast, andrii, mykyta.yatsenko5, lkp, Jenny Guanni Qu The BPF interpreter's signed 32-bit division and modulo handlers use the kernel abs() macro on s32 operands. The abs() macro documentation (include/linux/math.h) explicitly states the result is undefined when the input is the type minimum. When DST contains S32_MIN (0x80000000), abs((s32)DST) triggers undefined behavior and returns S32_MIN unchanged on arm64/x86. This value is then sign-extended to u64 as 0xFFFFFFFF80000000, causing do_div() to compute the wrong result. The verifier's abstract interpretation (scalar32_min_max_sdiv) computes the mathematically correct result for range tracking, creating a verifier/interpreter mismatch that can be exploited for out-of-bounds map value access. Introduce __safe_abs32() which handles S32_MIN correctly by casting to u32 before negating, avoiding signed overflow entirely. Replace all 8 abs((s32)...) call sites in the interpreter's sdiv32/smod32 handlers. Fixes: ec0e2da95f72 ("bpf: Support new signed div/mod instructions.") Signed-off-by: Jenny Guanni Qu <qguanni@gmail.com> --- kernel/bpf/core.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c index 3ece2da55625..0bee54db03c1 100644 --- a/kernel/bpf/core.c +++ b/kernel/bpf/core.c @@ -16,7 +16,6 @@ * Andi Kleen - Fix a few bad bugs and races. * Kris Katterjohn - Added many additional checks in bpf_check_classic() */ - #include <uapi/linux/btf.h> #include <crypto/sha1.h> #include <linux/filter.h> @@ -1736,6 +1735,12 @@ bool bpf_opcode_in_insntable(u8 code) } #ifndef CONFIG_BPF_JIT_ALWAYS_ON +/* Safe absolute value for s32 - abs() is undefined for S32_MIN */ +static inline u32 __safe_abs32(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 @@ -1900,8 +1905,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 = __safe_abs32((s32)DST); + AX = do_div(AX, __safe_abs32((s32)SRC)); if ((s32)DST < 0) DST = (u32)-AX; else @@ -1928,8 +1933,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 = __safe_abs32((s32)DST); + AX = do_div(AX, __safe_abs32((s32)IMM)); if ((s32)DST < 0) DST = (u32)-AX; else @@ -1955,8 +1960,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 = __safe_abs32((s32)DST); + do_div(AX, __safe_abs32((s32)SRC)); if (((s32)DST < 0) == ((s32)SRC < 0)) DST = (u32)AX; else @@ -1982,8 +1987,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 = __safe_abs32((s32)DST); + do_div(AX, __safe_abs32((s32)IMM)); if (((s32)DST < 0) == ((s32)IMM < 0)) DST = (u32)AX; else -- 2.34.1 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v3 1/2] bpf: Fix undefined behavior in interpreter sdiv/smod for INT_MIN 2026-03-09 18:57 ` [PATCH v3 1/2] bpf: Fix undefined behavior in interpreter sdiv/smod for INT_MIN Jenny Guanni Qu @ 2026-03-09 21:30 ` Yonghong Song 0 siblings, 0 replies; 5+ messages in thread From: Yonghong Song @ 2026-03-09 21:30 UTC (permalink / raw) To: Jenny Guanni Qu, bpf; +Cc: daniel, ast, andrii, mykyta.yatsenko5, lkp On 3/9/26 11:57 AM, Jenny Guanni Qu wrote: > The BPF interpreter's signed 32-bit division and modulo handlers use > the kernel abs() macro on s32 operands. The abs() macro documentation > (include/linux/math.h) explicitly states the result is undefined when > the input is the type minimum. When DST contains S32_MIN (0x80000000), > abs((s32)DST) triggers undefined behavior and returns S32_MIN unchanged > on arm64/x86. This value is then sign-extended to u64 as > 0xFFFFFFFF80000000, causing do_div() to compute the wrong result. > > The verifier's abstract interpretation (scalar32_min_max_sdiv) computes > the mathematically correct result for range tracking, creating a > verifier/interpreter mismatch that can be exploited for out-of-bounds > map value access. > > Introduce __safe_abs32() which handles S32_MIN correctly by casting > to u32 before negating, avoiding signed overflow entirely. Replace > all 8 abs((s32)...) call sites in the interpreter's sdiv32/smod32 > handlers. > > Fixes: ec0e2da95f72 ("bpf: Support new signed div/mod instructions.") > Signed-off-by: Jenny Guanni Qu <qguanni@gmail.com> LGTM with a nit below. Acked-by: Yonghong Song <yonghong.song@linux.dev> > --- > kernel/bpf/core.c | 23 ++++++++++++++--------- > 1 file changed, 14 insertions(+), 9 deletions(-) > > diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c > index 3ece2da55625..0bee54db03c1 100644 > --- a/kernel/bpf/core.c > +++ b/kernel/bpf/core.c > @@ -16,7 +16,6 @@ > * Andi Kleen - Fix a few bad bugs and races. > * Kris Katterjohn - Added many additional checks in bpf_check_classic() > */ > - You should not change the above line. > #include <uapi/linux/btf.h> > #include <crypto/sha1.h> > #include <linux/filter.h> > @@ -1736,6 +1735,12 @@ bool bpf_opcode_in_insntable(u8 code) > } > > #ifndef CONFIG_BPF_JIT_ALWAYS_ON > +/* Safe absolute value for s32 - abs() is undefined for S32_MIN */ Maybe /* Safe absolute value for s32 to prevent undefined behavior for abs(S32_MIN) */ ? > +static inline u32 __safe_abs32(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 > @@ -1900,8 +1905,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 = __safe_abs32((s32)DST); > + AX = do_div(AX, __safe_abs32((s32)SRC)); > if ((s32)DST < 0) > DST = (u32)-AX; > else > @@ -1928,8 +1933,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 = __safe_abs32((s32)DST); > + AX = do_div(AX, __safe_abs32((s32)IMM)); > if ((s32)DST < 0) > DST = (u32)-AX; > else > @@ -1955,8 +1960,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 = __safe_abs32((s32)DST); > + do_div(AX, __safe_abs32((s32)SRC)); > if (((s32)DST < 0) == ((s32)SRC < 0)) > DST = (u32)AX; > else > @@ -1982,8 +1987,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 = __safe_abs32((s32)DST); > + do_div(AX, __safe_abs32((s32)IMM)); > if (((s32)DST < 0) == ((s32)IMM < 0)) > DST = (u32)AX; > else ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v3 2/2] selftests/bpf: Add tests for sdiv32/smod32 with INT_MIN dividend 2026-03-09 18:57 [PATCH v3 0/2] bpf: Fix abs(INT_MIN) undefined behavior in interpreter sdiv/smod Jenny Guanni Qu 2026-03-09 18:57 ` [PATCH v3 1/2] bpf: Fix undefined behavior in interpreter sdiv/smod for INT_MIN Jenny Guanni Qu @ 2026-03-09 18:57 ` Jenny Guanni Qu 2026-03-09 21:42 ` Yonghong Song 1 sibling, 1 reply; 5+ messages in thread From: Jenny Guanni Qu @ 2026-03-09 18:57 UTC (permalink / raw) To: bpf; +Cc: daniel, ast, andrii, mykyta.yatsenko5, lkp, Jenny Guanni Qu Add tests to verify that signed 32-bit division and modulo operations produce correct results when the dividend is INT_MIN (0x80000000). These test the fix in the previous commit which replaced abs() with a safe helper to avoid undefined behavior for S32_MIN. Test cases: - SDIV32 INT_MIN / 2 = -1073741824 (imm and reg divisor) - SMOD32 INT_MIN % 2 = 0 (positive and negative divisor) Signed-off-by: Jenny Guanni Qu <qguanni@gmail.com> --- .../selftests/bpf/progs/verifier_sdiv.c | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) 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") -- 2.34.1 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v3 2/2] selftests/bpf: Add tests for sdiv32/smod32 with INT_MIN dividend 2026-03-09 18:57 ` [PATCH v3 2/2] selftests/bpf: Add tests for sdiv32/smod32 with INT_MIN dividend Jenny Guanni Qu @ 2026-03-09 21:42 ` Yonghong Song 0 siblings, 0 replies; 5+ messages in thread From: Yonghong Song @ 2026-03-09 21:42 UTC (permalink / raw) To: Jenny Guanni Qu, bpf; +Cc: daniel, ast, andrii, mykyta.yatsenko5, lkp On 3/9/26 11:57 AM, Jenny Guanni Qu wrote: > Add tests to verify that signed 32-bit division and modulo operations > produce correct results when the dividend is INT_MIN (0x80000000). > > These test the fix in the previous commit which replaced abs() with a > safe helper to avoid undefined behavior for S32_MIN. > > Test cases: > - SDIV32 INT_MIN / 2 = -1073741824 (imm and reg divisor) > - SMOD32 INT_MIN % 2 = 0 (positive and negative divisor) The tests you added below will always succeed with CI without your previous patch. The reason is the default config is to enable jit where the verifier already handles potential overflow properly. > > Signed-off-by: Jenny Guanni Qu <qguanni@gmail.com> > --- > .../selftests/bpf/progs/verifier_sdiv.c | 58 +++++++++++++++++++ > 1 file changed, 58 insertions(+) > > 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); > +} If you have sysctl bpf_jit_enable=0, the above two tests will show failure without your previous patch. With your previous patch, two tests will pass. > + > +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); > +} These two patches will succeed regardless of your previous patch. I suggest you to have more detailed explanation in commit message for the above tests, e.g., how bpf_jit_enable = 0 can tigger the failure without kernel change, etc. > + > + > #else > > SEC("socket") ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-03-09 21:42 UTC | newest] Thread overview: 5+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-03-09 18:57 [PATCH v3 0/2] bpf: Fix abs(INT_MIN) undefined behavior in interpreter sdiv/smod Jenny Guanni Qu 2026-03-09 18:57 ` [PATCH v3 1/2] bpf: Fix undefined behavior in interpreter sdiv/smod for INT_MIN Jenny Guanni Qu 2026-03-09 21:30 ` Yonghong Song 2026-03-09 18:57 ` [PATCH v3 2/2] selftests/bpf: Add tests for sdiv32/smod32 with INT_MIN dividend Jenny Guanni Qu 2026-03-09 21:42 ` Yonghong Song
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox