[PATCH 12/25] bpf/validate: fix BPF_DIV and BPF_MOD signed part

Marat Khalili marat.khalili at huawei.com
Wed May 6 19:38:30 CEST 2026


Function `eval_divmod` for _unsigned_ division or modulo operation
calculated signed ranges using _signed_ division, which is
mathematically incorrect: unlike some other mathematical operations,
signed and unsigned divisions in the CPU register cyclic ring math are
not equivalent.

E.g. consider the following program with the current validation code:

     Tested program:
         0:  mov r0, #0x0
         1:  lddw r2, #0xaaaaaaaaaaaaaaaa
         3:  mov r3, #0x2
         4:  div r2, r3  ; tested instruction
         5:  mov r0, #0x1
         6:  exit
     Pre-state:
        r2:  -6148914691236517206
        r3:  2
     Post-state:
        r2:  -3074457345618258603 INTERSECT 0x5555555555555555 (!)

After the tested instruction validator considers r2 to equal
0x5555555555555555 if viewed as unsigned (correct, this is
0xaaaaaaaaaaaaaaaaull / 2), but equal -3074457345618258603 or
0xd555555555555555 if viewed as signed, although it cannot be both true.

Additionally, when validating division or modulo of INT64_MIN by -1
overflow happened in the validator possibly triggering an exception.

The following error is shown without sanitizer:

    1/1 DPDK:fast-tests / bpf_autotest FAIL            0.37s
    killed by signal 8 SIGFPE

With sanitizer the following diagnostic is generated:

    lib/bpf/bpf_validate.c:1086:14: runtime error: division of
    -9223372036854775808 by -1 cannot be represented in type 'long int'
        #0 0x0000027484bb in eval_divmod lib/bpf/bpf_validate.c:1086
        #1 0x00000274bcf3 in eval_alu lib/bpf/bpf_validate.c:1280
        #2 0x00000275cb3e in evaluate lib/bpf/bpf_validate.c:3192
        ...

    SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior
    lib/bpf/bpf_validate.c:1086:14

Change logic to copy results from unsigned division into signed. Add
both validation and execution tests for the case that triggered an
exception. Add validation tests for non-constant division to make sure
it is still valid (ranges of the non-constant division or modulo are not
really minimal, this can be addressed in the future).

Fixes: 8021917293d0 ("bpf: add extra validation for input BPF program")
Cc: stable at dpdk.org

Signed-off-by: Marat Khalili <marat.khalili at huawei.com>
---
 app/test/test_bpf.c          |  99 +++++++++++++++++++++++++
 app/test/test_bpf_validate.c | 135 +++++++++++++++++++++++++++++++++++
 lib/bpf/bpf_validate.c       |  38 +++-------
 3 files changed, 244 insertions(+), 28 deletions(-)

diff --git a/app/test/test_bpf.c b/app/test/test_bpf.c
index 69e84f0cab56..744bf02f7356 100644
--- a/app/test/test_bpf.c
+++ b/app/test/test_bpf.c
@@ -393,6 +393,13 @@ cmp_res(const char *func, uint64_t exp_rc, uint64_t ret_rc,
 	return ret;
 }
 
+/* Empty prepare function */
+static void
+dummy_prepare(void *arg)
+{
+	RTE_SET_USED(arg);
+}
+
 /* store immediate test-cases */
 static const struct ebpf_insn test_store1_prog[] = {
 	{
@@ -3157,6 +3164,70 @@ static const struct ebpf_insn test_ld_mbuf3_prog[] = {
 	},
 };
 
+/* divide INT64_MIN by -1 */
+static const struct ebpf_insn test_int64min_udiv_uint64max_prog[] = {
+	/* Load INT64_MIN into r0 */
+	{
+		.code = (BPF_LD | BPF_IMM | EBPF_DW),
+		.dst_reg = EBPF_REG_0,
+		.imm = (int32_t)INT64_MIN,
+	},
+	{
+		.imm = (int32_t)(INT64_MIN >> 32),
+	},
+	/* Divide r0 by immediate -1 */
+	{
+		.code = (EBPF_ALU64 | BPF_DIV | BPF_K),
+		.dst_reg = EBPF_REG_0,
+		.imm = -1,
+	},
+	/* Exit for correctness otherwise */
+	{
+		.code = (BPF_JMP | EBPF_EXIT),
+	},
+};
+
+static int
+test_int64min_udiv_uint64max_check(uint64_t rc, const void *arg)
+{
+	RTE_SET_USED(arg);
+	/* 0x8000000000000000ull / 0xFFFFFFFFFFFFFFFFull == 0 */
+	TEST_ASSERT_EQUAL(rc, 0, "expected 0, found %#" PRIx64, rc);
+	return TEST_SUCCESS;
+}
+
+/* modulo INT64_MIN by -1 */
+static const struct ebpf_insn test_int64min_umod_uint64max_prog[] = {
+	/* Load INT64_MIN into r0 */
+	{
+		.code = (BPF_LD | BPF_IMM | EBPF_DW),
+		.dst_reg = EBPF_REG_0,
+		.imm = (int32_t)INT64_MIN,
+	},
+	{
+		.imm = (int32_t)(INT64_MIN >> 32),
+	},
+	/* Modulo r0 by immediate -1 */
+	{
+		.code = (EBPF_ALU64 | BPF_MOD | BPF_K),
+		.dst_reg = EBPF_REG_0,
+		.imm = -1,
+	},
+	/* Exit for correctness otherwise */
+	{
+		.code = (BPF_JMP | EBPF_EXIT),
+	},
+};
+
+static int
+test_int64min_umod_uint64max_check(uint64_t rc, const void *arg)
+{
+	RTE_SET_USED(arg);
+	/* 0x8000000000000000ull % 0xFFFFFFFFFFFFFFFFull == 0x8000000000000000ull  */
+	TEST_ASSERT_EQUAL(rc, (uint64_t)INT64_MIN, "expected INT64_MIN, found %#" PRIx64, rc);
+	return TEST_SUCCESS;
+}
+
 /* all bpf test cases */
 static const struct bpf_test tests[] = {
 	{
@@ -3465,6 +3536,34 @@ static const struct bpf_test tests[] = {
 		/* mbuf as input argument is not supported on 32 bit platform */
 		.allow_fail = (sizeof(uint64_t) != sizeof(uintptr_t)),
 	},
+	{
+		.name = "test_int64min_udiv_uint64max",
+		.arg_sz = sizeof(struct dummy_vect8),
+		.prm = {
+			.ins = test_int64min_udiv_uint64max_prog,
+			.nb_ins = RTE_DIM(test_int64min_udiv_uint64max_prog),
+			.prog_arg = {
+				.type = RTE_BPF_ARG_PTR,
+				.size = sizeof(struct dummy_vect8),
+			},
+		},
+		.prepare = dummy_prepare,
+		.check_result = test_int64min_udiv_uint64max_check,
+	},
+	{
+		.name = "test_int64min_umod_uint64max",
+		.arg_sz = 1,
+		.prm = {
+			.ins = test_int64min_umod_uint64max_prog,
+			.nb_ins = RTE_DIM(test_int64min_umod_uint64max_prog),
+			.prog_arg = {
+				.type = RTE_BPF_ARG_PTR,
+				.size = 1,
+			},
+		},
+		.prepare = dummy_prepare,
+		.check_result = test_int64min_umod_uint64max_check,
+	},
 };
 
 static int
diff --git a/app/test/test_bpf_validate.c b/app/test/test_bpf_validate.c
index 995f7363b80f..aada6e110337 100644
--- a/app/test/test_bpf_validate.c
+++ b/app/test/test_bpf_validate.c
@@ -1154,6 +1154,141 @@ test_alu64_add_x_scalar_scalar(void)
 REGISTER_FAST_TEST(bpf_validate_alu64_add_x_scalar_scalar_autotest, NOHUGE_OK, ASAN_OK,
 	test_alu64_add_x_scalar_scalar);
 
+/* 64-bit division and modulo of UINT64_MAX*2/3. */
+static int
+test_alu64_div_mod_big_constant(void)
+{
+	const uint64_t dividend = UINT64_MAX / 3 * 2;
+	static const uint64_t divisors[] = {
+		1,
+		2,
+		3,
+		UINT64_MAX / 3,
+		INT64_MAX,
+		INT64_MIN,
+		UINT64_MAX / 3 * 2,
+		UINT64_MAX / 4 * 3,
+		UINT64_MAX,
+	};
+	for (int index = 0; index != RTE_DIM(divisors); ++index) {
+		const uint64_t divisor = divisors[index];
+
+		TEST_ASSERT_SUCCESS(verify_instruction((struct verify_instruction_param){
+			.tested_instruction = {
+				.code = (EBPF_ALU64 | BPF_DIV | BPF_X),
+			},
+			.pre.dst = make_singleton_domain(dividend),
+			.pre.src = make_singleton_domain(divisor),
+			.post.dst = make_singleton_domain(dividend / divisor),
+		}), "(EBPF_ALU64 | BPF_DIV | BPF_X) check, index=%d", index);
+
+		TEST_ASSERT_SUCCESS(verify_instruction((struct verify_instruction_param){
+			.tested_instruction = {
+				.code = (EBPF_ALU64 | BPF_MOD | BPF_X),
+			},
+			.pre.dst = make_singleton_domain(dividend),
+			.pre.src = make_singleton_domain(divisor),
+			.post.dst = make_singleton_domain(dividend % divisor),
+		}), "(EBPF_ALU64 | BPF_MOD | BPF_X) check, index=%d", index);
+	}
+
+	return TEST_SUCCESS;
+}
+
+REGISTER_FAST_TEST(bpf_validate_alu64_div_mod_big_constant_autotest, NOHUGE_OK, ASAN_OK,
+	test_alu64_div_mod_big_constant);
+
+/* 64-bit division and modulo of UINT64_MAX/3..UINT64_MAX*2/3 by a constant. */
+static int
+test_alu64_div_mod_big_range(void)
+{
+	const uint64_t dividend_first = UINT64_MAX / 3;
+	const uint64_t dividend_last = UINT64_MAX / 3 * 2;
+	static const uint64_t divisors[] = {
+		1,
+		2,
+		3,
+		UINT64_MAX / 3,
+		INT64_MAX,
+		INT64_MIN,
+		UINT64_MAX / 3 * 2,
+		UINT64_MAX / 4 * 3,
+		UINT64_MAX,
+	};
+	for (int index = 0; index != RTE_DIM(divisors); ++index) {
+		const uint64_t divisor = divisors[index];
+
+		TEST_ASSERT_SUCCESS(verify_instruction((struct verify_instruction_param){
+			.tested_instruction = {
+				.code = (EBPF_ALU64 | BPF_DIV | BPF_X),
+			},
+			.pre.dst = make_unsigned_domain(dividend_first, dividend_last),
+			.pre.src = make_singleton_domain(divisor),
+			.post.dst = make_unsigned_domain(0, dividend_last),
+		}), "(EBPF_ALU64 | BPF_DIV | BPF_X) check, index=%d", index);
+
+		TEST_ASSERT_SUCCESS(verify_instruction((struct verify_instruction_param){
+			.tested_instruction = {
+				.code = (EBPF_ALU64 | BPF_MOD | BPF_X),
+			},
+			.pre.dst = make_unsigned_domain(dividend_first, dividend_last),
+			.pre.src = make_singleton_domain(divisor),
+			.post.dst = make_unsigned_domain(0, RTE_MIN(dividend_last, divisor - 1)),
+		}), "(EBPF_ALU64 | BPF_MOD | BPF_X) check, index=%d", index);
+	}
+
+	return TEST_SUCCESS;
+}
+
+REGISTER_FAST_TEST(bpf_validate_alu64_div_mod_big_range_autotest, NOHUGE_OK, ASAN_OK,
+	test_alu64_div_mod_big_range);
+
+/* 64-bit division and modulo of INT64_MIN by -1. */
+static int
+test_alu64_div_mod_overflow(void)
+{
+	TEST_ASSERT_SUCCESS(verify_instruction((struct verify_instruction_param){
+		.tested_instruction = {
+			.code = (EBPF_ALU64 | BPF_DIV | BPF_K),
+			.imm = -1,
+		},
+		.pre.dst = make_singleton_domain(INT64_MIN),
+		.post.dst = make_singleton_domain(0),
+	}), "(EBPF_ALU64 | BPF_DIV | BPF_K) check");
+
+	TEST_ASSERT_SUCCESS(verify_instruction((struct verify_instruction_param){
+		.tested_instruction = {
+			.code = (EBPF_ALU64 | BPF_DIV | BPF_X),
+		},
+		.pre.dst = make_singleton_domain(INT64_MIN),
+		.pre.src = make_singleton_domain(-1),
+		.post.dst = make_singleton_domain(0),
+	}), "(EBPF_ALU64 | BPF_DIV | BPF_X) check");
+
+	TEST_ASSERT_SUCCESS(verify_instruction((struct verify_instruction_param){
+		.tested_instruction = {
+			.code = (EBPF_ALU64 | BPF_MOD | BPF_K),
+			.imm = -1,
+		},
+		.pre.dst = make_singleton_domain(INT64_MIN),
+		.post.dst = make_singleton_domain(INT64_MIN),
+	}), "(EBPF_ALU64 | BPF_MOD | BPF_K) check");
+
+	TEST_ASSERT_SUCCESS(verify_instruction((struct verify_instruction_param){
+		.tested_instruction = {
+			.code = (EBPF_ALU64 | BPF_MOD | BPF_X),
+		},
+		.pre.dst = make_singleton_domain(INT64_MIN),
+		.pre.src = make_singleton_domain(-1),
+		.post.dst = make_singleton_domain(INT64_MIN),
+	}), "(EBPF_ALU64 | BPF_MOD | BPF_X) check");
+
+	return TEST_SUCCESS;
+}
+
+REGISTER_FAST_TEST(bpf_validate_alu64_div_mod_overflow_autotest, NOHUGE_OK, ASAN_OK,
+	test_alu64_div_mod_overflow);
+
 /* 64-bit negation when interval first element is INT64_MIN. */
 static int
 test_alu64_neg_int64min_first(void)
diff --git a/lib/bpf/bpf_validate.c b/lib/bpf/bpf_validate.c
index 79c8679ac535..b784777bbb6b 100644
--- a/lib/bpf/bpf_validate.c
+++ b/lib/bpf/bpf_validate.c
@@ -932,8 +932,7 @@ eval_mul(struct bpf_reg_val *rd, const struct bpf_reg_val *rs, size_t opsz,
 }
 
 static const char *
-eval_divmod(uint32_t op, struct bpf_reg_val *rd, struct bpf_reg_val *rs,
-	size_t opsz, uint64_t msk)
+eval_divmod(uint32_t op, struct bpf_reg_val *rd, struct bpf_reg_val *rs, uint64_t msk)
 {
 	/* both operands are constants */
 	if (rd->u.min == rd->u.max && rs->u.min == rs->u.max) {
@@ -954,34 +953,17 @@ eval_divmod(uint32_t op, struct bpf_reg_val *rd, struct bpf_reg_val *rs,
 		rd->u.min = 0;
 	}
 
-	/* if we have 32-bit values - extend them to 64-bit */
-	if (opsz == sizeof(uint32_t) * CHAR_BIT) {
-		rd->s.min = (int32_t)rd->s.min;
-		rd->s.max = (int32_t)rd->s.max;
-		rs->s.min = (int32_t)rs->s.min;
-		rs->s.max = (int32_t)rs->s.max;
-	}
-
-	/* both operands are constants */
-	if (rd->s.min == rd->s.max && rs->s.min == rs->s.max) {
-		if (rs->s.max == 0)
-			return "division by 0";
-		if (op == BPF_DIV) {
-			rd->s.min /= rs->s.min;
-			rd->s.max /= rs->s.max;
-		} else {
-			rd->s.min %= rs->s.min;
-			rd->s.max %= rs->s.max;
-		}
-	} else if (op == BPF_MOD) {
-		rd->s.min = RTE_MAX(rd->s.max, 0);
-		rd->s.min = RTE_MIN(rd->s.min, 0);
+	if (rd->u.min >= (uint64_t)INT64_MIN || rd->u.max <= (uint64_t)INT64_MAX) {
+		/*
+		 * All values have the same sign bit, which means range
+		 * contiguous as unsigned is also contiguous as signed,
+		 * so we can just reuse it without any changes.
+		 */
+		rd->s.min = rd->u.min;
+		rd->s.max = rd->u.max;
 	} else
 		eval_smax_bound(rd, msk);
 
-	rd->s.max &= msk;
-	rd->s.min &= msk;
-
 	return NULL;
 }
 
@@ -1165,7 +1147,7 @@ eval_alu(struct bpf_verifier *bvf, const struct ebpf_insn *ins)
 	else if (op == BPF_MUL)
 		eval_mul(rd, &rs, opsz, msk);
 	else if (op == BPF_DIV || op == BPF_MOD)
-		err = eval_divmod(op, rd, &rs, opsz, msk);
+		err = eval_divmod(op, rd, &rs, msk);
 	else if (op == BPF_NEG)
 		eval_neg(rd, opsz, msk);
 	else if (op == EBPF_MOV)
-- 
2.43.0



More information about the dev mailing list