Skip to content

Commit 8c74b27

Browse files
author
Alexei Starovoitov
committed
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>
2 parents fe69a1b + 62ccdb1 commit 8c74b27

File tree

4 files changed

+89
-15
lines changed

4 files changed

+89
-15
lines changed

include/linux/bpf.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -909,10 +909,14 @@ bpf_ctx_record_field_size(struct bpf_insn_access_aux *aux, u32 size)
909909
aux->ctx_field_size = size;
910910
}
911911

912+
static bool bpf_is_ldimm64(const struct bpf_insn *insn)
913+
{
914+
return insn->code == (BPF_LD | BPF_IMM | BPF_DW);
915+
}
916+
912917
static inline bool bpf_pseudo_func(const struct bpf_insn *insn)
913918
{
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;
916920
}
917921

918922
struct bpf_prog_ops {

kernel/bpf/verifier.c

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3516,12 +3516,29 @@ static int push_jmp_history(struct bpf_verifier_env *env,
35163516

35173517
/* Backtrack one insn at a time. If idx is not at the top of recorded
35183518
* 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.
35193529
*/
35203530
static int get_prev_insn_idx(struct bpf_verifier_state *st, int i,
35213531
u32 *history)
35223532
{
35233533
u32 cnt = *history;
35243534

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+
}
3541+
35253542
if (cnt && st->jmp_history[cnt - 1].idx == i) {
35263543
i = st->jmp_history[cnt - 1].prev_idx;
35273544
(*history)--;
@@ -4401,10 +4418,10 @@ static int __mark_chain_precision(struct bpf_verifier_env *env, int regno)
44014418
* Nothing to be tracked further in the parent state.
44024419
*/
44034420
return 0;
4404-
if (i == first_idx)
4405-
break;
44064421
subseq_idx = i;
44074422
i = get_prev_insn_idx(st, i, &history);
4423+
if (i == -ENOENT)
4424+
break;
44084425
if (i >= env->prog->len) {
44094426
/* This can happen if backtracking reached insn 0
44104427
* and there are still reg_mask or stack_mask
@@ -15439,15 +15456,16 @@ static int visit_func_call_insn(int t, struct bpf_insn *insns,
1543915456
struct bpf_verifier_env *env,
1544015457
bool visit_callee)
1544115458
{
15442-
int ret;
15459+
int ret, insn_sz;
1544315460

15444-
ret = push_insn(t, t + 1, FALLTHROUGH, env, false);
15461+
insn_sz = bpf_is_ldimm64(&insns[t]) ? 2 : 1;
15462+
ret = push_insn(t, t + insn_sz, FALLTHROUGH, env, false);
1544515463
if (ret)
1544615464
return ret;
1544715465

15448-
mark_prune_point(env, t + 1);
15466+
mark_prune_point(env, t + insn_sz);
1544915467
/* when we exit from subprog, we need to record non-linear history */
15450-
mark_jmp_point(env, t + 1);
15468+
mark_jmp_point(env, t + insn_sz);
1545115469

1545215470
if (visit_callee) {
1545315471
mark_prune_point(env, t);
@@ -15469,15 +15487,17 @@ static int visit_func_call_insn(int t, struct bpf_insn *insns,
1546915487
static int visit_insn(int t, struct bpf_verifier_env *env)
1547015488
{
1547115489
struct bpf_insn *insns = env->prog->insnsi, *insn = &insns[t];
15472-
int ret, off;
15490+
int ret, off, insn_sz;
1547315491

1547415492
if (bpf_pseudo_func(insn))
1547515493
return visit_func_call_insn(t, insns, env, true);
1547615494

1547715495
/* All non-branch instructions have a single fall-through edge. */
1547815496
if (BPF_CLASS(insn->code) != BPF_JMP &&
15479-
BPF_CLASS(insn->code) != BPF_JMP32)
15480-
return push_insn(t, t + 1, FALLTHROUGH, env, false);
15497+
BPF_CLASS(insn->code) != BPF_JMP32) {
15498+
insn_sz = bpf_is_ldimm64(insn) ? 2 : 1;
15499+
return push_insn(t, t + insn_sz, FALLTHROUGH, env, false);
15500+
}
1548115501

1548215502
switch (BPF_OP(insn->code)) {
1548315503
case BPF_EXIT:
@@ -15607,11 +15627,21 @@ static int check_cfg(struct bpf_verifier_env *env)
1560715627
}
1560815628

1560915629
for (i = 0; i < insn_cnt; i++) {
15630+
struct bpf_insn *insn = &env->prog->insnsi[i];
15631+
1561015632
if (insn_state[i] != EXPLORED) {
1561115633
verbose(env, "unreachable insn %d\n", i);
1561215634
ret = -EINVAL;
1561315635
goto err_free;
1561415636
}
15637+
if (bpf_is_ldimm64(insn)) {
15638+
if (insn_state[i + 1] != 0) {
15639+
verbose(env, "jump into the middle of ldimm64 insn %d\n", i);
15640+
ret = -EINVAL;
15641+
goto err_free;
15642+
}
15643+
i++; /* skip second half of ldimm64 */
15644+
}
1561515645
}
1561615646
ret = 0; /* cfg looks good */
1561715647

tools/testing/selftests/bpf/progs/verifier_precision.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,43 @@ __naked int bpf_end_bswap(void)
9191
}
9292

9393
#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";

tools/testing/selftests/bpf/verifier/ld_imm64.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
BPF_MOV64_IMM(BPF_REG_0, 2),
1010
BPF_EXIT_INSN(),
1111
},
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",
1414
.result = REJECT,
1515
},
1616
{
@@ -23,8 +23,8 @@
2323
BPF_LD_IMM64(BPF_REG_0, 1),
2424
BPF_EXIT_INSN(),
2525
},
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",
2828
.result = REJECT,
2929
},
3030
{

0 commit comments

Comments
 (0)