Skip to content

Commit 2afae08

Browse files
anakryikoborkmann
authored andcommitted
bpf: Validate global subprogs lazily
Slightly change BPF verifier logic around eagerness and order of global subprog validation. Instead of going over every global subprog eagerly and validating it before main (entry) BPF program is verified, turn it around. Validate main program first, mark subprogs that were called from main program for later verification, but otherwise assume it is valid. Afterwards, go over marked global subprogs and validate those, potentially marking some more global functions as being called. Continue this process until all (transitively) callable global subprogs are validated. It's a BFS traversal at its heart and will always converge. This is an important change because it allows to feature-gate some subprograms that might not be verifiable on some older kernel, depending on supported set of features. E.g., at some point, global functions were allowed to accept a pointer to memory, which size is identified by user-provided type. Unfortunately, older kernels don't support this feature. With BPF CO-RE approach, the natural way would be to still compile BPF object file once and guard calls to this global subprog with some CO-RE check or using .rodata variables. That's what people do to guard usage of new helpers or kfuncs, and any other new BPF-side feature that might be missing on old kernels. That's currently impossible to do with global subprogs, unfortunately, because they are eagerly and unconditionally validated. This patch set aims to change this, so that in the future when global funcs gain new features, those can be guarded using BPF CO-RE techniques in the same fashion as any other new kernel feature. Two selftests had to be adjusted in sync with these changes. test_global_func12 relied on eager global subprog validation failing before main program failure is detected (unknown return value). Fix by making sure that main program is always valid. verifier_subprog_precision's parent_stack_slot_precise subtest relied on verifier checkpointing heuristic to do a checkpoint at instruction #5, but that's no longer true because we don't have enough jumps validated before reaching insn #5 due to global subprogs being validated later. Other than that, no changes, as one would expect. Signed-off-by: Andrii Nakryiko <[email protected]> Signed-off-by: Daniel Borkmann <[email protected]> Acked-by: Eduard Zingerman <[email protected]> Acked-by: Daniel Borkmann <[email protected]> Link: https://lore.kernel.org/bpf/[email protected]
1 parent 491dd8e commit 2afae08

File tree

4 files changed

+48
-10
lines changed

4 files changed

+48
-10
lines changed

include/linux/bpf.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1347,6 +1347,8 @@ static inline bool bpf_prog_has_trampoline(const struct bpf_prog *prog)
13471347
struct bpf_func_info_aux {
13481348
u16 linkage;
13491349
bool unreliable;
1350+
bool called : 1;
1351+
bool verified : 1;
13501352
};
13511353

13521354
enum bpf_jit_poke_reason {

kernel/bpf/verifier.c

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,11 @@ static const char *subprog_name(const struct bpf_verifier_env *env, int subprog)
434434
return btf_type_name(env->prog->aux->btf, info->type_id);
435435
}
436436

437+
static struct bpf_func_info_aux *subprog_aux(const struct bpf_verifier_env *env, int subprog)
438+
{
439+
return &env->prog->aux->func_info_aux[subprog];
440+
}
441+
437442
static bool reg_may_point_to_spin_lock(const struct bpf_reg_state *reg)
438443
{
439444
return btf_record_has_field(reg_btf_record(reg), BPF_SPIN_LOCK);
@@ -9290,6 +9295,8 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
92909295

92919296
verbose(env, "Func#%d ('%s') is global and assumed valid.\n",
92929297
subprog, sub_name);
9298+
/* mark global subprog for verifying after main prog */
9299+
subprog_aux(env, subprog)->called = true;
92939300
clear_caller_saved_regs(env, caller->regs);
92949301

92959302
/* All global functions return a 64-bit SCALAR_VALUE */
@@ -19873,8 +19880,11 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog, bool is_ex
1987319880
return ret;
1987419881
}
1987519882

19876-
/* Verify all global functions in a BPF program one by one based on their BTF.
19877-
* All global functions must pass verification. Otherwise the whole program is rejected.
19883+
/* Lazily verify all global functions based on their BTF, if they are called
19884+
* from main BPF program or any of subprograms transitively.
19885+
* BPF global subprogs called from dead code are not validated.
19886+
* All callable global functions must pass verification.
19887+
* Otherwise the whole program is rejected.
1987819888
* Consider:
1987919889
* int bar(int);
1988019890
* int foo(int f)
@@ -19893,14 +19903,26 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog, bool is_ex
1989319903
static int do_check_subprogs(struct bpf_verifier_env *env)
1989419904
{
1989519905
struct bpf_prog_aux *aux = env->prog->aux;
19896-
int i, ret;
19906+
struct bpf_func_info_aux *sub_aux;
19907+
int i, ret, new_cnt;
1989719908

1989819909
if (!aux->func_info)
1989919910
return 0;
1990019911

19912+
/* exception callback is presumed to be always called */
19913+
if (env->exception_callback_subprog)
19914+
subprog_aux(env, env->exception_callback_subprog)->called = true;
19915+
19916+
again:
19917+
new_cnt = 0;
1990119918
for (i = 1; i < env->subprog_cnt; i++) {
19902-
if (aux->func_info_aux[i].linkage != BTF_FUNC_GLOBAL)
19919+
if (!subprog_is_global(env, i))
19920+
continue;
19921+
19922+
sub_aux = subprog_aux(env, i);
19923+
if (!sub_aux->called || sub_aux->verified)
1990319924
continue;
19925+
1990419926
env->insn_idx = env->subprog_info[i].start;
1990519927
WARN_ON_ONCE(env->insn_idx == 0);
1990619928
ret = do_check_common(env, i, env->exception_callback_subprog == i);
@@ -19910,7 +19932,21 @@ static int do_check_subprogs(struct bpf_verifier_env *env)
1991019932
verbose(env, "Func#%d ('%s') is safe for any args that match its prototype\n",
1991119933
i, subprog_name(env, i));
1991219934
}
19935+
19936+
/* We verified new global subprog, it might have called some
19937+
* more global subprogs that we haven't verified yet, so we
19938+
* need to do another pass over subprogs to verify those.
19939+
*/
19940+
sub_aux->verified = true;
19941+
new_cnt++;
1991319942
}
19943+
19944+
/* We can't loop forever as we verify at least one global subprog on
19945+
* each pass.
19946+
*/
19947+
if (new_cnt)
19948+
goto again;
19949+
1991419950
return 0;
1991519951
}
1991619952

@@ -20556,8 +20592,8 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3
2055620592
if (ret < 0)
2055720593
goto skip_full_check;
2055820594

20559-
ret = do_check_subprogs(env);
20560-
ret = ret ?: do_check_main(env);
20595+
ret = do_check_main(env);
20596+
ret = ret ?: do_check_subprogs(env);
2056120597

2056220598
if (ret == 0 && bpf_prog_is_offloaded(env->prog->aux))
2056320599
ret = bpf_prog_offload_finalize(env);

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,7 @@ int global_func12(struct __sk_buff *skb)
1919
{
2020
const struct S s = {.x = skb->len };
2121

22-
return foo(&s);
22+
foo(&s);
23+
24+
return 1;
2325
}

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -370,12 +370,10 @@ __naked int parent_stack_slot_precise(void)
370370
SEC("?raw_tp")
371371
__success __log_level(2)
372372
__msg("9: (0f) r1 += r6")
373-
__msg("mark_precise: frame0: last_idx 9 first_idx 6")
373+
__msg("mark_precise: frame0: last_idx 9 first_idx 0")
374374
__msg("mark_precise: frame0: regs=r6 stack= before 8: (bf) r1 = r7")
375375
__msg("mark_precise: frame0: regs=r6 stack= before 7: (27) r6 *= 4")
376376
__msg("mark_precise: frame0: regs=r6 stack= before 6: (79) r6 = *(u64 *)(r10 -8)")
377-
__msg("mark_precise: frame0: parent state regs= stack=-8:")
378-
__msg("mark_precise: frame0: last_idx 5 first_idx 0")
379377
__msg("mark_precise: frame0: regs= stack=-8 before 5: (85) call pc+6")
380378
__msg("mark_precise: frame0: regs= stack=-8 before 4: (b7) r1 = 0")
381379
__msg("mark_precise: frame0: regs= stack=-8 before 3: (7b) *(u64 *)(r10 -8) = r6")

0 commit comments

Comments
 (0)