Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'Refactor verifier prune and jump point handling'

Andrii Nakryiko says:

====================

Disentangle prune and jump points in BPF verifier code. They are conceptually
independent but currently coupled together. This small patch set refactors
related code and make it possible to have some instruction marked as pruning
or jump point independently.

Besides just conceptual cleanliness, this allows to remove unnecessary jump
points (saving a tiny bit of performance and memory usage, potentially), and
even more importantly it allows for clean extension of special pruning points,
similarly to how it's done for BPF_FUNC_timer_set_callback. This will be used
by future patches implementing open-coded BPF iterators.

v1->v2:
- clarified path #3 commit message and a comment in the code (John);
- added back mark_jmp_point() to right after subprog call to record
non-linear implicit jump from BPF_EXIT to right after CALL <subprog>.
====================

Signed-off-by: Alexei Starovoitov <ast@kernel.org>

+64 -45
+1
include/linux/bpf_verifier.h
··· 452 452 /* below fields are initialized once */ 453 453 unsigned int orig_idx; /* original instruction index */ 454 454 bool prune_point; 455 + bool jmp_point; 455 456 }; 456 457 457 458 #define MAX_USED_MAPS 64 /* max number of maps accessed by one eBPF program */
+63 -45
kernel/bpf/verifier.c
··· 2530 2530 return 0; 2531 2531 } 2532 2532 2533 + static void mark_jmp_point(struct bpf_verifier_env *env, int idx) 2534 + { 2535 + env->insn_aux_data[idx].jmp_point = true; 2536 + } 2537 + 2538 + static bool is_jmp_point(struct bpf_verifier_env *env, int insn_idx) 2539 + { 2540 + return env->insn_aux_data[insn_idx].jmp_point; 2541 + } 2542 + 2533 2543 /* for any branch, call, exit record the history of jmps in the given state */ 2534 2544 static int push_jmp_history(struct bpf_verifier_env *env, 2535 2545 struct bpf_verifier_state *cur) ··· 2547 2537 u32 cnt = cur->jmp_history_cnt; 2548 2538 struct bpf_idx_pair *p; 2549 2539 size_t alloc_size; 2540 + 2541 + if (!is_jmp_point(env, env->insn_idx)) 2542 + return 0; 2550 2543 2551 2544 cnt++; 2552 2545 alloc_size = kmalloc_size_roundup(size_mul(cnt, sizeof(*p))); ··· 12153 12140 return &env->explored_states[(idx ^ state->callsite) % state_htab_size(env)]; 12154 12141 } 12155 12142 12156 - static void init_explored_state(struct bpf_verifier_env *env, int idx) 12143 + static void mark_prune_point(struct bpf_verifier_env *env, int idx) 12157 12144 { 12158 12145 env->insn_aux_data[idx].prune_point = true; 12146 + } 12147 + 12148 + static bool is_prune_point(struct bpf_verifier_env *env, int insn_idx) 12149 + { 12150 + return env->insn_aux_data[insn_idx].prune_point; 12159 12151 } 12160 12152 12161 12153 enum { ··· 12191 12173 return -EINVAL; 12192 12174 } 12193 12175 12194 - if (e == BRANCH) 12176 + if (e == BRANCH) { 12195 12177 /* mark branch target for state pruning */ 12196 - init_explored_state(env, w); 12178 + mark_prune_point(env, w); 12179 + mark_jmp_point(env, w); 12180 + } 12197 12181 12198 12182 if (insn_state[w] == 0) { 12199 12183 /* tree-edge */ ··· 12233 12213 if (ret) 12234 12214 return ret; 12235 12215 12236 - if (t + 1 < insn_cnt) 12237 - init_explored_state(env, t + 1); 12216 + mark_prune_point(env, t + 1); 12217 + /* when we exit from subprog, we need to record non-linear history */ 12218 + mark_jmp_point(env, t + 1); 12219 + 12238 12220 if (visit_callee) { 12239 - init_explored_state(env, t); 12221 + mark_prune_point(env, t); 12240 12222 ret = push_insn(t, t + insns[t].imm + 1, BRANCH, env, 12241 12223 /* It's ok to allow recursion from CFG point of 12242 12224 * view. __check_func_call() will do the actual ··· 12273 12251 12274 12252 case BPF_CALL: 12275 12253 if (insns[t].imm == BPF_FUNC_timer_set_callback) 12276 - /* Mark this call insn to trigger is_state_visited() check 12277 - * before call itself is processed by __check_func_call(). 12278 - * Otherwise new async state will be pushed for further 12279 - * exploration. 12254 + /* Mark this call insn as a prune point to trigger 12255 + * is_state_visited() check before call itself is 12256 + * processed by __check_func_call(). Otherwise new 12257 + * async state will be pushed for further exploration. 12280 12258 */ 12281 - init_explored_state(env, t); 12259 + mark_prune_point(env, t); 12282 12260 return visit_func_call_insn(t, insn_cnt, insns, env, 12283 12261 insns[t].src_reg == BPF_PSEUDO_CALL); 12284 12262 ··· 12292 12270 if (ret) 12293 12271 return ret; 12294 12272 12295 - /* unconditional jmp is not a good pruning point, 12296 - * but it's marked, since backtracking needs 12297 - * to record jmp history in is_state_visited(). 12298 - */ 12299 - init_explored_state(env, t + insns[t].off + 1); 12300 - /* tell verifier to check for equivalent states 12301 - * after every call and jump 12302 - */ 12303 - if (t + 1 < insn_cnt) 12304 - init_explored_state(env, t + 1); 12273 + mark_prune_point(env, t + insns[t].off + 1); 12274 + mark_jmp_point(env, t + insns[t].off + 1); 12305 12275 12306 12276 return ret; 12307 12277 12308 12278 default: 12309 12279 /* conditional jump with two edges */ 12310 - init_explored_state(env, t); 12280 + mark_prune_point(env, t); 12281 + 12311 12282 ret = push_insn(t, t + 1, FALLTHROUGH, env, true); 12312 12283 if (ret) 12313 12284 return ret; ··· 13334 13319 int i, j, err, states_cnt = 0; 13335 13320 bool add_new_state = env->test_state_freq ? true : false; 13336 13321 13337 - cur->last_insn_idx = env->prev_insn_idx; 13338 - if (!env->insn_aux_data[insn_idx].prune_point) 13339 - /* this 'insn_idx' instruction wasn't marked, so we will not 13340 - * be doing state search here 13341 - */ 13342 - return 0; 13343 - 13344 13322 /* bpf progs typically have pruning point every 4 instructions 13345 13323 * http://vger.kernel.org/bpfconf2019.html#session-1 13346 13324 * Do not add new state for future pruning if the verifier hasn't seen ··· 13468 13460 env->max_states_per_insn = states_cnt; 13469 13461 13470 13462 if (!env->bpf_capable && states_cnt > BPF_COMPLEXITY_LIMIT_STATES) 13471 - return push_jmp_history(env, cur); 13463 + return 0; 13472 13464 13473 13465 if (!add_new_state) 13474 - return push_jmp_history(env, cur); 13466 + return 0; 13475 13467 13476 13468 /* There were no equivalent states, remember the current one. 13477 13469 * Technically the current state is not proven to be safe yet, ··· 13611 13603 return -E2BIG; 13612 13604 } 13613 13605 13614 - err = is_state_visited(env, env->insn_idx); 13615 - if (err < 0) 13616 - return err; 13617 - if (err == 1) { 13618 - /* found equivalent state, can prune the search */ 13619 - if (env->log.level & BPF_LOG_LEVEL) { 13620 - if (do_print_state) 13621 - verbose(env, "\nfrom %d to %d%s: safe\n", 13622 - env->prev_insn_idx, env->insn_idx, 13623 - env->cur_state->speculative ? 13624 - " (speculative execution)" : ""); 13625 - else 13626 - verbose(env, "%d: safe\n", env->insn_idx); 13606 + state->last_insn_idx = env->prev_insn_idx; 13607 + 13608 + if (is_prune_point(env, env->insn_idx)) { 13609 + err = is_state_visited(env, env->insn_idx); 13610 + if (err < 0) 13611 + return err; 13612 + if (err == 1) { 13613 + /* found equivalent state, can prune the search */ 13614 + if (env->log.level & BPF_LOG_LEVEL) { 13615 + if (do_print_state) 13616 + verbose(env, "\nfrom %d to %d%s: safe\n", 13617 + env->prev_insn_idx, env->insn_idx, 13618 + env->cur_state->speculative ? 13619 + " (speculative execution)" : ""); 13620 + else 13621 + verbose(env, "%d: safe\n", env->insn_idx); 13622 + } 13623 + goto process_bpf_exit; 13627 13624 } 13628 - goto process_bpf_exit; 13625 + } 13626 + 13627 + if (is_jmp_point(env, env->insn_idx)) { 13628 + err = push_jmp_history(env, state); 13629 + if (err) 13630 + return err; 13629 13631 } 13630 13632 13631 13633 if (signal_pending(current))