* [PATCH v2 0/2] bpf: Fix abs(INT_MIN) undefined behavior in interpreter sdiv/smod
@ 2026-03-06 19:08 Jenny Guanni Qu
2026-03-06 19:08 ` [PATCH v2 1/2] bpf: Fix undefined behavior in interpreter sdiv/smod for INT_MIN Jenny Guanni Qu
2026-03-06 19:08 ` [PATCH v2 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-06 19:08 UTC (permalink / raw)
To: bpf; +Cc: daniel, ast, andrii, 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 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 v2 1/2] bpf: Fix undefined behavior in interpreter sdiv/smod for INT_MIN 2026-03-06 19:08 [PATCH v2 0/2] bpf: Fix abs(INT_MIN) undefined behavior in interpreter sdiv/smod Jenny Guanni Qu @ 2026-03-06 19:08 ` Jenny Guanni Qu 2026-03-09 18:22 ` Mykyta Yatsenko 2026-03-06 19:08 ` [PATCH v2 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-06 19:08 UTC (permalink / raw) To: bpf; +Cc: daniel, ast, andrii, 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 and 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..663b9af6d19b 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 : x == S32_MIN ? (u32)S32_MIN : (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 v2 1/2] bpf: Fix undefined behavior in interpreter sdiv/smod for INT_MIN 2026-03-06 19:08 ` [PATCH v2 1/2] bpf: Fix undefined behavior in interpreter sdiv/smod for INT_MIN Jenny Guanni Qu @ 2026-03-09 18:22 ` Mykyta Yatsenko 2026-03-09 18:53 ` Guanni Qu 0 siblings, 1 reply; 5+ messages in thread From: Mykyta Yatsenko @ 2026-03-09 18:22 UTC (permalink / raw) To: Jenny Guanni Qu, bpf; +Cc: daniel, ast, andrii, lkp, Jenny Guanni Qu Jenny Guanni Qu <qguanni@gmail.com> writes: > 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 and 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..663b9af6d19b 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 : x == S32_MIN ? (u32)S32_MIN : (u32)-x; can we write it as x >= 0 ? (u32)x : -(u32)x; ? By casting to u32 first, the negation operates in unsigned arithmetic where overflow does not happen — it wraps. Please correct me if I'm wrong. > +} > + > /** > * ___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 [flat|nested] 5+ messages in thread
* Re: [PATCH v2 1/2] bpf: Fix undefined behavior in interpreter sdiv/smod for INT_MIN 2026-03-09 18:22 ` Mykyta Yatsenko @ 2026-03-09 18:53 ` Guanni Qu 0 siblings, 0 replies; 5+ messages in thread From: Guanni Qu @ 2026-03-09 18:53 UTC (permalink / raw) To: Mykyta Yatsenko; +Cc: bpf, daniel, ast, andrii, lkp You're right, casting to u32 before negating avoids the UB entirely without needing a special case for S32_MIN. Will send v3 with that simplification. Thanks! On Mon, Mar 9, 2026 at 11:22 AM Mykyta Yatsenko <mykyta.yatsenko5@gmail.com> wrote: > > Jenny Guanni Qu <qguanni@gmail.com> writes: > > > 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 and 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..663b9af6d19b 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 : x == S32_MIN ? (u32)S32_MIN : (u32)-x; > can we write it as x >= 0 ? (u32)x : -(u32)x; ? > By casting to u32 first, the negation operates in unsigned arithmetic > where overflow does not happen — it wraps. > Please correct me if I'm wrong. > > +} > > + > > /** > > * ___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 [flat|nested] 5+ messages in thread
* [PATCH v2 2/2] selftests/bpf: Add tests for sdiv32/smod32 with INT_MIN dividend 2026-03-06 19:08 [PATCH v2 0/2] bpf: Fix abs(INT_MIN) undefined behavior in interpreter sdiv/smod Jenny Guanni Qu 2026-03-06 19:08 ` [PATCH v2 1/2] bpf: Fix undefined behavior in interpreter sdiv/smod for INT_MIN Jenny Guanni Qu @ 2026-03-06 19:08 ` Jenny Guanni Qu 1 sibling, 0 replies; 5+ messages in thread From: Jenny Guanni Qu @ 2026-03-06 19:08 UTC (permalink / raw) To: bpf; +Cc: daniel, ast, andrii, 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
end of thread, other threads:[~2026-03-09 18:53 UTC | newest] Thread overview: 5+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-03-06 19:08 [PATCH v2 0/2] bpf: Fix abs(INT_MIN) undefined behavior in interpreter sdiv/smod Jenny Guanni Qu 2026-03-06 19:08 ` [PATCH v2 1/2] bpf: Fix undefined behavior in interpreter sdiv/smod for INT_MIN Jenny Guanni Qu 2026-03-09 18:22 ` Mykyta Yatsenko 2026-03-09 18:53 ` Guanni Qu 2026-03-06 19:08 ` [PATCH v2 2/2] selftests/bpf: Add tests for sdiv32/smod32 with INT_MIN dividend Jenny Guanni Qu
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox