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

bpf: simple DFA-based live registers analysis

Compute may-live registers before each instruction in the program.
The register is live before the instruction I if it is read by I or
some instruction S following I during program execution and is not
overwritten between I and S.

This information would be used in the next patch as a hint in
func_states_equal().

Use a simple algorithm described in [1] to compute this information:
- define the following:
- I.use : a set of all registers read by instruction I;
- I.def : a set of all registers written by instruction I;
- I.in : a set of all registers that may be alive before I execution;
- I.out : a set of all registers that may be alive after I execution;
- I.successors : a set of instructions S that might immediately
follow I for some program execution;
- associate separate empty sets 'I.in' and 'I.out' with each instruction;
- visit each instruction in a postorder and update corresponding
'I.in' and 'I.out' sets as follows:

I.out = U [S.in for S in I.successors]
I.in = (I.out / I.def) U I.use

(where U stands for set union, / stands for set difference)
- repeat the computation while I.{in,out} changes for any instruction.

On implementation side keep things as simple, as possible:
- check_cfg() already marks instructions EXPLORED in post-order,
modify it to save the index of each EXPLORED instruction in a vector;
- represent I.{in,out,use,def} as bitmasks;
- don't split the program into basic blocks and don't maintain the
work queue, instead:
- do fixed-point computation by visiting each instruction;
- maintain a simple 'changed' flag if I.{in,out} for any instruction
change;
Measurements show that even such simplistic implementation does not
add measurable verification time overhead (for selftests, at-least).

Note on check_cfg() ex_insn_beg/ex_done change:
To avoid out of bounds access to env->cfg.insn_postorder array,
it should be guaranteed that instruction transitions to EXPLORED state
only once. Previously this was not the fact for incorrect programs
with direct calls to exception callbacks.

The 'align' selftest needs adjustment to skip computed insn/live
registers printout. Otherwise it matches lines from the live registers
printout.

[1] https://en.wikipedia.org/wiki/Live-variable_analysis

Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://lore.kernel.org/r/20250304195024.2478889-4-eddyz87@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>

authored by

Eduard Zingerman and committed by
Alexei Starovoitov
14c8552d 22f83974

+330 -7
+6
include/linux/bpf_verifier.h
··· 591 591 * accepts callback function as a parameter. 592 592 */ 593 593 bool calls_callback; 594 + /* registers alive before this instruction. */ 595 + u16 live_regs_before; 594 596 }; 595 597 596 598 #define MAX_USED_MAPS 64 /* max number of maps accessed by one eBPF program */ ··· 750 748 struct { 751 749 int *insn_state; 752 750 int *insn_stack; 751 + /* vector of instruction indexes sorted in post-order */ 752 + int *insn_postorder; 753 753 int cur_stack; 754 + /* current position in the insn_postorder vector */ 755 + int cur_postorder; 754 756 } cfg; 755 757 struct backtrack_state bt; 756 758 struct bpf_insn_hist_entry *insn_hist;
+314 -6
kernel/bpf/verifier.c
··· 17402 17402 static int check_cfg(struct bpf_verifier_env *env) 17403 17403 { 17404 17404 int insn_cnt = env->prog->len; 17405 - int *insn_stack, *insn_state; 17405 + int *insn_stack, *insn_state, *insn_postorder; 17406 17406 int ex_insn_beg, i, ret = 0; 17407 - bool ex_done = false; 17408 17407 17409 17408 insn_state = env->cfg.insn_state = kvcalloc(insn_cnt, sizeof(int), GFP_KERNEL); 17410 17409 if (!insn_state) ··· 17414 17415 kvfree(insn_state); 17415 17416 return -ENOMEM; 17416 17417 } 17418 + 17419 + insn_postorder = env->cfg.insn_postorder = kvcalloc(insn_cnt, sizeof(int), GFP_KERNEL); 17420 + if (!insn_postorder) { 17421 + kvfree(insn_state); 17422 + kvfree(insn_stack); 17423 + return -ENOMEM; 17424 + } 17425 + 17426 + ex_insn_beg = env->exception_callback_subprog 17427 + ? env->subprog_info[env->exception_callback_subprog].start 17428 + : 0; 17417 17429 17418 17430 insn_state[0] = DISCOVERED; /* mark 1st insn as discovered */ 17419 17431 insn_stack[0] = 0; /* 0 is the first instruction */ ··· 17439 17429 case DONE_EXPLORING: 17440 17430 insn_state[t] = EXPLORED; 17441 17431 env->cfg.cur_stack--; 17432 + insn_postorder[env->cfg.cur_postorder++] = t; 17442 17433 break; 17443 17434 case KEEP_EXPLORING: 17444 17435 break; ··· 17458 17447 goto err_free; 17459 17448 } 17460 17449 17461 - if (env->exception_callback_subprog && !ex_done) { 17462 - ex_insn_beg = env->subprog_info[env->exception_callback_subprog].start; 17463 - 17450 + if (ex_insn_beg && insn_state[ex_insn_beg] != EXPLORED) { 17464 17451 insn_state[ex_insn_beg] = DISCOVERED; 17465 17452 insn_stack[0] = ex_insn_beg; 17466 17453 env->cfg.cur_stack = 1; 17467 - ex_done = true; 17468 17454 goto walk_cfg; 17469 17455 } 17470 17456 ··· 23387 23379 return 0; 23388 23380 } 23389 23381 23382 + static bool can_fallthrough(struct bpf_insn *insn) 23383 + { 23384 + u8 class = BPF_CLASS(insn->code); 23385 + u8 opcode = BPF_OP(insn->code); 23386 + 23387 + if (class != BPF_JMP && class != BPF_JMP32) 23388 + return true; 23389 + 23390 + if (opcode == BPF_EXIT || opcode == BPF_JA) 23391 + return false; 23392 + 23393 + return true; 23394 + } 23395 + 23396 + static bool can_jump(struct bpf_insn *insn) 23397 + { 23398 + u8 class = BPF_CLASS(insn->code); 23399 + u8 opcode = BPF_OP(insn->code); 23400 + 23401 + if (class != BPF_JMP && class != BPF_JMP32) 23402 + return false; 23403 + 23404 + switch (opcode) { 23405 + case BPF_JA: 23406 + case BPF_JEQ: 23407 + case BPF_JNE: 23408 + case BPF_JLT: 23409 + case BPF_JLE: 23410 + case BPF_JGT: 23411 + case BPF_JGE: 23412 + case BPF_JSGT: 23413 + case BPF_JSGE: 23414 + case BPF_JSLT: 23415 + case BPF_JSLE: 23416 + case BPF_JCOND: 23417 + return true; 23418 + } 23419 + 23420 + return false; 23421 + } 23422 + 23423 + static int insn_successors(struct bpf_prog *prog, u32 idx, u32 succ[2]) 23424 + { 23425 + struct bpf_insn *insn = &prog->insnsi[idx]; 23426 + int i = 0, insn_sz; 23427 + u32 dst; 23428 + 23429 + insn_sz = bpf_is_ldimm64(insn) ? 2 : 1; 23430 + if (can_fallthrough(insn) && idx + 1 < prog->len) 23431 + succ[i++] = idx + insn_sz; 23432 + 23433 + if (can_jump(insn)) { 23434 + dst = idx + jmp_offset(insn) + 1; 23435 + if (i == 0 || succ[0] != dst) 23436 + succ[i++] = dst; 23437 + } 23438 + 23439 + return i; 23440 + } 23441 + 23442 + /* Each field is a register bitmask */ 23443 + struct insn_live_regs { 23444 + u16 use; /* registers read by instruction */ 23445 + u16 def; /* registers written by instruction */ 23446 + u16 in; /* registers that may be alive before instruction */ 23447 + u16 out; /* registers that may be alive after instruction */ 23448 + }; 23449 + 23450 + /* Bitmask with 1s for all caller saved registers */ 23451 + #define ALL_CALLER_SAVED_REGS ((1u << CALLER_SAVED_REGS) - 1) 23452 + 23453 + /* Compute info->{use,def} fields for the instruction */ 23454 + static void compute_insn_live_regs(struct bpf_verifier_env *env, 23455 + struct bpf_insn *insn, 23456 + struct insn_live_regs *info) 23457 + { 23458 + struct call_summary cs; 23459 + u8 class = BPF_CLASS(insn->code); 23460 + u8 code = BPF_OP(insn->code); 23461 + u8 mode = BPF_MODE(insn->code); 23462 + u16 src = BIT(insn->src_reg); 23463 + u16 dst = BIT(insn->dst_reg); 23464 + u16 r0 = BIT(0); 23465 + u16 def = 0; 23466 + u16 use = 0xffff; 23467 + 23468 + switch (class) { 23469 + case BPF_LD: 23470 + switch (mode) { 23471 + case BPF_IMM: 23472 + if (BPF_SIZE(insn->code) == BPF_DW) { 23473 + def = dst; 23474 + use = 0; 23475 + } 23476 + break; 23477 + case BPF_LD | BPF_ABS: 23478 + case BPF_LD | BPF_IND: 23479 + /* stick with defaults */ 23480 + break; 23481 + } 23482 + break; 23483 + case BPF_LDX: 23484 + switch (mode) { 23485 + case BPF_MEM: 23486 + case BPF_MEMSX: 23487 + def = dst; 23488 + use = src; 23489 + break; 23490 + } 23491 + break; 23492 + case BPF_ST: 23493 + switch (mode) { 23494 + case BPF_MEM: 23495 + def = 0; 23496 + use = dst; 23497 + break; 23498 + } 23499 + break; 23500 + case BPF_STX: 23501 + switch (mode) { 23502 + case BPF_MEM: 23503 + def = 0; 23504 + use = dst | src; 23505 + break; 23506 + case BPF_ATOMIC: 23507 + switch (insn->imm) { 23508 + case BPF_CMPXCHG: 23509 + use = r0 | dst | src; 23510 + def = r0; 23511 + break; 23512 + case BPF_LOAD_ACQ: 23513 + def = dst; 23514 + use = src; 23515 + break; 23516 + case BPF_STORE_REL: 23517 + def = 0; 23518 + use = dst | src; 23519 + break; 23520 + default: 23521 + use = dst | src; 23522 + if (insn->imm & BPF_FETCH) 23523 + def = src; 23524 + else 23525 + def = 0; 23526 + } 23527 + break; 23528 + } 23529 + break; 23530 + case BPF_ALU: 23531 + case BPF_ALU64: 23532 + switch (code) { 23533 + case BPF_END: 23534 + use = dst; 23535 + def = dst; 23536 + break; 23537 + case BPF_MOV: 23538 + def = dst; 23539 + if (BPF_SRC(insn->code) == BPF_K) 23540 + use = 0; 23541 + else 23542 + use = src; 23543 + break; 23544 + default: 23545 + def = dst; 23546 + if (BPF_SRC(insn->code) == BPF_K) 23547 + use = dst; 23548 + else 23549 + use = dst | src; 23550 + } 23551 + break; 23552 + case BPF_JMP: 23553 + case BPF_JMP32: 23554 + switch (code) { 23555 + case BPF_JA: 23556 + def = 0; 23557 + use = 0; 23558 + break; 23559 + case BPF_EXIT: 23560 + def = 0; 23561 + use = r0; 23562 + break; 23563 + case BPF_CALL: 23564 + def = ALL_CALLER_SAVED_REGS; 23565 + use = def & ~BIT(BPF_REG_0); 23566 + if (get_call_summary(env, insn, &cs)) 23567 + use = GENMASK(cs.num_params, 1); 23568 + break; 23569 + default: 23570 + def = 0; 23571 + if (BPF_SRC(insn->code) == BPF_K) 23572 + use = dst; 23573 + else 23574 + use = dst | src; 23575 + } 23576 + break; 23577 + } 23578 + 23579 + info->def = def; 23580 + info->use = use; 23581 + } 23582 + 23583 + /* Compute may-live registers after each instruction in the program. 23584 + * The register is live after the instruction I if it is read by some 23585 + * instruction S following I during program execution and is not 23586 + * overwritten between I and S. 23587 + * 23588 + * Store result in env->insn_aux_data[i].live_regs. 23589 + */ 23590 + static int compute_live_registers(struct bpf_verifier_env *env) 23591 + { 23592 + struct bpf_insn_aux_data *insn_aux = env->insn_aux_data; 23593 + struct bpf_insn *insns = env->prog->insnsi; 23594 + struct insn_live_regs *state; 23595 + int insn_cnt = env->prog->len; 23596 + int err = 0, i, j; 23597 + bool changed; 23598 + 23599 + /* Use the following algorithm: 23600 + * - define the following: 23601 + * - I.use : a set of all registers read by instruction I; 23602 + * - I.def : a set of all registers written by instruction I; 23603 + * - I.in : a set of all registers that may be alive before I execution; 23604 + * - I.out : a set of all registers that may be alive after I execution; 23605 + * - insn_successors(I): a set of instructions S that might immediately 23606 + * follow I for some program execution; 23607 + * - associate separate empty sets 'I.in' and 'I.out' with each instruction; 23608 + * - visit each instruction in a postorder and update 23609 + * state[i].in, state[i].out as follows: 23610 + * 23611 + * state[i].out = U [state[s].in for S in insn_successors(i)] 23612 + * state[i].in = (state[i].out / state[i].def) U state[i].use 23613 + * 23614 + * (where U stands for set union, / stands for set difference) 23615 + * - repeat the computation while {in,out} fields changes for 23616 + * any instruction. 23617 + */ 23618 + state = kvcalloc(insn_cnt, sizeof(*state), GFP_KERNEL); 23619 + if (!state) { 23620 + err = -ENOMEM; 23621 + goto out; 23622 + } 23623 + 23624 + for (i = 0; i < insn_cnt; ++i) 23625 + compute_insn_live_regs(env, &insns[i], &state[i]); 23626 + 23627 + changed = true; 23628 + while (changed) { 23629 + changed = false; 23630 + for (i = 0; i < env->cfg.cur_postorder; ++i) { 23631 + int insn_idx = env->cfg.insn_postorder[i]; 23632 + struct insn_live_regs *live = &state[insn_idx]; 23633 + int succ_num; 23634 + u32 succ[2]; 23635 + u16 new_out = 0; 23636 + u16 new_in = 0; 23637 + 23638 + succ_num = insn_successors(env->prog, insn_idx, succ); 23639 + for (int s = 0; s < succ_num; ++s) 23640 + new_out |= state[succ[s]].in; 23641 + new_in = (new_out & ~live->def) | live->use; 23642 + if (new_out != live->out || new_in != live->in) { 23643 + live->in = new_in; 23644 + live->out = new_out; 23645 + changed = true; 23646 + } 23647 + } 23648 + } 23649 + 23650 + for (i = 0; i < insn_cnt; ++i) 23651 + insn_aux[i].live_regs_before = state[i].in; 23652 + 23653 + if (env->log.level & BPF_LOG_LEVEL2) { 23654 + verbose(env, "Live regs before insn:\n"); 23655 + for (i = 0; i < insn_cnt; ++i) { 23656 + verbose(env, "%3d: ", i); 23657 + for (j = BPF_REG_0; j < BPF_REG_10; ++j) 23658 + if (insn_aux[i].live_regs_before & BIT(j)) 23659 + verbose(env, "%d", j); 23660 + else 23661 + verbose(env, "."); 23662 + verbose(env, " "); 23663 + verbose_insn(env, &insns[i]); 23664 + if (bpf_is_ldimm64(&insns[i])) 23665 + i++; 23666 + } 23667 + } 23668 + 23669 + out: 23670 + kvfree(state); 23671 + kvfree(env->cfg.insn_postorder); 23672 + env->cfg.insn_postorder = NULL; 23673 + env->cfg.cur_postorder = 0; 23674 + return err; 23675 + } 23676 + 23390 23677 int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u32 uattr_size) 23391 23678 { 23392 23679 u64 start_time = ktime_get_ns(); ··· 23801 23498 23802 23499 ret = check_attach_btf_id(env); 23803 23500 if (ret) 23501 + goto skip_full_check; 23502 + 23503 + ret = compute_live_registers(env); 23504 + if (ret < 0) 23804 23505 goto skip_full_check; 23805 23506 23806 23507 ret = mark_fastcall_patterns(env); ··· 23945 23638 vfree(env->insn_aux_data); 23946 23639 kvfree(env->insn_hist); 23947 23640 err_free_env: 23641 + kvfree(env->cfg.insn_postorder); 23948 23642 kvfree(env); 23949 23643 return ret; 23950 23644 }
+10 -1
tools/testing/selftests/bpf/prog_tests/align.c
··· 610 610 .log_size = sizeof(bpf_vlog), 611 611 .log_level = 2, 612 612 ); 613 + const char *main_pass_start = "0: R1=ctx() R10=fp0"; 613 614 const char *line_ptr; 614 615 int cur_line = -1; 615 616 int prog_len, i; 617 + char *start; 616 618 int fd_prog; 617 619 int ret; 618 620 ··· 634 632 ret = 0; 635 633 /* We make a local copy so that we can strtok() it */ 636 634 strncpy(bpf_vlog_copy, bpf_vlog, sizeof(bpf_vlog_copy)); 637 - line_ptr = strtok(bpf_vlog_copy, "\n"); 635 + start = strstr(bpf_vlog_copy, main_pass_start); 636 + if (!start) { 637 + ret = 1; 638 + printf("Can't find initial line '%s'\n", main_pass_start); 639 + goto out; 640 + } 641 + line_ptr = strtok(start, "\n"); 638 642 for (i = 0; i < MAX_MATCHES; i++) { 639 643 struct bpf_reg_match m = test->matches[i]; 640 644 const char *p; ··· 690 682 break; 691 683 } 692 684 } 685 + out: 693 686 if (fd_prog >= 0) 694 687 close(fd_prog); 695 688 }