summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/bpf/prog_tests/reg_bounds.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/testing/selftests/bpf/prog_tests/reg_bounds.c')
-rw-r--r--tools/testing/selftests/bpf/prog_tests/reg_bounds.c121
1 files changed, 113 insertions, 8 deletions
diff --git a/tools/testing/selftests/bpf/prog_tests/reg_bounds.c b/tools/testing/selftests/bpf/prog_tests/reg_bounds.c
index 39d42271cc46..71f5240cc5b7 100644
--- a/tools/testing/selftests/bpf/prog_tests/reg_bounds.c
+++ b/tools/testing/selftests/bpf/prog_tests/reg_bounds.c
@@ -422,15 +422,69 @@ static bool is_valid_range(enum num_t t, struct range x)
}
}
-static struct range range_improve(enum num_t t, struct range old, struct range new)
+static struct range range_intersection(enum num_t t, struct range old, struct range new)
{
return range(t, max_t(t, old.a, new.a), min_t(t, old.b, new.b));
}
+/*
+ * Result is precise when 'x' and 'y' overlap or form a continuous range,
+ * result is an over-approximation if 'x' and 'y' do not overlap.
+ */
+static struct range range_union(enum num_t t, struct range x, struct range y)
+{
+ if (!is_valid_range(t, x))
+ return y;
+ if (!is_valid_range(t, y))
+ return x;
+ return range(t, min_t(t, x.a, y.a), max_t(t, x.b, y.b));
+}
+
+/*
+ * This function attempts to improve x range intersecting it with y.
+ * range_cast(... to_t ...) looses precision for ranges that pass to_t
+ * min/max boundaries. To avoid such precision loses this function
+ * splits both x and y into halves corresponding to non-overflowing
+ * sub-ranges: [0, smin] and [smax, -1].
+ * Final result is computed as follows:
+ *
+ * ((x ∩ [0, smax]) ∩ (y ∩ [0, smax])) ∪
+ * ((x ∩ [smin,-1]) ∩ (y ∩ [smin,-1]))
+ *
+ * Precision might still be lost if final union is not a continuous range.
+ */
+static struct range range_refine_in_halves(enum num_t x_t, struct range x,
+ enum num_t y_t, struct range y)
+{
+ struct range x_pos, x_neg, y_pos, y_neg, r_pos, r_neg;
+ u64 smax, smin, neg_one;
+
+ if (t_is_32(x_t)) {
+ smax = (u64)(u32)S32_MAX;
+ smin = (u64)(u32)S32_MIN;
+ neg_one = (u64)(u32)(s32)(-1);
+ } else {
+ smax = (u64)S64_MAX;
+ smin = (u64)S64_MIN;
+ neg_one = U64_MAX;
+ }
+ x_pos = range_intersection(x_t, x, range(x_t, 0, smax));
+ x_neg = range_intersection(x_t, x, range(x_t, smin, neg_one));
+ y_pos = range_intersection(y_t, y, range(x_t, 0, smax));
+ y_neg = range_intersection(y_t, y, range(y_t, smin, neg_one));
+ r_pos = range_intersection(x_t, x_pos, range_cast(y_t, x_t, y_pos));
+ r_neg = range_intersection(x_t, x_neg, range_cast(y_t, x_t, y_neg));
+ return range_union(x_t, r_pos, r_neg);
+
+}
+
static struct range range_refine(enum num_t x_t, struct range x, enum num_t y_t, struct range y)
{
struct range y_cast;
+ if (t_is_32(x_t) == t_is_32(y_t))
+ x = range_refine_in_halves(x_t, x, y_t, y);
+
y_cast = range_cast(y_t, x_t, y);
/* If we know that
@@ -444,7 +498,40 @@ static struct range range_refine(enum num_t x_t, struct range x, enum num_t y_t,
*/
if (x_t == S64 && y_t == S32 && y_cast.a <= S32_MAX && y_cast.b <= S32_MAX &&
(s64)x.a >= S32_MIN && (s64)x.b <= S32_MAX)
- return range_improve(x_t, x, y_cast);
+ return range_intersection(x_t, x, y_cast);
+
+ if (y_t == U32 && x_t == U64) {
+ u64 xmin_swap, xmax_swap, xmin_lower32, xmax_lower32;
+
+ xmin_lower32 = x.a & 0xffffffff;
+ xmax_lower32 = x.b & 0xffffffff;
+ if (xmin_lower32 < y.a || xmin_lower32 > y.b) {
+ /* The 32 lower bits of the umin64 are outside the u32
+ * range. Let's update umin64 to match the u32 range.
+ * We want to *increase* the umin64 to the *minimum*
+ * value that matches the u32 range.
+ */
+ xmin_swap = swap_low32(x.a, y.a);
+ /* We should always only increase the minimum, so if
+ * the new value is lower than before, we need to
+ * increase the 32 upper bits by 1.
+ */
+ if (xmin_swap < x.a)
+ xmin_swap += 0x100000000;
+ if (xmin_swap == x.b)
+ return range(x_t, x.b, x.b);
+ } else if (xmax_lower32 < y.a || xmax_lower32 > y.b) {
+ /* Same for the umax64, but we want to *decrease*
+ * umax64 to the *maximum* value that matches the u32
+ * range.
+ */
+ xmax_swap = swap_low32(x.b, y.b);
+ if (xmax_swap > x.b)
+ xmax_swap -= 0x100000000;
+ if (xmax_swap == x.a)
+ return range(x_t, x.a, x.a);
+ }
+ }
/* the case when new range knowledge, *y*, is a 32-bit subregister
* range, while previous range knowledge, *x*, is a full register
@@ -462,11 +549,11 @@ static struct range range_refine(enum num_t x_t, struct range x, enum num_t y_t,
x_swap = range(x_t, swap_low32(x.a, y_cast.a), swap_low32(x.b, y_cast.b));
if (!is_valid_range(x_t, x_swap))
return x;
- return range_improve(x_t, x, x_swap);
+ return range_intersection(x_t, x, x_swap);
}
/* otherwise, plain range cast and intersection works */
- return range_improve(x_t, x, y_cast);
+ return range_intersection(x_t, x, y_cast);
}
/* =======================
@@ -609,7 +696,7 @@ static void range_cond(enum num_t t, struct range x, struct range y,
*newx = range(t, x.a, x.b);
*newy = range(t, y.a + 1, y.b);
} else if (x.a == x.b && x.b == y.b) {
- /* X is a constant matching rigth side of Y */
+ /* X is a constant matching right side of Y */
*newx = range(t, x.a, x.b);
*newy = range(t, y.a, y.b - 1);
} else if (y.a == y.b && x.a == y.a) {
@@ -617,7 +704,7 @@ static void range_cond(enum num_t t, struct range x, struct range y,
*newx = range(t, x.a + 1, x.b);
*newy = range(t, y.a, y.b);
} else if (y.a == y.b && x.b == y.b) {
- /* Y is a constant matching rigth side of X */
+ /* Y is a constant matching right side of X */
*newx = range(t, x.a, x.b - 1);
*newy = range(t, y.a, y.b);
} else {
@@ -1163,7 +1250,23 @@ static int parse_range_cmp_log(const char *log_buf, struct case_spec spec,
spec.compare_subregs ? "w0" : "r0",
spec.compare_subregs ? "w" : "r", specs[i].reg_idx);
- q = strstr(p, buf);
+ /*
+ * In the verifier log look for lines:
+ * 18: (bf) r0 = r6 ; R0=... R6=...
+ * Different verifier passes may print
+ * 18: (bf) r0 = r6
+ * as well, but never followed by ';'.
+ */
+ q = p;
+ while ((q = strstr(q, buf)) != NULL) {
+ const char *s = q + strlen(buf);
+
+ while (*s == ' ' || *s == '\t')
+ s++;
+ if (*s == ';')
+ break;
+ q = s;
+ }
if (!q) {
*specs[i].state = (struct reg_state){.valid = false};
continue;
@@ -2075,9 +2178,11 @@ static struct subtest_case crafted_cases[] = {
{U64, S64, {0x7fffffff00000001ULL, 0xffffffff00000000ULL}, {0, 0}},
{U64, S64, {0, 0xffffffffULL}, {1, 1}},
{U64, S64, {0, 0xffffffffULL}, {0x7fffffff, 0x7fffffff}},
+ {U64, S32, {0xfffffffe00000001, 0xffffffff00000000}, {S64_MIN, S64_MIN}},
+ {U64, U32, {0xfffffffe00000000, U64_MAX - 1}, {U64_MAX, U64_MAX}},
{U64, U32, {0, 0x100000000}, {0, 0}},
- {U64, U32, {0xfffffffe, 0x100000000}, {0x80000000, 0x80000000}},
+ {U64, U32, {0xfffffffe, 0x300000000}, {0x80000000, 0x80000000}},
{U64, S32, {0, 0xffffffff00000000ULL}, {0, 0}},
/* these are tricky cases where lower 32 bits allow to tighten 64