Skip to content

Commit 10e14e9

Browse files
anakryikoAlexei Starovoitov
authored andcommitted
bpf: fix control-flow graph checking in privileged mode
When BPF program is verified in privileged mode, BPF verifier allows bounded loops. This means that from CFG point of view there are definitely some back-edges. Original commit adjusted check_cfg() logic to not detect back-edges in control flow graph if they are resulting from conditional jumps, which the idea that subsequent full BPF verification process will determine whether such loops are bounded or not, and either accept or reject the BPF program. At least that's my reading of the intent. Unfortunately, the implementation of this idea doesn't work correctly in all possible situations. Conditional jump might not result in immediate back-edge, but just a few unconditional instructions later we can arrive at back-edge. In such situations check_cfg() would reject BPF program even in privileged mode, despite it might be bounded loop. Next patch adds one simple program demonstrating such scenario. To keep things simple, instead of trying to detect back edges in privileged mode, just assume every back edge is valid and let subsequent BPF verification prove or reject bounded loops. Note a few test changes. For unknown reason, we have a few tests that are specified to detect a back-edge in a privileged mode, but looking at their code it seems like the right outcome is passing check_cfg() and letting subsequent verification to make a decision about bounded or not bounded looping. Bounded recursion case is also interesting. The example should pass, as recursion is limited to just a few levels and so we never reach maximum number of nested frames and never exhaust maximum stack depth. But the way that max stack depth logic works today it falsely detects this as exceeding max nested frame count. This patch series doesn't attempt to fix this orthogonal problem, so we just adjust expected verifier failure. Suggested-by: Alexei Starovoitov <ast@kernel.org> Fixes: 2589726 ("bpf: introduce bounded loops") Reported-by: Hao Sun <sunhao.th@gmail.com> Signed-off-by: Andrii Nakryiko <andrii@kernel.org> Link: https://lore.kernel.org/r/20231110061412.2995786-1-andrii@kernel.org Signed-off-by: Alexei Starovoitov <ast@kernel.org>
1 parent 8c74b27 commit 10e14e9

3 files changed

Lines changed: 17 additions & 21 deletions

File tree

kernel/bpf/verifier.c

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15403,8 +15403,7 @@ enum {
1540315403
* w - next instruction
1540415404
* e - edge
1540515405
*/
15406-
static int push_insn(int t, int w, int e, struct bpf_verifier_env *env,
15407-
bool loop_ok)
15406+
static int push_insn(int t, int w, int e, struct bpf_verifier_env *env)
1540815407
{
1540915408
int *insn_stack = env->cfg.insn_stack;
1541015409
int *insn_state = env->cfg.insn_state;
@@ -15436,7 +15435,7 @@ static int push_insn(int t, int w, int e, struct bpf_verifier_env *env,
1543615435
insn_stack[env->cfg.cur_stack++] = w;
1543715436
return KEEP_EXPLORING;
1543815437
} else if ((insn_state[w] & 0xF0) == DISCOVERED) {
15439-
if (loop_ok && env->bpf_capable)
15438+
if (env->bpf_capable)
1544015439
return DONE_EXPLORING;
1544115440
verbose_linfo(env, t, "%d: ", t);
1544215441
verbose_linfo(env, w, "%d: ", w);
@@ -15459,7 +15458,7 @@ static int visit_func_call_insn(int t, struct bpf_insn *insns,
1545915458
int ret, insn_sz;
1546015459

1546115460
insn_sz = bpf_is_ldimm64(&insns[t]) ? 2 : 1;
15462-
ret = push_insn(t, t + insn_sz, FALLTHROUGH, env, false);
15461+
ret = push_insn(t, t + insn_sz, FALLTHROUGH, env);
1546315462
if (ret)
1546415463
return ret;
1546515464

@@ -15469,12 +15468,7 @@ static int visit_func_call_insn(int t, struct bpf_insn *insns,
1546915468

1547015469
if (visit_callee) {
1547115470
mark_prune_point(env, t);
15472-
ret = push_insn(t, t + insns[t].imm + 1, BRANCH, env,
15473-
/* It's ok to allow recursion from CFG point of
15474-
* view. __check_func_call() will do the actual
15475-
* check.
15476-
*/
15477-
bpf_pseudo_func(insns + t));
15471+
ret = push_insn(t, t + insns[t].imm + 1, BRANCH, env);
1547815472
}
1547915473
return ret;
1548015474
}
@@ -15496,7 +15490,7 @@ static int visit_insn(int t, struct bpf_verifier_env *env)
1549615490
if (BPF_CLASS(insn->code) != BPF_JMP &&
1549715491
BPF_CLASS(insn->code) != BPF_JMP32) {
1549815492
insn_sz = bpf_is_ldimm64(insn) ? 2 : 1;
15499-
return push_insn(t, t + insn_sz, FALLTHROUGH, env, false);
15493+
return push_insn(t, t + insn_sz, FALLTHROUGH, env);
1550015494
}
1550115495

1550215496
switch (BPF_OP(insn->code)) {
@@ -15543,8 +15537,7 @@ static int visit_insn(int t, struct bpf_verifier_env *env)
1554315537
off = insn->imm;
1554415538

1554515539
/* unconditional jump with single edge */
15546-
ret = push_insn(t, t + off + 1, FALLTHROUGH, env,
15547-
true);
15540+
ret = push_insn(t, t + off + 1, FALLTHROUGH, env);
1554815541
if (ret)
1554915542
return ret;
1555015543

@@ -15557,11 +15550,11 @@ static int visit_insn(int t, struct bpf_verifier_env *env)
1555715550
/* conditional jump with two edges */
1555815551
mark_prune_point(env, t);
1555915552

15560-
ret = push_insn(t, t + 1, FALLTHROUGH, env, true);
15553+
ret = push_insn(t, t + 1, FALLTHROUGH, env);
1556115554
if (ret)
1556215555
return ret;
1556315556

15564-
return push_insn(t, t + insn->off + 1, BRANCH, env, true);
15557+
return push_insn(t, t + insn->off + 1, BRANCH, env);
1556515558
}
1556615559
}
1556715560

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,10 @@ l0_%=: r0 += 1; \
7575
" ::: __clobber_all);
7676
}
7777

78-
SEC("tracepoint")
78+
SEC("socket")
7979
__description("bounded loop, start in the middle")
80-
__failure __msg("back-edge")
80+
__success
81+
__failure_unpriv __msg_unpriv("back-edge")
8182
__naked void loop_start_in_the_middle(void)
8283
{
8384
asm volatile (" \
@@ -136,7 +137,9 @@ l0_%=: exit; \
136137

137138
SEC("tracepoint")
138139
__description("bounded recursion")
139-
__failure __msg("back-edge")
140+
__failure
141+
/* verifier limitation in detecting max stack depth */
142+
__msg("the call stack of 8 frames is too deep !")
140143
__naked void bounded_recursion(void)
141144
{
142145
asm volatile (" \

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@
442442
BPF_EXIT_INSN(),
443443
},
444444
.prog_type = BPF_PROG_TYPE_TRACEPOINT,
445-
.errstr = "back-edge from insn 0 to 0",
445+
.errstr = "the call stack of 9 frames is too deep",
446446
.result = REJECT,
447447
},
448448
{
@@ -799,7 +799,7 @@
799799
BPF_EXIT_INSN(),
800800
},
801801
.prog_type = BPF_PROG_TYPE_TRACEPOINT,
802-
.errstr = "back-edge",
802+
.errstr = "the call stack of 9 frames is too deep",
803803
.result = REJECT,
804804
},
805805
{
@@ -811,7 +811,7 @@
811811
BPF_EXIT_INSN(),
812812
},
813813
.prog_type = BPF_PROG_TYPE_TRACEPOINT,
814-
.errstr = "back-edge",
814+
.errstr = "the call stack of 9 frames is too deep",
815815
.result = REJECT,
816816
},
817817
{

0 commit comments

Comments
 (0)