Skip to content

Commit 96a30e4

Browse files
anakryikoAlexei Starovoitov
authored andcommitted
bpf: use common instruction history across all states
Instead of allocating and copying instruction history each time we enqueue child verifier state, switch to a model where we use one common dynamically sized array of instruction history entries across all states. The key observation for proving this is correct is that instruction history is only relevant while state is active, which means it either is a current state (and thus we are actively modifying instruction history and no other state can interfere with us) or we are checkpointed state with some children still active (either enqueued or being current). In the latter case our portion of instruction history is finalized and won't change or grow, so as long as we keep it immutable until the state is finalized, we are good. Now, when state is finalized and is put into state hash for potentially future pruning lookups, instruction history is not used anymore. This is because instruction history is only used by precision marking logic, and we never modify precision markings for finalized states. So, instead of each state having its own small instruction history, we keep a global dynamically-sized instruction history, where each state in current DFS path from root to active state remembers its portion of instruction history. Current state can append to this history, but cannot modify any of its parent histories. Async callback state enqueueing, while logically detached from parent state, still is part of verification backtracking tree, so has to follow the same schema as normal state checkpoints. Because the insn_hist array can be grown through realloc, states don't keep pointers, they instead maintain two indices, [start, end), into global instruction history array. End is exclusive index, so `start == end` means there is no relevant instruction history. This eliminates a lot of allocations and minimizes overall memory usage. For instance, running a worst-case test from [0] (but without the heuristics-based fix [1]), it took 12.5 minutes until we get -ENOMEM. With the changes in this patch the whole test succeeds in 10 minutes (very slow, so heuristics from [1] is important, of course). To further validate correctness, veristat-based comparison was performed for Meta production BPF objects and BPF selftests objects. In both cases there were no differences *at all* in terms of verdict or instruction and state counts, providing a good confidence in the change. Having this low-memory-overhead solution of keeping dynamic per-instruction history cheaply opens up some new possibilities, like keeping extra information for literally every single validated instruction. This will be used for simplifying precision backpropagation logic in follow up patches. [0] https://lore.kernel.org/bpf/[email protected]/ [1] https://lore.kernel.org/bpf/[email protected]/ Acked-by: Eduard Zingerman <[email protected]> Signed-off-by: Andrii Nakryiko <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Alexei Starovoitov <[email protected]>
1 parent 4ff04ab commit 96a30e4

File tree

2 files changed

+63
-63
lines changed

2 files changed

+63
-63
lines changed

include/linux/bpf_verifier.h

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ struct bpf_func_state {
334334

335335
#define MAX_CALL_FRAMES 8
336336

337-
/* instruction history flags, used in bpf_jmp_history_entry.flags field */
337+
/* instruction history flags, used in bpf_insn_hist_entry.flags field */
338338
enum {
339339
/* instruction references stack slot through PTR_TO_STACK register;
340340
* we also store stack's frame number in lower 3 bits (MAX_CALL_FRAMES is 8)
@@ -352,7 +352,7 @@ enum {
352352
static_assert(INSN_F_FRAMENO_MASK + 1 >= MAX_CALL_FRAMES);
353353
static_assert(INSN_F_SPI_MASK + 1 >= MAX_BPF_STACK / 8);
354354

355-
struct bpf_jmp_history_entry {
355+
struct bpf_insn_hist_entry {
356356
u32 idx;
357357
/* insn idx can't be bigger than 1 million */
358358
u32 prev_idx : 22;
@@ -442,13 +442,14 @@ struct bpf_verifier_state {
442442
* See get_loop_entry() for more information.
443443
*/
444444
struct bpf_verifier_state *loop_entry;
445-
/* jmp history recorded from first to last.
446-
* backtracking is using it to go from last to first.
447-
* For most states jmp_history_cnt is [0-3].
445+
/* Sub-range of env->insn_hist[] corresponding to this state's
446+
* instruction history.
447+
* Backtracking is using it to go from last to first.
448+
* For most states instruction history is short, 0-3 instructions.
448449
* For loops can go up to ~40.
449450
*/
450-
struct bpf_jmp_history_entry *jmp_history;
451-
u32 jmp_history_cnt;
451+
u32 insn_hist_start;
452+
u32 insn_hist_end;
452453
u32 dfs_depth;
453454
u32 callback_unroll_depth;
454455
u32 may_goto_depth;
@@ -738,7 +739,9 @@ struct bpf_verifier_env {
738739
int cur_stack;
739740
} cfg;
740741
struct backtrack_state bt;
741-
struct bpf_jmp_history_entry *cur_hist_ent;
742+
struct bpf_insn_hist_entry *insn_hist;
743+
struct bpf_insn_hist_entry *cur_hist_ent;
744+
u32 insn_hist_cap;
742745
u32 pass_cnt; /* number of times do_check() was called */
743746
u32 subprog_cnt;
744747
/* number of instructions analyzed by the verifier */

kernel/bpf/verifier.c

Lines changed: 52 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,13 +1452,6 @@ static void free_func_state(struct bpf_func_state *state)
14521452
kfree(state);
14531453
}
14541454

1455-
static void clear_jmp_history(struct bpf_verifier_state *state)
1456-
{
1457-
kfree(state->jmp_history);
1458-
state->jmp_history = NULL;
1459-
state->jmp_history_cnt = 0;
1460-
}
1461-
14621455
static void free_verifier_state(struct bpf_verifier_state *state,
14631456
bool free_self)
14641457
{
@@ -1468,7 +1461,6 @@ static void free_verifier_state(struct bpf_verifier_state *state,
14681461
free_func_state(state->frame[i]);
14691462
state->frame[i] = NULL;
14701463
}
1471-
clear_jmp_history(state);
14721464
if (free_self)
14731465
kfree(state);
14741466
}
@@ -1494,13 +1486,6 @@ static int copy_verifier_state(struct bpf_verifier_state *dst_state,
14941486
struct bpf_func_state *dst;
14951487
int i, err;
14961488

1497-
dst_state->jmp_history = copy_array(dst_state->jmp_history, src->jmp_history,
1498-
src->jmp_history_cnt, sizeof(*dst_state->jmp_history),
1499-
GFP_USER);
1500-
if (!dst_state->jmp_history)
1501-
return -ENOMEM;
1502-
dst_state->jmp_history_cnt = src->jmp_history_cnt;
1503-
15041489
/* if dst has more stack frames then src frame, free them, this is also
15051490
* necessary in case of exceptional exits using bpf_throw.
15061491
*/
@@ -1517,6 +1502,8 @@ static int copy_verifier_state(struct bpf_verifier_state *dst_state,
15171502
dst_state->parent = src->parent;
15181503
dst_state->first_insn_idx = src->first_insn_idx;
15191504
dst_state->last_insn_idx = src->last_insn_idx;
1505+
dst_state->insn_hist_start = src->insn_hist_start;
1506+
dst_state->insn_hist_end = src->insn_hist_end;
15201507
dst_state->dfs_depth = src->dfs_depth;
15211508
dst_state->callback_unroll_depth = src->callback_unroll_depth;
15221509
dst_state->used_as_loop_entry = src->used_as_loop_entry;
@@ -2569,9 +2556,14 @@ static struct bpf_verifier_state *push_async_cb(struct bpf_verifier_env *env,
25692556
* The caller state doesn't matter.
25702557
* This is async callback. It starts in a fresh stack.
25712558
* Initialize it similar to do_check_common().
2559+
* But we do need to make sure to not clobber insn_hist, so we keep
2560+
* chaining insn_hist_start/insn_hist_end indices as for a normal
2561+
* child state.
25722562
*/
25732563
elem->st.branches = 1;
25742564
elem->st.in_sleepable = is_sleepable;
2565+
elem->st.insn_hist_start = env->cur_state->insn_hist_end;
2566+
elem->st.insn_hist_end = elem->st.insn_hist_start;
25752567
frame = kzalloc(sizeof(*frame), GFP_KERNEL);
25762568
if (!frame)
25772569
goto err;
@@ -3551,11 +3543,10 @@ static void linked_regs_unpack(u64 val, struct linked_regs *s)
35513543
}
35523544

35533545
/* for any branch, call, exit record the history of jmps in the given state */
3554-
static int push_jmp_history(struct bpf_verifier_env *env, struct bpf_verifier_state *cur,
3555-
int insn_flags, u64 linked_regs)
3546+
static int push_insn_history(struct bpf_verifier_env *env, struct bpf_verifier_state *cur,
3547+
int insn_flags, u64 linked_regs)
35563548
{
3557-
u32 cnt = cur->jmp_history_cnt;
3558-
struct bpf_jmp_history_entry *p;
3549+
struct bpf_insn_hist_entry *p;
35593550
size_t alloc_size;
35603551

35613552
/* combine instruction flags if we already recorded this instruction */
@@ -3575,29 +3566,32 @@ static int push_jmp_history(struct bpf_verifier_env *env, struct bpf_verifier_st
35753566
return 0;
35763567
}
35773568

3578-
cnt++;
3579-
alloc_size = kmalloc_size_roundup(size_mul(cnt, sizeof(*p)));
3580-
p = krealloc(cur->jmp_history, alloc_size, GFP_USER);
3581-
if (!p)
3582-
return -ENOMEM;
3583-
cur->jmp_history = p;
3569+
if (cur->insn_hist_end + 1 > env->insn_hist_cap) {
3570+
alloc_size = size_mul(cur->insn_hist_end + 1, sizeof(*p));
3571+
p = kvrealloc(env->insn_hist, alloc_size, GFP_USER);
3572+
if (!p)
3573+
return -ENOMEM;
3574+
env->insn_hist = p;
3575+
env->insn_hist_cap = alloc_size / sizeof(*p);
3576+
}
35843577

3585-
p = &cur->jmp_history[cnt - 1];
3578+
p = &env->insn_hist[cur->insn_hist_end];
35863579
p->idx = env->insn_idx;
35873580
p->prev_idx = env->prev_insn_idx;
35883581
p->flags = insn_flags;
35893582
p->linked_regs = linked_regs;
3590-
cur->jmp_history_cnt = cnt;
3583+
3584+
cur->insn_hist_end++;
35913585
env->cur_hist_ent = p;
35923586

35933587
return 0;
35943588
}
35953589

3596-
static struct bpf_jmp_history_entry *get_jmp_hist_entry(struct bpf_verifier_state *st,
3597-
u32 hist_end, int insn_idx)
3590+
static struct bpf_insn_hist_entry *get_insn_hist_entry(struct bpf_verifier_env *env,
3591+
u32 hist_start, u32 hist_end, int insn_idx)
35983592
{
3599-
if (hist_end > 0 && st->jmp_history[hist_end - 1].idx == insn_idx)
3600-
return &st->jmp_history[hist_end - 1];
3593+
if (hist_end > hist_start && env->insn_hist[hist_end - 1].idx == insn_idx)
3594+
return &env->insn_hist[hist_end - 1];
36013595
return NULL;
36023596
}
36033597

@@ -3614,25 +3608,26 @@ static struct bpf_jmp_history_entry *get_jmp_hist_entry(struct bpf_verifier_stat
36143608
* history entry recording a jump from last instruction of parent state and
36153609
* first instruction of given state.
36163610
*/
3617-
static int get_prev_insn_idx(struct bpf_verifier_state *st, int i,
3618-
u32 *history)
3611+
static int get_prev_insn_idx(const struct bpf_verifier_env *env,
3612+
struct bpf_verifier_state *st,
3613+
int insn_idx, u32 hist_start, u32 *hist_endp)
36193614
{
3620-
u32 cnt = *history;
3615+
u32 hist_end = *hist_endp;
3616+
u32 cnt = hist_end - hist_start;
36213617

3622-
if (i == st->first_insn_idx) {
3618+
if (insn_idx == st->first_insn_idx) {
36233619
if (cnt == 0)
36243620
return -ENOENT;
3625-
if (cnt == 1 && st->jmp_history[0].idx == i)
3621+
if (cnt == 1 && env->insn_hist[hist_start].idx == insn_idx)
36263622
return -ENOENT;
36273623
}
36283624

3629-
if (cnt && st->jmp_history[cnt - 1].idx == i) {
3630-
i = st->jmp_history[cnt - 1].prev_idx;
3631-
(*history)--;
3625+
if (cnt && env->insn_hist[hist_end - 1].idx == insn_idx) {
3626+
(*hist_endp)--;
3627+
return env->insn_hist[hist_end - 1].prev_idx;
36323628
} else {
3633-
i--;
3629+
return insn_idx - 1;
36343630
}
3635-
return i;
36363631
}
36373632

36383633
static const char *disasm_kfunc_name(void *data, const struct bpf_insn *insn)
@@ -3804,7 +3799,7 @@ static void fmt_stack_mask(char *buf, ssize_t buf_sz, u64 stack_mask)
38043799
/* If any register R in hist->linked_regs is marked as precise in bt,
38053800
* do bt_set_frame_{reg,slot}(bt, R) for all registers in hist->linked_regs.
38063801
*/
3807-
static void bt_sync_linked_regs(struct backtrack_state *bt, struct bpf_jmp_history_entry *hist)
3802+
static void bt_sync_linked_regs(struct backtrack_state *bt, struct bpf_insn_hist_entry *hist)
38083803
{
38093804
struct linked_regs linked_regs;
38103805
bool some_precise = false;
@@ -3849,7 +3844,7 @@ static bool calls_callback(struct bpf_verifier_env *env, int insn_idx);
38493844
* - *was* processed previously during backtracking.
38503845
*/
38513846
static int backtrack_insn(struct bpf_verifier_env *env, int idx, int subseq_idx,
3852-
struct bpf_jmp_history_entry *hist, struct backtrack_state *bt)
3847+
struct bpf_insn_hist_entry *hist, struct backtrack_state *bt)
38533848
{
38543849
const struct bpf_insn_cbs cbs = {
38553850
.cb_call = disasm_kfunc_name,
@@ -4268,7 +4263,7 @@ static void mark_all_scalars_imprecise(struct bpf_verifier_env *env, struct bpf_
42684263
* SCALARS, as well as any other registers and slots that contribute to
42694264
* a tracked state of given registers/stack slots, depending on specific BPF
42704265
* assembly instructions (see backtrack_insns() for exact instruction handling
4271-
* logic). This backtracking relies on recorded jmp_history and is able to
4266+
* logic). This backtracking relies on recorded insn_hist and is able to
42724267
* traverse entire chain of parent states. This process ends only when all the
42734268
* necessary registers/slots and their transitive dependencies are marked as
42744269
* precise.
@@ -4385,8 +4380,9 @@ static int __mark_chain_precision(struct bpf_verifier_env *env, int regno)
43854380

43864381
for (;;) {
43874382
DECLARE_BITMAP(mask, 64);
4388-
u32 history = st->jmp_history_cnt;
4389-
struct bpf_jmp_history_entry *hist;
4383+
u32 hist_start = st->insn_hist_start;
4384+
u32 hist_end = st->insn_hist_end;
4385+
struct bpf_insn_hist_entry *hist;
43904386

43914387
if (env->log.level & BPF_LOG_LEVEL2) {
43924388
verbose(env, "mark_precise: frame%d: last_idx %d first_idx %d subseq_idx %d \n",
@@ -4425,7 +4421,7 @@ static int __mark_chain_precision(struct bpf_verifier_env *env, int regno)
44254421
err = 0;
44264422
skip_first = false;
44274423
} else {
4428-
hist = get_jmp_hist_entry(st, history, i);
4424+
hist = get_insn_hist_entry(env, hist_start, hist_end, i);
44294425
err = backtrack_insn(env, i, subseq_idx, hist, bt);
44304426
}
44314427
if (err == -ENOTSUPP) {
@@ -4442,7 +4438,7 @@ static int __mark_chain_precision(struct bpf_verifier_env *env, int regno)
44424438
*/
44434439
return 0;
44444440
subseq_idx = i;
4445-
i = get_prev_insn_idx(st, i, &history);
4441+
i = get_prev_insn_idx(env, st, i, hist_start, &hist_end);
44464442
if (i == -ENOENT)
44474443
break;
44484444
if (i >= env->prog->len) {
@@ -4808,7 +4804,7 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
48084804
}
48094805

48104806
if (insn_flags)
4811-
return push_jmp_history(env, env->cur_state, insn_flags, 0);
4807+
return push_insn_history(env, env->cur_state, insn_flags, 0);
48124808
return 0;
48134809
}
48144810

@@ -5115,7 +5111,7 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
51155111
insn_flags = 0; /* we are not restoring spilled register */
51165112
}
51175113
if (insn_flags)
5118-
return push_jmp_history(env, env->cur_state, insn_flags, 0);
5114+
return push_insn_history(env, env->cur_state, insn_flags, 0);
51195115
return 0;
51205116
}
51215117

@@ -15740,7 +15736,7 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
1574015736
if (dst_reg->type == SCALAR_VALUE && dst_reg->id)
1574115737
collect_linked_regs(this_branch, dst_reg->id, &linked_regs);
1574215738
if (linked_regs.cnt > 1) {
15743-
err = push_jmp_history(env, this_branch, 0, linked_regs_pack(&linked_regs));
15739+
err = push_insn_history(env, this_branch, 0, linked_regs_pack(&linked_regs));
1574415740
if (err)
1574515741
return err;
1574615742
}
@@ -18129,7 +18125,7 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx)
1812918125

1813018126
force_new_state = env->test_state_freq || is_force_checkpoint(env, insn_idx) ||
1813118127
/* Avoid accumulating infinitely long jmp history */
18132-
cur->jmp_history_cnt > 40;
18128+
cur->insn_hist_end - cur->insn_hist_start > 40;
1813318129

1813418130
/* bpf progs typically have pruning point every 4 instructions
1813518131
* http://vger.kernel.org/bpfconf2019.html#session-1
@@ -18327,7 +18323,7 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx)
1832718323
* the current state.
1832818324
*/
1832918325
if (is_jmp_point(env, env->insn_idx))
18330-
err = err ? : push_jmp_history(env, cur, 0, 0);
18326+
err = err ? : push_insn_history(env, cur, 0, 0);
1833118327
err = err ? : propagate_precision(env, &sl->state);
1833218328
if (err)
1833318329
return err;
@@ -18426,8 +18422,8 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx)
1842618422

1842718423
cur->parent = new;
1842818424
cur->first_insn_idx = insn_idx;
18425+
cur->insn_hist_start = cur->insn_hist_end;
1842918426
cur->dfs_depth = new->dfs_depth + 1;
18430-
clear_jmp_history(cur);
1843118427
new_sl->next = *explored_state(env, insn_idx);
1843218428
*explored_state(env, insn_idx) = new_sl;
1843318429
/* connect new state to parentage chain. Current frame needs all
@@ -18595,7 +18591,7 @@ static int do_check(struct bpf_verifier_env *env)
1859518591
}
1859618592

1859718593
if (is_jmp_point(env, env->insn_idx)) {
18598-
err = push_jmp_history(env, state, 0, 0);
18594+
err = push_insn_history(env, state, 0, 0);
1859918595
if (err)
1860018596
return err;
1860118597
}
@@ -22789,6 +22785,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3
2278922785
if (!is_priv)
2279022786
mutex_unlock(&bpf_verifier_lock);
2279122787
vfree(env->insn_aux_data);
22788+
kvfree(env->insn_hist);
2279222789
err_free_env:
2279322790
kvfree(env);
2279422791
return ret;

0 commit comments

Comments
 (0)