public inbox for bpf@vger.kernel.org
 help / color / mirror / Atom feed
* [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

* [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 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

* 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