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

Merge branch 'bpf-control-flow-graph-and-precision-backtrack-fixes'

Andrii Nakryiko says:

====================
BPF control flow graph and precision backtrack fixes

A small fix to BPF verifier's CFG logic around handling and reporting ldimm64
instructions. Patch #1 was previously submitted separately ([0]), and so this
patch set supersedes that patch.

Second patch is fixing obscure corner case in mark_chain_precise() logic. See
patch for details. Patch #3 adds a dedicated test, however fragile it might.

[0] https://patchwork.kernel.org/project/netdevbpf/patch/20231101205626.119243-1-andrii@kernel.org/
====================

Link: https://lore.kernel.org/r/20231110002638.4168352-1-andrii@kernel.org
Signed-off-by: Alexei Starovoitov <ast@kernel.org>

+89 -15
+6 -2
include/linux/bpf.h
··· 909 909 aux->ctx_field_size = size; 910 910 } 911 911 912 + static bool bpf_is_ldimm64(const struct bpf_insn *insn) 913 + { 914 + return insn->code == (BPF_LD | BPF_IMM | BPF_DW); 915 + } 916 + 912 917 static inline bool bpf_pseudo_func(const struct bpf_insn *insn) 913 918 { 914 - return insn->code == (BPF_LD | BPF_IMM | BPF_DW) && 915 - insn->src_reg == BPF_PSEUDO_FUNC; 919 + return bpf_is_ldimm64(insn) && insn->src_reg == BPF_PSEUDO_FUNC; 916 920 } 917 921 918 922 struct bpf_prog_ops {
+39 -9
kernel/bpf/verifier.c
··· 3516 3516 3517 3517 /* Backtrack one insn at a time. If idx is not at the top of recorded 3518 3518 * history then previous instruction came from straight line execution. 3519 + * Return -ENOENT if we exhausted all instructions within given state. 3520 + * 3521 + * It's legal to have a bit of a looping with the same starting and ending 3522 + * insn index within the same state, e.g.: 3->4->5->3, so just because current 3523 + * instruction index is the same as state's first_idx doesn't mean we are 3524 + * done. If there is still some jump history left, we should keep going. We 3525 + * need to take into account that we might have a jump history between given 3526 + * state's parent and itself, due to checkpointing. In this case, we'll have 3527 + * history entry recording a jump from last instruction of parent state and 3528 + * first instruction of given state. 3519 3529 */ 3520 3530 static int get_prev_insn_idx(struct bpf_verifier_state *st, int i, 3521 3531 u32 *history) 3522 3532 { 3523 3533 u32 cnt = *history; 3534 + 3535 + if (i == st->first_insn_idx) { 3536 + if (cnt == 0) 3537 + return -ENOENT; 3538 + if (cnt == 1 && st->jmp_history[0].idx == i) 3539 + return -ENOENT; 3540 + } 3524 3541 3525 3542 if (cnt && st->jmp_history[cnt - 1].idx == i) { 3526 3543 i = st->jmp_history[cnt - 1].prev_idx; ··· 4418 4401 * Nothing to be tracked further in the parent state. 4419 4402 */ 4420 4403 return 0; 4421 - if (i == first_idx) 4422 - break; 4423 4404 subseq_idx = i; 4424 4405 i = get_prev_insn_idx(st, i, &history); 4406 + if (i == -ENOENT) 4407 + break; 4425 4408 if (i >= env->prog->len) { 4426 4409 /* This can happen if backtracking reached insn 0 4427 4410 * and there are still reg_mask or stack_mask ··· 15456 15439 struct bpf_verifier_env *env, 15457 15440 bool visit_callee) 15458 15441 { 15459 - int ret; 15442 + int ret, insn_sz; 15460 15443 15461 - ret = push_insn(t, t + 1, FALLTHROUGH, env, false); 15444 + insn_sz = bpf_is_ldimm64(&insns[t]) ? 2 : 1; 15445 + ret = push_insn(t, t + insn_sz, FALLTHROUGH, env, false); 15462 15446 if (ret) 15463 15447 return ret; 15464 15448 15465 - mark_prune_point(env, t + 1); 15449 + mark_prune_point(env, t + insn_sz); 15466 15450 /* when we exit from subprog, we need to record non-linear history */ 15467 - mark_jmp_point(env, t + 1); 15451 + mark_jmp_point(env, t + insn_sz); 15468 15452 15469 15453 if (visit_callee) { 15470 15454 mark_prune_point(env, t); ··· 15487 15469 static int visit_insn(int t, struct bpf_verifier_env *env) 15488 15470 { 15489 15471 struct bpf_insn *insns = env->prog->insnsi, *insn = &insns[t]; 15490 - int ret, off; 15472 + int ret, off, insn_sz; 15491 15473 15492 15474 if (bpf_pseudo_func(insn)) 15493 15475 return visit_func_call_insn(t, insns, env, true); 15494 15476 15495 15477 /* All non-branch instructions have a single fall-through edge. */ 15496 15478 if (BPF_CLASS(insn->code) != BPF_JMP && 15497 - BPF_CLASS(insn->code) != BPF_JMP32) 15498 - return push_insn(t, t + 1, FALLTHROUGH, env, false); 15479 + BPF_CLASS(insn->code) != BPF_JMP32) { 15480 + insn_sz = bpf_is_ldimm64(insn) ? 2 : 1; 15481 + return push_insn(t, t + insn_sz, FALLTHROUGH, env, false); 15482 + } 15499 15483 15500 15484 switch (BPF_OP(insn->code)) { 15501 15485 case BPF_EXIT: ··· 15627 15607 } 15628 15608 15629 15609 for (i = 0; i < insn_cnt; i++) { 15610 + struct bpf_insn *insn = &env->prog->insnsi[i]; 15611 + 15630 15612 if (insn_state[i] != EXPLORED) { 15631 15613 verbose(env, "unreachable insn %d\n", i); 15632 15614 ret = -EINVAL; 15633 15615 goto err_free; 15616 + } 15617 + if (bpf_is_ldimm64(insn)) { 15618 + if (insn_state[i + 1] != 0) { 15619 + verbose(env, "jump into the middle of ldimm64 insn %d\n", i); 15620 + ret = -EINVAL; 15621 + goto err_free; 15622 + } 15623 + i++; /* skip second half of ldimm64 */ 15634 15624 } 15635 15625 } 15636 15626 ret = 0; /* cfg looks good */
+40
tools/testing/selftests/bpf/progs/verifier_precision.c
··· 91 91 } 92 92 93 93 #endif /* v4 instruction */ 94 + 95 + SEC("?raw_tp") 96 + __success __log_level(2) 97 + /* 98 + * Without the bug fix there will be no history between "last_idx 3 first_idx 3" 99 + * and "parent state regs=" lines. "R0_w=6" parts are here to help anchor 100 + * expected log messages to the one specific mark_chain_precision operation. 101 + * 102 + * This is quite fragile: if verifier checkpointing heuristic changes, this 103 + * might need adjusting. 104 + */ 105 + __msg("2: (07) r0 += 1 ; R0_w=6") 106 + __msg("3: (35) if r0 >= 0xa goto pc+1") 107 + __msg("mark_precise: frame0: last_idx 3 first_idx 3 subseq_idx -1") 108 + __msg("mark_precise: frame0: regs=r0 stack= before 2: (07) r0 += 1") 109 + __msg("mark_precise: frame0: regs=r0 stack= before 1: (07) r0 += 1") 110 + __msg("mark_precise: frame0: regs=r0 stack= before 4: (05) goto pc-4") 111 + __msg("mark_precise: frame0: regs=r0 stack= before 3: (35) if r0 >= 0xa goto pc+1") 112 + __msg("mark_precise: frame0: parent state regs= stack=: R0_rw=P4") 113 + __msg("3: R0_w=6") 114 + __naked int state_loop_first_last_equal(void) 115 + { 116 + asm volatile ( 117 + "r0 = 0;" 118 + "l0_%=:" 119 + "r0 += 1;" 120 + "r0 += 1;" 121 + /* every few iterations we'll have a checkpoint here with 122 + * first_idx == last_idx, potentially confusing precision 123 + * backtracking logic 124 + */ 125 + "if r0 >= 10 goto l1_%=;" /* checkpoint + mark_precise */ 126 + "goto l0_%=;" 127 + "l1_%=:" 128 + "exit;" 129 + ::: __clobber_common 130 + ); 131 + } 132 + 133 + char _license[] SEC("license") = "GPL";
+4 -4
tools/testing/selftests/bpf/verifier/ld_imm64.c
··· 9 9 BPF_MOV64_IMM(BPF_REG_0, 2), 10 10 BPF_EXIT_INSN(), 11 11 }, 12 - .errstr = "invalid BPF_LD_IMM insn", 13 - .errstr_unpriv = "R1 pointer comparison", 12 + .errstr = "jump into the middle of ldimm64 insn 1", 13 + .errstr_unpriv = "jump into the middle of ldimm64 insn 1", 14 14 .result = REJECT, 15 15 }, 16 16 { ··· 23 23 BPF_LD_IMM64(BPF_REG_0, 1), 24 24 BPF_EXIT_INSN(), 25 25 }, 26 - .errstr = "invalid BPF_LD_IMM insn", 27 - .errstr_unpriv = "R1 pointer comparison", 26 + .errstr = "jump into the middle of ldimm64 insn 1", 27 + .errstr_unpriv = "jump into the middle of ldimm64 insn 1", 28 28 .result = REJECT, 29 29 }, 30 30 {