Add necessary harness for testing pointer values in the registers and
add basic tests for adding pointers and scalars in various combinations.
These tests cover previously introduced fixes for BPF_ADD and BPF_LDX.

Signed-off-by: Marat Khalili <[email protected]>
---
 app/test/test_bpf_validate.c | 311 +++++++++++++++++++++++++++++++++--
 1 file changed, 297 insertions(+), 14 deletions(-)

diff --git a/app/test/test_bpf_validate.c b/app/test/test_bpf_validate.c
index 20b0dfaf87b2..cdceae3e0728 100644
--- a/app/test/test_bpf_validate.c
+++ b/app/test/test_bpf_validate.c
@@ -51,9 +51,12 @@ struct unsigned_interval {
  * parameters (instruction is not accessing corresponding register).
  * It's not the same as `unknown` domain which describes register that is being
  * used but can hold any value.
+ *
+ * Flag `is_pointer` tells if the interval is relative to some memory area 
base.
  */
 struct domain {
        bool is_defined;
+       bool is_pointer;
        struct signed_interval s;
        struct unsigned_interval u;
 };
@@ -149,7 +152,16 @@ make_unsigned_domain(uint64_t min, uint64_t max)
        };
 }
 
-/* Return true if domain is a singleton. */
+/* Create domain from signed interval. */
+static struct domain
+make_pointer_domain(int64_t min, int64_t max)
+{
+       struct domain result = make_signed_domain(min, max);
+       result.is_pointer = true;
+       return result;
+}
+
+/* Return true if domain is a scalar or pointer singleton. */
 static bool
 domain_is_singleton(const struct domain *domain)
 {
@@ -195,7 +207,8 @@ format_domain(char *buffer, size_t bufsz, const struct 
domain *domain)
 
        const int rc = !domain->is_defined ?
                snprintf(buffer, bufsz, "UNDEFINED") :
-               snprintf(buffer, bufsz, "%s INTERSECT %s",
+               snprintf(buffer, bufsz, "%s %s INTERSECT %s",
+                       domain->is_pointer ? "pointer" : "scalar",
                        format_interval(signed_buffer, sizeof(signed_buffer), 
'd',
                                domain->s.min, domain->s.max),
                        format_interval(unsigned_buffer, 
sizeof(unsigned_buffer), 'x',
@@ -228,7 +241,7 @@ may_jump(const struct rte_bpf_validate_debug *debug,
        return (result & RTE_BPF_VALIDATE_DEBUG_MAY_BE_TRUE) != 0;
 }
 
-/* Check interval of the register interpreted as signed. */
+/* Check interval of the register interpreted as signed scalar. */
 static int
 check_signed_interval(struct rte_bpf_validate_debug *debug,
        uint8_t reg, struct signed_interval interval)
@@ -274,7 +287,7 @@ check_signed_interval(struct rte_bpf_validate_debug *debug,
        return TEST_SUCCESS;
 }
 
-/* Check interval of the register interpreted as unsigned. */
+/* Check interval of the register interpreted as unsigned scalar. */
 static int
 check_unsigned_interval(struct rte_bpf_validate_debug *debug,
        uint8_t reg, struct unsigned_interval interval)
@@ -320,18 +333,154 @@ check_unsigned_interval(struct rte_bpf_validate_debug 
*debug,
        return TEST_SUCCESS;
 }
 
-/* Check domain of the register interpreted as value. */
+/* Check interval of the register relative to the base register. */
+static int
+check_relative_interval(struct rte_bpf_validate_debug *debug,
+       uint8_t reg, struct signed_interval interval, uint8_t base_reg)
+{
+       char buffer[VALUE_FORMAT_BUFFER_SIZE];
+
+       TEST_ASSERT_EQUAL(may_jump(debug,
+               &(struct ebpf_insn){
+                       .code = (BPF_JMP | EBPF_JLT | BPF_X),
+                       .dst_reg = reg,
+                       .src_reg = base_reg,
+               }, interval.min),
+               false,
+               "r%hhu u< r%hhu + %s is impossible", reg, base_reg,
+               format_value(buffer, sizeof(buffer), 'd', interval.min));
+
+       TEST_ASSERT_EQUAL(may_jump(debug,
+               &(struct ebpf_insn){
+                       .code = (BPF_JMP | BPF_JEQ | BPF_X),
+                       .dst_reg = reg,
+                       .src_reg = base_reg,
+               }, interval.min),
+               true,
+               "r%hhu == r%hhu + %s is possible", reg, base_reg,
+               format_value(buffer, sizeof(buffer), 'd', interval.min));
+
+       TEST_ASSERT_EQUAL(may_jump(debug,
+               &(struct ebpf_insn){
+                       .code = (BPF_JMP | BPF_JEQ | BPF_X),
+                       .dst_reg = reg,
+                       .src_reg = base_reg,
+               }, interval.max),
+               true,
+               "r%hhu == r%hhu + %s is possible", reg, base_reg,
+               format_value(buffer, sizeof(buffer), 'd', interval.max));
+
+       TEST_ASSERT_EQUAL(may_jump(debug,
+               &(struct ebpf_insn){
+                       .code = (BPF_JMP | BPF_JGT | BPF_X),
+                       .dst_reg = reg,
+                       .src_reg = base_reg,
+               }, interval.max),
+               false,
+               "r%hhu u> r%hhu + %s is impossible", reg, base_reg,
+               format_value(buffer, sizeof(buffer), 'd', interval.max));
+
+       return TEST_SUCCESS;
+}
+
+/*
+ * Check access of the register interpreted as pointer.
+ *
+ * Unlike other similar functions, min > max is not a problem here,
+ * so either signed or unsigned pair can be passed without any issues.
+ *
+ * This is the reason we are not using signed_interval or unsigned_interval 
here
+ * to avoid confusion.
+ */
 static int
-check_domain_impl(struct rte_bpf_validate_debug *debug, uint8_t reg,
+check_pointer_access(struct rte_bpf_validate_debug *debug, uint8_t reg,
+       uint64_t min, uint64_t max, size_t area_size)
+{
+       char buffer[VALUE_FORMAT_BUFFER_SIZE];
+
+       /* Start and end of the valid offsets window (unless empty). */
+       const uint64_t window_begin = -min;
+       const uint64_t window_end = area_size - max;
+
+       /* Only have accessible bytes if the interval is smaller than the area. 
*/
+       const uint64_t interval_size = max - min;
+       const bool window_empty = (interval_size >= area_size);
+
+       TEST_ASSERT_EQUAL(rte_bpf_validate_debug_can_access(debug,
+               &(struct ebpf_insn){
+                       .code = (BPF_LDX | BPF_B | BPF_MEM),
+                       .src_reg = reg
+               }, window_begin - 1),
+               false,
+               "r%hhu + %s (before window begin) dereference is invalid", reg,
+               format_value(buffer, sizeof(buffer), 'd', window_begin - 1));
+
+       TEST_ASSERT_EQUAL(rte_bpf_validate_debug_can_access(debug,
+               &(struct ebpf_insn){
+                       .code = (BPF_LDX | BPF_B | BPF_MEM),
+                       .src_reg = reg
+               }, window_begin),
+               !window_empty,
+               "r%hhu + %s (after window begin) dereference is %s", reg,
+               format_value(buffer, sizeof(buffer), 'd', window_begin),
+               window_empty ? "invalid for empty window" : "valid");
+
+       TEST_ASSERT_EQUAL(rte_bpf_validate_debug_can_access(debug,
+               &(struct ebpf_insn){
+                       .code = (BPF_LDX | BPF_B | BPF_MEM),
+                       .src_reg = reg
+               }, window_end - 1),
+               !window_empty,
+               "r%hhu + %s (before window end) dereference is %s", reg,
+               format_value(buffer, sizeof(buffer), 'd', window_end - 1),
+               window_empty ? "invalid for empty window" : "valid");
+
+       TEST_ASSERT_EQUAL(rte_bpf_validate_debug_can_access(debug,
+               &(struct ebpf_insn){
+                       .code = (BPF_LDX | BPF_B | BPF_MEM),
+                       .src_reg = reg
+               }, window_end),
+               false,
+               "r%hhu + %s (after window end) dereference is invalid", reg,
+               format_value(buffer, sizeof(buffer), 'd', window_end));
+
+       return TEST_SUCCESS;
+}
+
+/* Check domain of the register interpreted as absolute value. */
+static int
+check_scalar_domain(struct rte_bpf_validate_debug *debug, uint8_t reg,
        const struct domain *domain)
 {
        TEST_ASSERT_SUCCESS(
                check_signed_interval(debug, reg, domain->s),
-               "signed interval check");
+               "absolute signed interval check");
 
        TEST_ASSERT_SUCCESS(
                check_unsigned_interval(debug, reg, domain->u),
-               "unsigned interval check");
+               "absolute unsigned interval check");
+
+       return TEST_SUCCESS;
+}
+
+/* Check domain of the register interpreted as relative pointer. */
+static int
+check_pointer_domain(struct rte_bpf_validate_debug *debug, uint8_t reg,
+       const struct domain *domain, uint8_t base_reg, size_t area_size)
+{
+       TEST_ASSERT_SUCCESS(
+               check_relative_interval(debug, reg, domain->s, base_reg),
+               "relative interval check");
+
+       TEST_ASSERT_SUCCESS(
+               check_pointer_access(debug, reg, domain->s.min, domain->s.max,
+                       area_size),
+               "pointer signed access check");
+
+       TEST_ASSERT_SUCCESS(
+               check_pointer_access(debug, reg, domain->u.min, domain->u.max,
+                       area_size),
+               "pointer unsigned access check");
 
        return TEST_SUCCESS;
 }
@@ -339,11 +488,13 @@ check_domain_impl(struct rte_bpf_validate_debug *debug, 
uint8_t reg,
 /* Check domain of the register and format the values in case of an error. */
 static int
 check_domain(struct rte_bpf_validate_debug *debug, uint8_t reg,
-       const struct domain *domain)
+       const struct domain *domain, uint8_t base_reg, size_t area_size)
 {
        char buffer[REGISTER_FORMAT_BUFFER_SIZE];
 
-       const int rc = check_domain_impl(debug, reg, domain);
+       const int rc = domain->is_pointer ?
+               check_pointer_domain(debug, reg, domain, base_reg, area_size) :
+               check_scalar_domain(debug, reg, domain);
 
        if (rc != TEST_SUCCESS) {
                TEST_LOG_LINE(WARNING, "\tExpected: r%hhu = %s", reg,
@@ -419,13 +570,13 @@ compare_and_jump(struct ebpf_insn **ins, uint8_t op, 
uint8_t reg,
 }
 
 /*
- * Prepare register to be in the specified domain.
+ * Prepare register to be in the specified scalar domain.
  *
  * Unless singleton, load unknown value into it and clamp it with conditional 
jumps.
  * (Jump offsets are not filled and should be patched in by the caller.)
  */
 static void
-prepare_domain(struct ebpf_insn **ins, uint8_t reg,
+prepare_scalar_domain(struct ebpf_insn **ins, uint8_t reg,
        const struct domain *domain, uint8_t base_reg, int *service_cell_count,
        uint8_t tmp_reg)
 {
@@ -460,6 +611,28 @@ prepare_domain(struct ebpf_insn **ins, uint8_t reg,
                compare_and_jump(ins, EBPF_JSGT, reg, domain->s.max, tmp_reg);
 }
 
+/*
+ * Prepare register to be in the specified scalar or pointer domain, if any.
+ *
+ * If `domain` is NULL, do nothing. Otherwise prepare scalar domain,
+ * and then add base register to it to convert it to a pointer, if needed.
+ */
+static void
+prepare_domain(struct ebpf_insn **ins, uint8_t reg,
+       const struct domain *domain, uint8_t base_reg, int *service_cell_count,
+       uint8_t tmp_reg)
+{
+       prepare_scalar_domain(ins, reg, domain, base_reg, service_cell_count, 
tmp_reg);
+
+       if (domain->is_pointer)
+               /* Add base_reg to convert resulting scalar into a pointer. */
+               *(*ins)++ = (struct ebpf_insn){
+                       .code = (EBPF_ALU64 | BPF_ADD | BPF_X),
+                       .dst_reg = reg,
+                       .src_reg = base_reg,
+               };
+}
+
 static void
 fill_verify_instruction_defaults(struct verify_instruction_param *prm)
 {
@@ -645,7 +818,8 @@ point_callback(struct rte_bpf_validate_debug *debug, const 
struct verify_instruc
 
                if (state->dst.is_defined) {
                        TEST_ASSERT_SUCCESS(
-                               check_domain(debug, ctx->dst_reg, &state->dst),
+                               check_domain(debug, ctx->dst_reg, &state->dst,
+                                       ctx->base_reg, ctx->prm.area_size),
                                "dst domain check");
                        TEST_LOG_LINE(DEBUG, "Successfully checked r%hhu.", 
ctx->dst_reg);
                } else
@@ -658,7 +832,8 @@ point_callback(struct rte_bpf_validate_debug *debug, const 
struct verify_instruc
 
                if (state->src.is_defined) {
                        TEST_ASSERT_SUCCESS(
-                               check_domain(debug, ctx->src_reg, &state->src),
+                               check_domain(debug, ctx->src_reg, &state->src,
+                                       ctx->base_reg, ctx->prm.area_size),
                                "src domain check");
                        TEST_LOG_LINE(DEBUG, "Successfully checked r%hhu.", 
ctx->src_reg);
                } else
@@ -889,6 +1064,96 @@ test_alu64_add_k(void)
 REGISTER_FAST_TEST(bpf_validate_alu64_add_k_autotest, NOHUGE_OK, ASAN_OK,
        test_alu64_add_k);
 
+/* 64-bit addition of immediate to a pointer range. */
+static int
+test_alu64_add_k_pointer(void)
+{
+       return verify_instruction((struct verify_instruction_param){
+               .tested_instruction = {
+                       .code = (EBPF_ALU64 | BPF_ADD | BPF_K),
+                       .imm = 17,
+               },
+               .area_size = 256,
+               .pre.dst = make_pointer_domain(11, 29),
+               .post.dst = make_pointer_domain(11 + 17, 29 + 17),
+       });
+}
+
+REGISTER_FAST_TEST(bpf_validate_alu64_add_k_pointer_autotest, NOHUGE_OK, 
ASAN_OK,
+       test_alu64_add_k_pointer);
+
+/* 64-bit addition of pointer to a pointer. */
+static int
+test_alu64_add_x_pointer_pointer(void)
+{
+       return verify_instruction((struct verify_instruction_param){
+               .tested_instruction = {
+                       .code = (EBPF_ALU64 | BPF_ADD | BPF_X),
+               },
+               .area_size = 256,
+               .pre.dst = make_pointer_domain(11, 29),
+               .pre.src = make_pointer_domain(17, 23),
+               .post.dst = unknown,
+       });
+}
+
+REGISTER_FAST_TEST(bpf_validate_alu64_add_x_pointer_pointer_autotest, 
NOHUGE_OK, ASAN_OK,
+       test_alu64_add_x_pointer_pointer);
+
+/* 64-bit addition of scalar to a pointer. */
+static int
+test_alu64_add_x_pointer_scalar(void)
+{
+       return verify_instruction((struct verify_instruction_param){
+               .tested_instruction = {
+                       .code = (EBPF_ALU64 | BPF_ADD | BPF_X),
+               },
+               .area_size = 256,
+               .pre.dst = make_pointer_domain(11, 29),
+               .pre.src = make_signed_domain(17, 23),
+               .post.dst = make_pointer_domain(11 + 17, 29 + 23),
+       });
+}
+
+REGISTER_FAST_TEST(bpf_validate_alu64_add_x_pointer_scalar_autotest, 
NOHUGE_OK, ASAN_OK,
+       test_alu64_add_x_pointer_scalar);
+
+/* 64-bit addition of pointer to a scalar. */
+static int
+test_alu64_add_x_scalar_pointer(void)
+{
+       return verify_instruction((struct verify_instruction_param){
+               .tested_instruction = {
+                       .code = (EBPF_ALU64 | BPF_ADD | BPF_X),
+               },
+               .area_size = 256,
+               .pre.dst = make_signed_domain(11, 29),
+               .pre.src = make_pointer_domain(17, 23),
+               .post.dst = make_pointer_domain(11 + 17, 29 + 23),
+       });
+}
+
+REGISTER_FAST_TEST(bpf_validate_alu64_add_x_scalar_pointer_autotest, 
NOHUGE_OK, ASAN_OK,
+       test_alu64_add_x_scalar_pointer);
+
+/* 64-bit addition of scalar to a scalar. */
+static int
+test_alu64_add_x_scalar_scalar(void)
+{
+       return verify_instruction((struct verify_instruction_param){
+               .tested_instruction = {
+                       .code = (EBPF_ALU64 | BPF_ADD | BPF_X),
+               },
+               .area_size = 256,
+               .pre.dst = make_signed_domain(11, 29),
+               .pre.src = make_signed_domain(17, 23),
+               .post.dst = make_signed_domain(11 + 17, 29 + 23),
+       });
+}
+
+REGISTER_FAST_TEST(bpf_validate_alu64_add_x_scalar_scalar_autotest, NOHUGE_OK, 
ASAN_OK,
+       test_alu64_add_x_scalar_scalar);
+
 /* Jump if greater than immediate. */
 static int
 test_jmp64_jeq_k(void)
@@ -906,3 +1171,21 @@ test_jmp64_jeq_k(void)
 
 REGISTER_FAST_TEST(bpf_validate_jmp64_jeq_k_autotest, NOHUGE_OK, ASAN_OK,
        test_jmp64_jeq_k);
+
+/* 64-bit load from heap (should be set to unknown). */
+static int
+test_mem_ldx_dw_heap(void)
+{
+       return verify_instruction((struct verify_instruction_param){
+               .tested_instruction = {
+                       .code = (BPF_MEM | BPF_LDX | EBPF_DW),
+                       .off = 16,
+               },
+               .area_size = 24,
+               .pre.src = make_pointer_domain(0, 0),
+               .post.dst = unknown,
+       });
+}
+
+REGISTER_FAST_TEST(bpf_validate_mem_ldx_dw_heap_autotest, NOHUGE_OK, ASAN_OK,
+       test_mem_ldx_dw_heap);
-- 
2.43.0

Reply via email to