Function `eval_neg` did not treat values of INT64_MIN and 0 specially
when calculating negation ranges (e.g.  negated unsigned range 0..2
should turn into 0..UINT64_MAX) producing incorrect results. On top of
this negating signed INT64_MIN caused undefined behaviour.

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

    Tested program:
        0:  mov r0, #0x0
        1:  ldxdw r2, [r1 + 0]
        2:  lddw r4, #0x8000000000000000
        4:  jgt r2, r4, L7
        5:  neg r2, #0x0  ; tested instruction
        6:  mov r0, #0x1
        7:  exit
    Pre-state:
       r2:  0..0x8000000000000000
    Post-state:
       r2:  INT64_MIN..INT64_MIN+1 INTERSECT 0..0x8000000000000000 (!)

After the tested instruction validator considers r2 to be within
INT64_MIN..INT64_MIN+1 if viewed as signed, or within
0..0x8000000000000000 if viewed as unsigned, however if 1 was loaded on
step 1 into r2 it is possible for it to become -1 after the tested
instruction which satisfies neither of the ranges.

With sanitizer the following diagnostic is generated:

    lib/bpf/bpf_validate.c:1120:7: runtime error: negation of
    -9223372036854775808 cannot be represented in type 'long int'; cast
    to an unsigned type to negate
        #0 0x000002747230 in eval_neg lib/bpf/bpf_validate.c:1120
        #1 0x000002748fb6 in eval_alu lib/bpf/bpf_validate.c:1251
        #2 0x000002759dd3 in evaluate lib/bpf/bpf_validate.c:3161
        ...

    SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior
    lib/bpf/bpf_validate.c:1120:7

Add missing handling of special cases, add tests.

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

Signed-off-by: Marat Khalili <[email protected]>
---
 app/test/test_bpf_validate.c | 126 +++++++++++++++++++++++++++++++++++
 lib/bpf/bpf_validate.c       |  55 ++++++++++++---
 2 files changed, 173 insertions(+), 8 deletions(-)

diff --git a/app/test/test_bpf_validate.c b/app/test/test_bpf_validate.c
index d7396a88beb8..995f7363b80f 100644
--- a/app/test/test_bpf_validate.c
+++ b/app/test/test_bpf_validate.c
@@ -1154,6 +1154,132 @@ 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 negation when interval first element is INT64_MIN. */
+static int
+test_alu64_neg_int64min_first(void)
+{
+       static const int64_t other_values[] = {
+               INT64_MIN,
+               INT64_MIN + 1,
+               INT64_MIN + 13,
+               -17,
+               -1,
+               0,
+               1,
+               19,
+               INT64_MAX - 23,
+               INT64_MAX - 1,
+               INT64_MAX,
+       };
+       for (int other_index = 0; other_index != RTE_DIM(other_values); 
++other_index) {
+               const int64_t other_value = other_values[other_index];
+               TEST_ASSERT_SUCCESS(verify_instruction((struct 
verify_instruction_param){
+                       .tested_instruction = {
+                               .code = (EBPF_ALU64 | BPF_NEG),
+                       },
+                       .pre.dst = make_signed_domain(INT64_MIN, other_value),
+                       .post.dst = other_value > 0 ? unknown :
+                               make_unsigned_domain(-(uint64_t)other_value, 
INT64_MIN),
+               }), "other_index=%d", other_index);
+       }
+       return TEST_SUCCESS;
+}
+
+REGISTER_FAST_TEST(bpf_validate_alu64_neg_int64min_first_autotest, NOHUGE_OK, 
ASAN_OK,
+       test_alu64_neg_int64min_first);
+
+/* 64-bit negation when interval last element is INT64_MIN. */
+static int
+test_alu64_neg_int64min_last(void)
+{
+       static const uint64_t other_values[] = {
+               0,
+               1,
+               19,
+               INT64_MAX - 23,
+               INT64_MAX - 1,
+               INT64_MAX,
+               INT64_MIN,
+       };
+       for (int other_index = 0; other_index != RTE_DIM(other_values); 
++other_index) {
+               const int64_t other_value = other_values[other_index];
+               TEST_ASSERT_SUCCESS(verify_instruction((struct 
verify_instruction_param){
+                       .tested_instruction = {
+                               .code = (EBPF_ALU64 | BPF_NEG),
+                       },
+                       .pre.dst = make_unsigned_domain(other_value, INT64_MIN),
+                       .post.dst = make_signed_domain(INT64_MIN, 
-(uint64_t)other_value),
+               }), "other_index=%d", other_index);
+       }
+       return TEST_SUCCESS;
+}
+
+REGISTER_FAST_TEST(bpf_validate_alu64_neg_int64min_last_autotest, NOHUGE_OK, 
ASAN_OK,
+       test_alu64_neg_int64min_last);
+
+/* 64-bit negation when interval first element is zero. */
+static int
+test_alu64_neg_zero_first(void)
+{
+       static const uint64_t other_values[] = {
+               0,
+               1,
+               19,
+               INT64_MAX - 23,
+               INT64_MAX - 1,
+               INT64_MAX,
+               INT64_MIN,
+               INT64_MIN + 1,
+               INT64_MIN + 13,
+               -17,
+               -1,
+       };
+       for (int other_index = 0; other_index != RTE_DIM(other_values); 
++other_index) {
+               const uint64_t other_value = other_values[other_index];
+               TEST_ASSERT_SUCCESS(verify_instruction((struct 
verify_instruction_param){
+                       .tested_instruction = {
+                               .code = (EBPF_ALU64 | BPF_NEG),
+                       },
+                       .pre.dst = make_unsigned_domain(0, other_value),
+                       .post.dst = other_value > (uint64_t)INT64_MIN ? unknown 
:
+                               make_signed_domain(-(uint64_t)other_value, 0),
+               }), "other_index=%d", other_index);
+       }
+       return TEST_SUCCESS;
+}
+
+REGISTER_FAST_TEST(bpf_validate_alu64_neg_zero_first_autotest, NOHUGE_OK, 
ASAN_OK,
+       test_alu64_neg_zero_first);
+
+/* 64-bit negation when interval last element is zero. */
+static int
+test_alu64_neg_zero_last(void)
+{
+       static const int64_t other_values[] = {
+               INT64_MIN,
+               INT64_MIN + 1,
+               INT64_MIN + 13,
+               -17,
+               -1,
+               0,
+       };
+       for (int other_index = 0; other_index != RTE_DIM(other_values); 
++other_index) {
+               const int64_t other_value = other_values[other_index];
+               TEST_ASSERT_SUCCESS(verify_instruction((struct 
verify_instruction_param){
+                       .tested_instruction = {
+                               .code = (EBPF_ALU64 | BPF_NEG),
+                       },
+                       .pre.dst = make_signed_domain(other_value, 0),
+                       .post.dst = make_unsigned_domain(0, 
-(uint64_t)other_value),
+               }), "other_index=%d", other_index);
+       }
+
+       return TEST_SUCCESS;
+}
+
+REGISTER_FAST_TEST(bpf_validate_alu64_neg_zero_last_autotest, NOHUGE_OK, 
ASAN_OK,
+       test_alu64_neg_zero_last);
+
 /* Jump if greater than immediate. */
 static int
 test_jmp64_jeq_k(void)
diff --git a/lib/bpf/bpf_validate.c b/lib/bpf/bpf_validate.c
index b0d88fe7d273..79c8679ac535 100644
--- a/lib/bpf/bpf_validate.c
+++ b/lib/bpf/bpf_validate.c
@@ -990,6 +990,11 @@ eval_neg(struct bpf_reg_val *rd, size_t opsz, uint64_t msk)
 {
        uint64_t ux, uy;
        int64_t sx, sy;
+       /* additional limits imposed by signed on unsigned and back */
+       struct bpf_reg_val cross_limits = {
+               .s = { INT64_MIN, INT64_MAX },
+               .u = { 0, UINT64_MAX },
+       };
 
        /* if we have 32-bit values - extend them to 64-bit */
        if (opsz == sizeof(uint32_t) * CHAR_BIT) {
@@ -997,11 +1002,29 @@ eval_neg(struct bpf_reg_val *rd, size_t opsz, uint64_t 
msk)
                rd->u.max = (int32_t)rd->u.max;
        }
 
-       ux = -(int64_t)rd->u.min & msk;
-       uy = -(int64_t)rd->u.max & msk;
+       if (rd->u.min == 0) {
+               /* special case: ranges that include 0 and, possibly, 1 */
+
+               /*
+                * Calculate requirements on the signed range of negation.
+                * It is only possible when negated range does not cross from
+                * INT64_MIN to INT64_MAX, which means our original range does
+                * not reach (uint64_t)-INT64_MAX.
+                */
+               if (rd->u.max < (uint64_t)-INT64_MAX) {
+                       cross_limits.s.min = -rd->u.max;
+                       cross_limits.s.max = -rd->u.min;
+               }
+
+               if (rd->u.max != 0)
+                       rd->u.max = UINT64_MAX;
+       } else {
+               ux = -rd->u.min & msk;
+               uy = -rd->u.max & msk;
 
-       rd->u.max = RTE_MAX(ux, uy);
-       rd->u.min = RTE_MIN(ux, uy);
+               rd->u.max = RTE_MAX(ux, uy);
+               rd->u.min = RTE_MIN(ux, uy);
+       }
 
        /* if we have 32-bit values - extend them to 64-bit */
        if (opsz == sizeof(uint32_t) * CHAR_BIT) {
@@ -1009,11 +1032,27 @@ eval_neg(struct bpf_reg_val *rd, size_t opsz, uint64_t 
msk)
                rd->s.max = (int32_t)rd->s.max;
        }
 
-       sx = -rd->s.min & msk;
-       sy = -rd->s.max & msk;
+       if (rd->s.min == INT64_MIN) {
+               /* special case: negation of INT64_MIN is INT64_MIN */
+               if (rd->s.max <= 0) {
+                       cross_limits.u.min = -(uint64_t)rd->s.max;
+                       cross_limits.u.max = -(uint64_t)rd->s.min;
+               }
+               if (rd->s.max != INT64_MIN)
+                       rd->s.max = INT64_MAX;
+       } else {
+               /* since max >= min, neither can be INT64_MIN here */
+               sx = -rd->s.min & msk;
+               sy = -rd->s.max & msk;
+
+               rd->s.max = RTE_MAX(sx, sy);
+               rd->s.min = RTE_MIN(sx, sy);
+       }
 
-       rd->s.max = RTE_MAX(sx, sy);
-       rd->s.min = RTE_MIN(sx, sy);
+       rd->s.min = RTE_MAX(rd->s.min, cross_limits.s.min) & msk;
+       rd->s.max = RTE_MIN(rd->s.max, cross_limits.s.max) & msk;
+       rd->u.min = RTE_MAX(rd->u.min, cross_limits.u.min) & msk;
+       rd->u.max = RTE_MIN(rd->u.max, cross_limits.u.max) & msk;
 }
 
 static const char *
-- 
2.43.0

Reply via email to