mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-06-21 15:43:21 +02:00
bpf: replace min/max fields with struct cnum{32,64}
Replace eight independent s64, u64, s32, u32 min/max fields in
bpf_reg_state with two circular number fields:
- cnum64 for a unified signed/unsigned 64-bit range tracking;
- cnum32 for a unified signed/unsigned 32-bit range tracking.
Each cnum represents a range as a single arc on the circular number
line (base + size), from which signed and unsigned bounds are derived
on demand via accessor functions introduced in the preceding commit.
Notable changes:
- Signed<->unsigned deductions in __reg_deduce_bounds() are removed.
- 64<->32 bit deductions are replaced with:
- reg->r32 = cnum32_intersect(reg->r32, cnum32_from_cnum64(reg->r64));
this is functionally equivalent to the old code.
- reg->r64 = cnum64_cnum32_intersect(reg->r64, reg->r32);
this handles a few additional cases, see commit message for
"bpf: representation and basic operations on circular numbers".
- regs_refine_cond_op() now computes results in terms of operations on
sets, e.g. for JNE:
/* Complement of the range [val, val] as cnum64. */
lo = (struct cnum64){ val + 1, U64_MAX - 1 };
reg1->r64 = cnum64_intersect(reg1->r64, lo);
- For add, sub operations on scalars replace explicit bounds
computations with cnum{32,64}_{add,negate}.
- For add, sub operations on pointers deduplicate with arithmetic
operations on scalars and use cnum{32,64}_{add,negate}.
- For and, or, xor operations on scalars remove explicit signed bounds
computations.
- range_bounds_violation() reduces to checking cnum_is_empty().
- const_tnum_range_mismatch() reduces to checking cnum_is_const().
Selftest adjustments: a few existing tests are updated because a
single cnum arc cannot always represent what the old system expressed
as the intersection of independent signed and unsigned ranges.
For example, if the old system tracked u64=[0, U64_MAX-U32_MAX+2] and
s64=[S64_MIN+2, 2] independently, their intersection is a tight
two-point set. A single cnum must pick the shorter arc, losing the
other constraint. These cases are documented with comments in the
adjusted tests.
reg_bounds.c is updated with logic similar to
cnum64_cnum32_intersect(). Instead of using cnums it inspects
intersection between 'b' and first / last / next-after-first /
previous-before-last sub-ranges of 'a'.
reg_bounds.c is also updated to skip test cases that rely
in signed and unsigned ranges intersecting in two intervals,
as such cases are not representable by a single cnum.
The following "crafted" test cases are affected:
- reg_bounds_crafted/(s64)[0xffffffffffff8000; 0x7fff] (u32)<op> [0; 0x1f]
- reg_bounds_crafted/(s64)[0; 0x1f] (u32)<op> [0xffffffffffffff80; 0x7f]
- reg_bounds_crafted/(s64)[0xffffffffffffff80; 0x7f] (u32)<op> [0; 0x1f]
- reg_bounds_crafted/(u64)[0; 1] (s32)<op> [1; 2147483648]
- reg_bounds_crafted/(u64)[1; 2147483648] (s32)<op> [0; 1]
- reg_bounds_crafted/(u64)[0; 0xffffffff00000000] (s64)<op> 0
- reg_bounds_crafted/(u64)0 (s64)<op> [0; 0xffffffff00000000]
- reg_bounds_crafted/(u64)[0; 0xffffffff00000000] (s32)<op> 0
- reg_bounds_crafted/(u64)0 (s32)<op> [0; 0xffffffff00000000]
- reg_bounds_crafted/(s64)[S64_MIN; 0] (u64)<op> S64_MIN
- reg_bounds_crafted/(s64)S64_MIN (u64)<op> [S64_MIN; 0]
- reg_bounds_crafted/(s32)[S32_MIN; 0] (u32)<op> S32_MIN
- reg_bounds_crafted/(s32)S32_MIN (u32)<op> [S32_MIN; 0]
- reg_bounds_crafted/(s64)[0; 0x1f] (u32)<op> [0xffffffff80000000; 0x7fffffff]
- reg_bounds_crafted/(s64)[0xffffffff80000000; 0x7fffffff] (u32)<op> [0; 0x1f]
- reg_bounds_crafted/(s64)[0; 0x1f] (u32)<op> [0xffffffffffff8000; 0x7fff]
As well as some reg_bounds_roand_{consts,ranges}_A_B, where A and B
differ in sign domain.
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://lore.kernel.org/r/20260424-cnums-everywhere-rfc-v1-v3-3-ca434b39a486@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
committed by
Alexei Starovoitov
parent
b93f7180f0
commit
bbc6310855
@@ -8,6 +8,7 @@
|
||||
#include <linux/btf.h> /* for struct btf and btf_id() */
|
||||
#include <linux/filter.h> /* for MAX_BPF_STACK */
|
||||
#include <linux/tnum.h>
|
||||
#include <linux/cnum.h>
|
||||
|
||||
/* Maximum variable offset umax_value permitted when resolving memory accesses.
|
||||
* In practice this is far bigger than any realistic pointer offset; this limit
|
||||
@@ -120,14 +121,8 @@ struct bpf_reg_state {
|
||||
* These refer to the same value as var_off, not necessarily the actual
|
||||
* contents of the register.
|
||||
*/
|
||||
s64 smin_value; /* minimum possible (s64)value */
|
||||
s64 smax_value; /* maximum possible (s64)value */
|
||||
u64 umin_value; /* minimum possible (u64)value */
|
||||
u64 umax_value; /* maximum possible (u64)value */
|
||||
s32 s32_min_value; /* minimum possible (s32)value */
|
||||
s32 s32_max_value; /* maximum possible (s32)value */
|
||||
u32 u32_min_value; /* minimum possible (u32)value */
|
||||
u32 u32_max_value; /* maximum possible (u32)value */
|
||||
struct cnum64 r64; /* 64-bit range as circular number */
|
||||
struct cnum32 r32; /* 32-bit range as circular number */
|
||||
/* For PTR_TO_PACKET, used to find other pointers with the same variable
|
||||
* offset, so they can share range knowledge.
|
||||
* For PTR_TO_MAP_VALUE_OR_NULL this is used to share which map value we
|
||||
@@ -211,66 +206,62 @@ struct bpf_reg_state {
|
||||
|
||||
static inline s64 reg_smin(const struct bpf_reg_state *reg)
|
||||
{
|
||||
return reg->smin_value;
|
||||
return cnum64_smin(reg->r64);
|
||||
}
|
||||
|
||||
static inline s64 reg_smax(const struct bpf_reg_state *reg)
|
||||
{
|
||||
return reg->smax_value;
|
||||
return cnum64_smax(reg->r64);
|
||||
}
|
||||
|
||||
static inline u64 reg_umin(const struct bpf_reg_state *reg)
|
||||
{
|
||||
return reg->umin_value;
|
||||
return cnum64_umin(reg->r64);
|
||||
}
|
||||
|
||||
static inline u64 reg_umax(const struct bpf_reg_state *reg)
|
||||
{
|
||||
return reg->umax_value;
|
||||
return cnum64_umax(reg->r64);
|
||||
}
|
||||
|
||||
static inline s32 reg_s32_min(const struct bpf_reg_state *reg)
|
||||
{
|
||||
return reg->s32_min_value;
|
||||
return cnum32_smin(reg->r32);
|
||||
}
|
||||
|
||||
static inline s32 reg_s32_max(const struct bpf_reg_state *reg)
|
||||
{
|
||||
return reg->s32_max_value;
|
||||
return cnum32_smax(reg->r32);
|
||||
}
|
||||
|
||||
static inline u32 reg_u32_min(const struct bpf_reg_state *reg)
|
||||
{
|
||||
return reg->u32_min_value;
|
||||
return cnum32_umin(reg->r32);
|
||||
}
|
||||
|
||||
static inline u32 reg_u32_max(const struct bpf_reg_state *reg)
|
||||
{
|
||||
return reg->u32_max_value;
|
||||
return cnum32_umax(reg->r32);
|
||||
}
|
||||
|
||||
static inline void reg_set_srange32(struct bpf_reg_state *reg, s32 smin, s32 smax)
|
||||
{
|
||||
reg->s32_min_value = smin;
|
||||
reg->s32_max_value = smax;
|
||||
reg->r32 = cnum32_from_srange(smin, smax);
|
||||
}
|
||||
|
||||
static inline void reg_set_urange32(struct bpf_reg_state *reg, u32 umin, u32 umax)
|
||||
{
|
||||
reg->u32_min_value = umin;
|
||||
reg->u32_max_value = umax;
|
||||
reg->r32 = cnum32_from_urange(umin, umax);
|
||||
}
|
||||
|
||||
static inline void reg_set_srange64(struct bpf_reg_state *reg, s64 smin, s64 smax)
|
||||
{
|
||||
reg->smin_value = smin;
|
||||
reg->smax_value = smax;
|
||||
reg->r64 = cnum64_from_srange(smin, smax);
|
||||
}
|
||||
|
||||
static inline void reg_set_urange64(struct bpf_reg_state *reg, u64 umin, u64 umax)
|
||||
{
|
||||
reg->umin_value = umin;
|
||||
reg->umax_value = umax;
|
||||
reg->r64 = cnum64_from_urange(umin, umax);
|
||||
}
|
||||
|
||||
enum bpf_stack_slot_type {
|
||||
|
||||
+118
-725
File diff suppressed because it is too large
Load Diff
@@ -478,6 +478,52 @@ static struct range range_refine_in_halves(enum num_t x_t, struct range x,
|
||||
|
||||
}
|
||||
|
||||
static __always_inline u64 next_u32_block(u64 x) { return x + (1ULL << 32); }
|
||||
static __always_inline u64 prev_u32_block(u64 x) { return x - (1ULL << 32); }
|
||||
|
||||
/* Is v within the circular u64 range [base, base + len]? */
|
||||
static __always_inline bool u64_range_contains(u64 v, u64 base, u64 len)
|
||||
{
|
||||
return v - base <= len;
|
||||
}
|
||||
|
||||
/* Is v within the circular u32 range [base, base + len]? */
|
||||
static __always_inline bool u32_range_contains(u32 v, u32 base, u32 len)
|
||||
{
|
||||
return v - base <= len;
|
||||
}
|
||||
|
||||
static bool range64_range32_intersect(enum num_t a_t,
|
||||
struct range a /* 64 */,
|
||||
struct range b /* 32 */,
|
||||
struct range *out /* 64 */)
|
||||
{
|
||||
u64 b_len = (u32)(b.b - b.a);
|
||||
u64 a_len = a.b - a.a;
|
||||
u64 lo, hi;
|
||||
|
||||
if (u32_range_contains((u32)a.a, (u32)b.a, b_len)) {
|
||||
lo = a.a;
|
||||
} else {
|
||||
lo = swap_low32(a.a, (u32)b.a);
|
||||
if (!u64_range_contains(lo, a.a, a_len))
|
||||
lo = next_u32_block(lo);
|
||||
if (!u64_range_contains(lo, a.a, a_len))
|
||||
return false;
|
||||
}
|
||||
if (u32_range_contains(a.b, (u32)b.a, b_len)) {
|
||||
hi = a.b;
|
||||
} else {
|
||||
hi = swap_low32(a.b, (u32)b.b);
|
||||
if (!u64_range_contains(hi, a.a, a_len))
|
||||
hi = prev_u32_block(hi);
|
||||
if (!u64_range_contains(hi, a.a, a_len))
|
||||
return false;
|
||||
}
|
||||
*out = range(a_t, lo, hi);
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -533,23 +579,12 @@ static struct range range_refine(enum num_t x_t, struct range x, enum num_t y_t,
|
||||
}
|
||||
}
|
||||
|
||||
/* the case when new range knowledge, *y*, is a 32-bit subregister
|
||||
* range, while previous range knowledge, *x*, is a full register
|
||||
* 64-bit range, needs special treatment to take into account upper 32
|
||||
* bits of full register range
|
||||
*/
|
||||
if (t_is_32(y_t) && !t_is_32(x_t)) {
|
||||
struct range x_swap;
|
||||
struct range x1;
|
||||
|
||||
/* some combinations of upper 32 bits and sign bit can lead to
|
||||
* invalid ranges, in such cases it's easier to detect them
|
||||
* after cast/swap than try to enumerate all the conditions
|
||||
* under which transformation and knowledge transfer is valid
|
||||
*/
|
||||
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_intersection(x_t, x, x_swap);
|
||||
if (range64_range32_intersect(x_t, x, y, &x1))
|
||||
return x1;
|
||||
return x;
|
||||
}
|
||||
|
||||
/* otherwise, plain range cast and intersection works */
|
||||
@@ -1300,6 +1335,26 @@ static bool assert_range_eq(enum num_t t, struct range x, struct range y,
|
||||
return false;
|
||||
}
|
||||
|
||||
/* For a pair of signed/unsigned t1/t2 checks if r1/r2 intersect in two intervals. */
|
||||
static bool needs_two_arcs(enum num_t t1, struct range r1,
|
||||
enum num_t t2, struct range r2)
|
||||
{
|
||||
u64 lo = cast_t(t1, r2.a);
|
||||
u64 hi = cast_t(t1, r2.b);
|
||||
|
||||
/* does r2 wrap in t1's domain: [0, hi] ∪ [lo, MAX]? */
|
||||
return lo > hi && r1.a <= hi && r1.b >= lo;
|
||||
}
|
||||
|
||||
static bool reg_state_needs_two_arcs(struct reg_state *s)
|
||||
{
|
||||
if (!s->valid)
|
||||
return false;
|
||||
|
||||
return needs_two_arcs(U64, s->r[U64], S64, s->r[S64]) ||
|
||||
needs_two_arcs(U32, s->r[U32], S32, s->r[S32]);
|
||||
}
|
||||
|
||||
/* Validate that register states match, and print details if they don't */
|
||||
static bool assert_reg_state_eq(struct reg_state *r, struct reg_state *e, const char *ctx)
|
||||
{
|
||||
@@ -1524,6 +1579,11 @@ static int verify_case_op(enum num_t init_t, enum num_t cond_t,
|
||||
!assert_reg_state_eq(&fr2, &fe2, "false_reg2") ||
|
||||
!assert_reg_state_eq(&tr1, &te1, "true_reg1") ||
|
||||
!assert_reg_state_eq(&tr2, &te2, "true_reg2")) {
|
||||
if (reg_state_needs_two_arcs(&fe1) || reg_state_needs_two_arcs(&fe2) ||
|
||||
reg_state_needs_two_arcs(&te1) || reg_state_needs_two_arcs(&te2)) {
|
||||
test__skip();
|
||||
return 0;
|
||||
}
|
||||
failed = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1239,7 +1239,8 @@ l0_%=: r0 = 0; \
|
||||
SEC("tc")
|
||||
__description("multiply mixed sign bounds. test 1")
|
||||
__success __log_level(2)
|
||||
__msg("r6 *= r7 {{.*}}; R6=scalar(smin=umin=0x1bc16d5cd4927ee1,smax=umax=0x1bc16d674ec80000,smax32=0x7ffffeff,umax32=0xfffffeff,var_off=(0x1bc16d4000000000; 0x3ffffffeff))")
|
||||
__msg("r6 *= r7 {{.*}}; R6=scalar(smin=umin=0x1bc16d5cd4927ee1,smax=umax=0x1bc16d674ec80000,smax32=0x7ffffeff,var_off=(0x1bc16d4000000000; 0x3ffffffeff))")
|
||||
/* cnum can't represent both [0, 0xffff_feff] and [0x8000_0000, 0x7fff_feff], so it picks one */
|
||||
__naked void mult_mixed0_sign(void)
|
||||
{
|
||||
asm volatile (
|
||||
@@ -1648,7 +1649,8 @@ l0_%=: r0 = 0; \
|
||||
SEC("socket")
|
||||
__description("bounds deduction cross sign boundary, two overlaps")
|
||||
__failure
|
||||
__msg("3: (2d) if r0 > r1 {{.*}} R0=scalar(smin=smin32=-128,smax=smax32=127,umax=0xffffffffffffff80)")
|
||||
__msg("3: (2d) if r0 > r1 {{.*}} R0=scalar(smin=smin32=-128,smax=smax32=127)")
|
||||
/* smin=-128 includes point 0xffffffffffffff80 */
|
||||
__msg("frame pointer is read only")
|
||||
__naked void bounds_deduct_two_overlaps(void)
|
||||
{
|
||||
@@ -2043,7 +2045,8 @@ __naked void signed_unsigned_intersection32_case2(void *ctx)
|
||||
*/
|
||||
SEC("socket")
|
||||
__description("bounds refinement: 64bits ranges not overwritten by 32bits ranges")
|
||||
__msg("3: (65) if r0 s> 0x2 {{.*}} R0=scalar(smin=0x8000000000000002,smax=2,umin=smin32=umin32=2,umax=0xffffffff00000003,smax32=umax32=3")
|
||||
__msg("3: (65) if r0 s> 0x2 {{.*}} R0=scalar(smin=0x8000000000000002,smax=2,smin32=umin32=2,smax32=umax32=3,var_off{{.*}}))")
|
||||
/* Can't represent both [S64_MIN+2, 2] and [2, U64_MAX - U32_MAX + 2] at the same time, picks shorter interval */
|
||||
__msg("4: (25) if r0 > 0x13 {{.*}} R0=2")
|
||||
__success __log_level(2)
|
||||
__naked void refinement_32bounds_not_overwriting_64bounds(void *ctx)
|
||||
|
||||
@@ -558,7 +558,8 @@ __description("arsh32 imm sign negative extend check")
|
||||
__success __retval(0)
|
||||
__log_level(2)
|
||||
__msg("3: (17) r6 -= 4095 ; R6=scalar(smin=smin32=-4095,smax=smax32=0)")
|
||||
__msg("4: (67) r6 <<= 32 ; R6=scalar(smin=0xfffff00100000000,smax=smax32=umax32=0,umax=0xffffffff00000000,smin32=0,var_off=(0x0; 0xffffffff00000000))")
|
||||
__msg("4: (67) r6 <<= 32 ; R6=scalar(smin=0xfffff00100000000,smax=smax32=umax32=0,smin32=0,var_off=(0x0; 0xffffffff00000000))")
|
||||
/* represents shorter of signed / unsigned 64-bit ranges */
|
||||
__msg("5: (c7) r6 s>>= 32 ; R6=scalar(smin=smin32=-4095,smax=smax32=0)")
|
||||
__naked void arsh32_imm_sign_extend_negative_check(void)
|
||||
{
|
||||
@@ -581,7 +582,8 @@ __description("arsh32 imm sign extend check")
|
||||
__success __retval(0)
|
||||
__log_level(2)
|
||||
__msg("3: (17) r6 -= 2047 ; R6=scalar(smin=smin32=-2047,smax=smax32=2048)")
|
||||
__msg("4: (67) r6 <<= 32 ; R6=scalar(smin=0xfffff80100000000,smax=0x80000000000,umax=0xffffffff00000000,smin32=0,smax32=umax32=0,var_off=(0x0; 0xffffffff00000000))")
|
||||
__msg("4: (67) r6 <<= 32 ; R6=scalar(smin=0xfffff80100000000,smax=0x80000000000,smin32=0,smax32=umax32=0,var_off=(0x0; 0xffffffff00000000))")
|
||||
/* represents shorter of signed / unsigned 64-bit ranges */
|
||||
__msg("5: (c7) r6 s>>= 32 ; R6=scalar(smin=smin32=-2047,smax=smax32=2048)")
|
||||
__naked void arsh32_imm_sign_extend_check(void)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user