Function `eval_umax_bits`  when calculating the mask matching the
register value counted leading zeroes in 64-bit representation but then
subtracted it from the instruction width (32 for BPF_ALU instructions)
producing zero or negative mask length, all unsupported by macro
`RTE_LEN2MASK`. This function is used by functions `eval_and`, `eval_or`
and `eval_xor` evaluating `BPF_ALU | BPF_AND`, `BPF_ALU | BPF_OR` and
`BPF_ALU | BPF_XOR` correspondingly

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

    Tested program:
        0:  mov r0, #0x0
        1:  ldxdw r2, [r1 + 0]
        2:  jgt r2, #0x7fffffff, L7
        3:  jslt r2, #0x0, L7
        4:  jsgt r2, #0x7fffffff, L7
        5:  and32 r2, #0x7fffffff  ; tested instruction
        6:  mov r0, #0x1
        7:  exit
    Pre-state:
       r2:  0..INT32_MAX
    Post-state:
       r2:  0..INT64_MAX

At step 6 validator presumed that r2 may have values up to INT64_MAX,
which is impossible for the 32-bit bitwise operation on 32-bit operands
and could cause false positives.

With sanitizer the following diagnostic is generated:

    lib/bpf/bpf_validate.c:821:9: runtime error: shift exponent 65 is
    too large for 64-bit type 'long unsigned int'
        #0 0x00000837d9b5 in eval_umax_bits lib/bpf/bpf_validate.c:821
        #1 0x00000837d9f3 in eval_uand_max lib/bpf/bpf_validate.c:828
        #2 0x00000837df83 in eval_and lib/bpf/bpf_validate.c:851
        #3 0x000008384faf in eval_alu lib/bpf/bpf_validate.c:1155
        #4 0x000008396c24 in evaluate lib/bpf/bpf_validate.c:2999

Similarly for `or32 r2, #0x0`, `xor32 r2, #0x0`.

Found using ESBMC (https://esbmc.github.io/docs/) through an agentic
verification workflow, with integer-overflow and undefined-shift checks
enabled.

To fix the issue change to subtracting clz64 result from 64 instead of
instruction width, add tests. Remove no-longer-used opsz argument
(truncation of inputs to 32 bits is handled by the caller `eval_alu`).

Fixes: 8021917293d0 ("bpf: add extra validation for input BPF program")
Cc: [email protected]

Reported-by: Sebti Mouelhi <[email protected]>
Signed-off-by: Marat Khalili <[email protected]>
Acked-by: Konstantin Ananyev <[email protected]>
---
 app/test/test_bpf_validate.c | 45 ++++++++++++++++++++++++++++++++++++
 lib/bpf/bpf_validate.c       | 43 ++++++++++++++++------------------
 2 files changed, 65 insertions(+), 23 deletions(-)

diff --git a/app/test/test_bpf_validate.c b/app/test/test_bpf_validate.c
index 066f1fa156ae..e1c35a41a4f9 100644
--- a/app/test/test_bpf_validate.c
+++ b/app/test/test_bpf_validate.c
@@ -1277,6 +1277,48 @@ verify_comparison(struct verify_instruction_param prm, 
bool also_signed)
 
 /* TESTS FOR SPECIFIC INSTRUCTIONS */
 
+/* 32-bit bitwise AND between a scalar range and immediate, triggering UB. */
+static int
+test_alu32_and_k_ub(void)
+{
+       return verify_instruction((struct verify_instruction_param){
+               .tested_instruction = {
+                       .code = (BPF_ALU | BPF_AND | BPF_K),
+                       .imm = INT32_MAX,
+               },
+               .pre.dst = make_unsigned_domain(0, INT32_MAX),
+               .post.dst = make_unsigned_domain(0, INT32_MAX),
+       });
+}
+
+/* 32-bit bitwise OR between a scalar range and immediate, triggering UB. */
+static int
+test_alu32_or_k_ub(void)
+{
+       return verify_instruction((struct verify_instruction_param){
+               .tested_instruction = {
+                       .code = (BPF_ALU | BPF_OR | BPF_K),
+                       .imm = 0,
+               },
+               .pre.dst = make_unsigned_domain(0, INT32_MAX),
+               .post.dst = make_unsigned_domain(0, INT32_MAX),
+       });
+}
+
+/* 32-bit bitwise XOR between a scalar range and immediate, triggering UB. */
+static int
+test_alu32_xor_k_ub(void)
+{
+       return verify_instruction((struct verify_instruction_param){
+               .tested_instruction = {
+                       .code = (BPF_ALU | BPF_XOR | BPF_K),
+                       .imm = 0,
+               },
+               .pre.dst = make_unsigned_domain(0, INT32_MAX),
+               .post.dst = make_unsigned_domain(0, INT32_MAX),
+       });
+}
+
 /* 64-bit addition of immediate to a range. */
 static int
 test_alu64_add_k(void)
@@ -2187,6 +2229,9 @@ static struct
 unit_test_suite test_bpf_validate_suite  = {
        .suite_name = "Test BPF Validate Unit Test Suite",
        .unit_test_cases = {
+               TEST_CASE(test_alu32_and_k_ub),
+               TEST_CASE(test_alu32_or_k_ub),
+               TEST_CASE(test_alu32_xor_k_ub),
                TEST_CASE(test_alu64_add_k),
                TEST_CASE(test_alu64_add_k_pointer),
                TEST_CASE(test_alu64_add_x_pointer_pointer),
diff --git a/lib/bpf/bpf_validate.c b/lib/bpf/bpf_validate.c
index f9960088a285..d3cd14f24683 100644
--- a/lib/bpf/bpf_validate.c
+++ b/lib/bpf/bpf_validate.c
@@ -812,43 +812,42 @@ eval_arsh(struct bpf_reg_val *rd, const struct 
bpf_reg_val *rs, size_t opsz,
 }
 
 static uint64_t
-eval_umax_bits(uint64_t v, size_t opsz)
+eval_umax_bits(uint64_t v)
 {
        if (v == 0)
                return 0;
 
        v = rte_clz64(v);
-       return RTE_LEN2MASK(opsz - v, uint64_t);
+       return RTE_LEN2MASK(64 - v, uint64_t);
 }
 
 /* estimate max possible value for (v1 & v2) */
 static uint64_t
-eval_uand_max(uint64_t v1, uint64_t v2, size_t opsz)
+eval_uand_max(uint64_t v1, uint64_t v2)
 {
-       v1 = eval_umax_bits(v1, opsz);
-       v2 = eval_umax_bits(v2, opsz);
+       v1 = eval_umax_bits(v1);
+       v2 = eval_umax_bits(v2);
        return (v1 & v2);
 }
 
 /* estimate max possible value for (v1 | v2) */
 static uint64_t
-eval_uor_max(uint64_t v1, uint64_t v2, size_t opsz)
+eval_uor_max(uint64_t v1, uint64_t v2)
 {
-       v1 = eval_umax_bits(v1, opsz);
-       v2 = eval_umax_bits(v2, opsz);
+       v1 = eval_umax_bits(v1);
+       v2 = eval_umax_bits(v2);
        return (v1 | v2);
 }
 
 static void
-eval_and(struct bpf_reg_val *rd, const struct bpf_reg_val *rs, size_t opsz,
-       uint64_t msk)
+eval_and(struct bpf_reg_val *rd, const 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) {
                rd->u.min &= rs->u.min;
                rd->u.max &= rs->u.max;
        } else {
-               rd->u.max = eval_uand_max(rd->u.max, rs->u.max, opsz);
+               rd->u.max = eval_uand_max(rd->u.max, rs->u.max);
                rd->u.min = 0;
        }
 
@@ -859,22 +858,21 @@ eval_and(struct bpf_reg_val *rd, const struct bpf_reg_val 
*rs, size_t opsz,
        /* at least one of operand is non-negative */
        } else if (rd->s.min >= 0 || rs->s.min >= 0) {
                rd->s.max = eval_uand_max(rd->s.max & (msk >> 1),
-                       rs->s.max & (msk >> 1), opsz);
+                       rs->s.max & (msk >> 1));
                rd->s.min = 0;
        } else
                eval_smax_bound(rd, msk);
 }
 
 static void
-eval_or(struct bpf_reg_val *rd, const struct bpf_reg_val *rs, size_t opsz,
-       uint64_t msk)
+eval_or(struct bpf_reg_val *rd, const 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) {
                rd->u.min |= rs->u.min;
                rd->u.max |= rs->u.max;
        } else {
-               rd->u.max = eval_uor_max(rd->u.max, rs->u.max, opsz);
+               rd->u.max = eval_uor_max(rd->u.max, rs->u.max);
                rd->u.min = RTE_MAX(rd->u.min, rs->u.min);
        }
 
@@ -885,22 +883,21 @@ eval_or(struct bpf_reg_val *rd, const struct bpf_reg_val 
*rs, size_t opsz,
 
        /* both operands are non-negative */
        } else if (rd->s.min >= 0 && rs->s.min >= 0) {
-               rd->s.max = eval_uor_max(rd->s.max, rs->s.max, opsz);
+               rd->s.max = eval_uor_max(rd->s.max, rs->s.max);
                rd->s.min = RTE_MAX(rd->s.min, rs->s.min);
        } else
                eval_smax_bound(rd, msk);
 }
 
 static void
-eval_xor(struct bpf_reg_val *rd, const struct bpf_reg_val *rs, size_t opsz,
-       uint64_t msk)
+eval_xor(struct bpf_reg_val *rd, const 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) {
                rd->u.min ^= rs->u.min;
                rd->u.max ^= rs->u.max;
        } else {
-               rd->u.max = eval_uor_max(rd->u.max, rs->u.max, opsz);
+               rd->u.max = eval_uor_max(rd->u.max, rs->u.max);
                rd->u.min = 0;
        }
 
@@ -911,7 +908,7 @@ eval_xor(struct bpf_reg_val *rd, const struct bpf_reg_val 
*rs, size_t opsz,
 
        /* both operands are non-negative */
        } else if (rd->s.min >= 0 && rs->s.min >= 0) {
-               rd->s.max = eval_uor_max(rd->s.max, rs->s.max, opsz);
+               rd->s.max = eval_uor_max(rd->s.max, rs->s.max);
                rd->s.min = 0;
        } else
                eval_smax_bound(rd, msk);
@@ -1152,11 +1149,11 @@ eval_alu(struct bpf_verifier *bvf, const struct 
ebpf_insn *ins)
        else if (op == EBPF_ARSH)
                eval_arsh(rd, &rs, opsz, msk);
        else if (op == BPF_AND)
-               eval_and(rd, &rs, opsz, msk);
+               eval_and(rd, &rs, msk);
        else if (op == BPF_OR)
-               eval_or(rd, &rs, opsz, msk);
+               eval_or(rd, &rs, msk);
        else if (op == BPF_XOR)
-               eval_xor(rd, &rs, opsz, msk);
+               eval_xor(rd, &rs, msk);
        else if (op == BPF_MUL)
                eval_mul(rd, &rs, opsz, msk);
        else if (op == BPF_DIV || op == BPF_MOD)
-- 
2.43.0

Reply via email to