Skip to content

Commit 6fb3f72

Browse files
committed
Merge branch 'improvements-for-tracking-scalars-in-the-bpf-verifier'
Maxim Mikityanskiy says: ==================== Improvements for tracking scalars in the BPF verifier From: Maxim Mikityanskiy <[email protected]> The goal of this series is to extend the verifier's capabilities of tracking scalars when they are spilled to stack, especially when the spill or fill is narrowing. It also contains a fix by Eduard for infinite loop detection and a state pruning optimization by Eduard that compensates for a verification complexity regression introduced by tracking unbounded scalars. These improvements reduce the surface of false rejections that I saw while working on Cilium codebase. Patches 1-9 of the original series were previously applied in v2. Patches 1-2 (Maxim): Support the case when boundary checks are first performed after the register was spilled to the stack. Patches 3-4 (Maxim): Support narrowing fills. Patches 5-6 (Eduard): Optimization for state pruning in stacksafe() to mitigate the verification complexity regression. veristat -e file,prog,states -f '!states_diff<50' -f '!states_pct<10' -f '!states_a<10' -f '!states_b<10' -C ... * Without patch 5: File Program States (A) States (B) States (DIFF) -------------------- -------- ---------- ---------- ---------------- pyperf100.bpf.o on_event 4878 6528 +1650 (+33.83%) pyperf180.bpf.o on_event 6936 11032 +4096 (+59.05%) pyperf600.bpf.o on_event 22271 39455 +17184 (+77.16%) pyperf600_iter.bpf.o on_event 400 490 +90 (+22.50%) strobemeta.bpf.o on_event 4895 14028 +9133 (+186.58%) * With patch 5: File Program States (A) States (B) States (DIFF) ----------------------- ------------- ---------- ---------- --------------- bpf_xdp.o tail_lb_ipv4 2770 2224 -546 (-19.71%) pyperf100.bpf.o on_event 4878 5848 +970 (+19.89%) pyperf180.bpf.o on_event 6936 8868 +1932 (+27.85%) pyperf600.bpf.o on_event 22271 29656 +7385 (+33.16%) pyperf600_iter.bpf.o on_event 400 450 +50 (+12.50%) xdp_synproxy_kern.bpf.o syncookie_tc 280 226 -54 (-19.29%) xdp_synproxy_kern.bpf.o syncookie_xdp 302 228 -74 (-24.50%) v2 changes: Fixed comments in patch 1, moved endianness checks to header files in patch 12 where possible, added Eduard's ACKs. v3 changes: Maxim: Removed __is_scalar_unbounded altogether, addressed Andrii's comments. Eduard: Patch #5 (#14 in v2) changed significantly: - Logical changes: - Handling of STACK_{MISC,ZERO} mix turned out to be incorrect: a mix of MISC and ZERO in old state is not equivalent to e.g. just MISC is current state, because verifier could have deduced zero scalars from ZERO slots in old state for some loads. - There is no reason to limit the change only to cases when old or current stack is a spill of unbounded scalar, it is valid to compare any 64-bit scalar spill with fake register impersonating MISC. - STACK_ZERO vs spilled zero case was dropped, after recent changes for zero handling by Andrii and Yonghong it is hard (impossible?) to conjure all ZERO slots for an spi. => the case does not make any difference in veristat results. - Use global static variable for unbound_reg (Andrii) - Code shuffling to remove duplication in stacksafe() (Andrii) ==================== Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Andrii Nakryiko <[email protected]>
2 parents 943b043 + 73a28d9 commit 6fb3f72

File tree

3 files changed

+404
-32
lines changed

3 files changed

+404
-32
lines changed

include/linux/bpf_verifier.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,15 @@ static inline void mark_verifier_state_scratched(struct bpf_verifier_env *env)
919919
env->scratched_stack_slots = ~0ULL;
920920
}
921921

922+
static inline bool bpf_stack_narrow_access_ok(int off, int fill_size, int spill_size)
923+
{
924+
#ifdef __BIG_ENDIAN
925+
off -= spill_size - fill_size;
926+
#endif
927+
928+
return !(off % BPF_REG_SIZE);
929+
}
930+
922931
const char *reg_type_str(struct bpf_verifier_env *env, enum bpf_reg_type type);
923932
const char *dynptr_type_str(enum bpf_dynptr_type type);
924933
const char *iter_type_str(const struct btf *btf, u32 btf_id);

kernel/bpf/verifier.c

Lines changed: 81 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,6 +1155,12 @@ static bool is_spilled_scalar_reg(const struct bpf_stack_state *stack)
11551155
stack->spilled_ptr.type == SCALAR_VALUE;
11561156
}
11571157

1158+
static bool is_spilled_scalar_reg64(const struct bpf_stack_state *stack)
1159+
{
1160+
return stack->slot_type[0] == STACK_SPILL &&
1161+
stack->spilled_ptr.type == SCALAR_VALUE;
1162+
}
1163+
11581164
/* Mark stack slot as STACK_MISC, unless it is already STACK_INVALID, in which
11591165
* case they are equivalent, or it's STACK_ZERO, in which case we preserve
11601166
* more precise STACK_ZERO.
@@ -2264,8 +2270,7 @@ static void __reg_assign_32_into_64(struct bpf_reg_state *reg)
22642270
}
22652271

22662272
/* Mark a register as having a completely unknown (scalar) value. */
2267-
static void __mark_reg_unknown(const struct bpf_verifier_env *env,
2268-
struct bpf_reg_state *reg)
2273+
static void __mark_reg_unknown_imprecise(struct bpf_reg_state *reg)
22692274
{
22702275
/*
22712276
* Clear type, off, and union(map_ptr, range) and
@@ -2277,10 +2282,20 @@ static void __mark_reg_unknown(const struct bpf_verifier_env *env,
22772282
reg->ref_obj_id = 0;
22782283
reg->var_off = tnum_unknown;
22792284
reg->frameno = 0;
2280-
reg->precise = !env->bpf_capable;
2285+
reg->precise = false;
22812286
__mark_reg_unbounded(reg);
22822287
}
22832288

2289+
/* Mark a register as having a completely unknown (scalar) value,
2290+
* initialize .precise as true when not bpf capable.
2291+
*/
2292+
static void __mark_reg_unknown(const struct bpf_verifier_env *env,
2293+
struct bpf_reg_state *reg)
2294+
{
2295+
__mark_reg_unknown_imprecise(reg);
2296+
reg->precise = !env->bpf_capable;
2297+
}
2298+
22842299
static void mark_reg_unknown(struct bpf_verifier_env *env,
22852300
struct bpf_reg_state *regs, u32 regno)
22862301
{
@@ -4380,20 +4395,6 @@ static u64 reg_const_value(struct bpf_reg_state *reg, bool subreg32)
43804395
return subreg32 ? tnum_subreg(reg->var_off).value : reg->var_off.value;
43814396
}
43824397

4383-
static bool __is_scalar_unbounded(struct bpf_reg_state *reg)
4384-
{
4385-
return tnum_is_unknown(reg->var_off) &&
4386-
reg->smin_value == S64_MIN && reg->smax_value == S64_MAX &&
4387-
reg->umin_value == 0 && reg->umax_value == U64_MAX &&
4388-
reg->s32_min_value == S32_MIN && reg->s32_max_value == S32_MAX &&
4389-
reg->u32_min_value == 0 && reg->u32_max_value == U32_MAX;
4390-
}
4391-
4392-
static bool register_is_bounded(struct bpf_reg_state *reg)
4393-
{
4394-
return reg->type == SCALAR_VALUE && !__is_scalar_unbounded(reg);
4395-
}
4396-
43974398
static bool __is_pointer_value(bool allow_ptr_leaks,
43984399
const struct bpf_reg_state *reg)
43994400
{
@@ -4504,7 +4505,7 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
45044505
return err;
45054506

45064507
mark_stack_slot_scratched(env, spi);
4507-
if (reg && !(off % BPF_REG_SIZE) && register_is_bounded(reg) && env->bpf_capable) {
4508+
if (reg && !(off % BPF_REG_SIZE) && reg->type == SCALAR_VALUE && env->bpf_capable) {
45084509
bool reg_value_fits;
45094510

45104511
reg_value_fits = get_reg_width(reg) <= BITS_PER_BYTE * size;
@@ -4792,14 +4793,21 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
47924793
if (dst_regno < 0)
47934794
return 0;
47944795

4795-
if (!(off % BPF_REG_SIZE) && size == spill_size) {
4796+
if (size <= spill_size &&
4797+
bpf_stack_narrow_access_ok(off, size, spill_size)) {
47964798
/* The earlier check_reg_arg() has decided the
47974799
* subreg_def for this insn. Save it first.
47984800
*/
47994801
s32 subreg_def = state->regs[dst_regno].subreg_def;
48004802

48014803
copy_register_state(&state->regs[dst_regno], reg);
48024804
state->regs[dst_regno].subreg_def = subreg_def;
4805+
4806+
/* Break the relation on a narrowing fill.
4807+
* coerce_reg_to_size will adjust the boundaries.
4808+
*/
4809+
if (get_reg_width(reg) > size * BITS_PER_BYTE)
4810+
state->regs[dst_regno].id = 0;
48034811
} else {
48044812
int spill_cnt = 0, zero_cnt = 0;
48054813

@@ -6075,10 +6083,10 @@ static void coerce_reg_to_size(struct bpf_reg_state *reg, int size)
60756083
* values are also truncated so we push 64-bit bounds into
60766084
* 32-bit bounds. Above were truncated < 32-bits already.
60776085
*/
6078-
if (size < 4) {
6086+
if (size < 4)
60796087
__mark_reg32_unbounded(reg);
6080-
reg_bounds_sync(reg);
6081-
}
6088+
6089+
reg_bounds_sync(reg);
60826090
}
60836091

60846092
static void set_sext64_default_val(struct bpf_reg_state *reg, int size)
@@ -16493,6 +16501,43 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
1649316501
}
1649416502
}
1649516503

16504+
static struct bpf_reg_state unbound_reg;
16505+
16506+
static __init int unbound_reg_init(void)
16507+
{
16508+
__mark_reg_unknown_imprecise(&unbound_reg);
16509+
unbound_reg.live |= REG_LIVE_READ;
16510+
return 0;
16511+
}
16512+
late_initcall(unbound_reg_init);
16513+
16514+
static bool is_stack_all_misc(struct bpf_verifier_env *env,
16515+
struct bpf_stack_state *stack)
16516+
{
16517+
u32 i;
16518+
16519+
for (i = 0; i < ARRAY_SIZE(stack->slot_type); ++i) {
16520+
if ((stack->slot_type[i] == STACK_MISC) ||
16521+
(stack->slot_type[i] == STACK_INVALID && env->allow_uninit_stack))
16522+
continue;
16523+
return false;
16524+
}
16525+
16526+
return true;
16527+
}
16528+
16529+
static struct bpf_reg_state *scalar_reg_for_stack(struct bpf_verifier_env *env,
16530+
struct bpf_stack_state *stack)
16531+
{
16532+
if (is_spilled_scalar_reg64(stack))
16533+
return &stack->spilled_ptr;
16534+
16535+
if (is_stack_all_misc(env, stack))
16536+
return &unbound_reg;
16537+
16538+
return NULL;
16539+
}
16540+
1649616541
static bool stacksafe(struct bpf_verifier_env *env, struct bpf_func_state *old,
1649716542
struct bpf_func_state *cur, struct bpf_idmap *idmap, bool exact)
1649816543
{
@@ -16531,6 +16576,20 @@ static bool stacksafe(struct bpf_verifier_env *env, struct bpf_func_state *old,
1653116576
if (i >= cur->allocated_stack)
1653216577
return false;
1653316578

16579+
/* 64-bit scalar spill vs all slots MISC and vice versa.
16580+
* Load from all slots MISC produces unbound scalar.
16581+
* Construct a fake register for such stack and call
16582+
* regsafe() to ensure scalar ids are compared.
16583+
*/
16584+
old_reg = scalar_reg_for_stack(env, &old->stack[spi]);
16585+
cur_reg = scalar_reg_for_stack(env, &cur->stack[spi]);
16586+
if (old_reg && cur_reg) {
16587+
if (!regsafe(env, old_reg, cur_reg, idmap, exact))
16588+
return false;
16589+
i += BPF_REG_SIZE - 1;
16590+
continue;
16591+
}
16592+
1653416593
/* if old state was safe with misc data in the stack
1653516594
* it will be safe with zero-initialized stack.
1653616595
* The opposite is not true

0 commit comments

Comments
 (0)