Skip to content

Commit 94e1c70

Browse files
anakryikoAlexei Starovoitov
authored andcommitted
bpf: support 'arg:xxx' btf_decl_tag-based hints for global subprog args
Add support for annotating global BPF subprog arguments to provide more information about expected semantics of the argument. Currently, verifier relies purely on argument's BTF type information, and supports three general use cases: scalar, pointer-to-context, and pointer-to-fixed-size-memory. Scalar and pointer-to-fixed-mem work well in practice and are quite natural to use. But pointer-to-context is a bit problematic, as typical BPF users don't realize that they need to use a special type name to signal to verifier that argument is not just some pointer, but actually a PTR_TO_CTX. Further, even if users do know which type to use, it is limiting in situations where the same BPF program logic is used across few different program types. Common case is kprobes, tracepoints, and perf_event programs having a helper to send some data over BPF perf buffer. bpf_perf_event_output() requires `ctx` argument, and so it's quite cumbersome to share such global subprog across few BPF programs of different types, necessitating extra static subprog that is context type-agnostic. Long story short, there is a need to go beyond types and allow users to add hints to global subprog arguments to define expectations. This patch adds such support for two initial special tags: - pointer to context; - non-null qualifier for generic pointer arguments. All of the above came up in practice already and seem generally useful additions. Non-null qualifier is an often requested feature, which currently has to be worked around by having unnecessary NULL checks inside subprogs even if we know that arguments are never NULL. Pointer to context was discussed earlier. As for implementation, we utilize btf_decl_tag attribute and set up an "arg:xxx" convention to specify argument hint. As such: - btf_decl_tag("arg:ctx") is a PTR_TO_CTX hint; - btf_decl_tag("arg:nonnull") marks pointer argument as not allowed to be NULL, making NULL check inside global subprog unnecessary. Acked-by: Eduard Zingerman <[email protected]> Signed-off-by: Andrii Nakryiko <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Alexei Starovoitov <[email protected]>
1 parent f18c3d8 commit 94e1c70

File tree

2 files changed

+42
-7
lines changed

2 files changed

+42
-7
lines changed

kernel/bpf/btf.c

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6782,7 +6782,7 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
67826782
enum bpf_prog_type prog_type = prog->type;
67836783
struct btf *btf = prog->aux->btf;
67846784
const struct btf_param *args;
6785-
const struct btf_type *t, *ref_t;
6785+
const struct btf_type *t, *ref_t, *fn_t;
67866786
u32 i, nargs, btf_id;
67876787
const char *tname;
67886788

@@ -6802,16 +6802,16 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
68026802
return -EFAULT;
68036803
}
68046804

6805-
t = btf_type_by_id(btf, btf_id);
6806-
if (!t || !btf_type_is_func(t)) {
6805+
fn_t = btf_type_by_id(btf, btf_id);
6806+
if (!fn_t || !btf_type_is_func(fn_t)) {
68076807
/* These checks were already done by the verifier while loading
68086808
* struct bpf_func_info
68096809
*/
68106810
bpf_log(log, "BTF of func#%d doesn't point to KIND_FUNC\n",
68116811
subprog);
68126812
return -EFAULT;
68136813
}
6814-
tname = btf_name_by_offset(btf, t->name_off);
6814+
tname = btf_name_by_offset(btf, fn_t->name_off);
68156815

68166816
if (prog->aux->func_info_aux[subprog].unreliable) {
68176817
bpf_log(log, "Verifier bug in function %s()\n", tname);
@@ -6820,7 +6820,7 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
68206820
if (prog_type == BPF_PROG_TYPE_EXT)
68216821
prog_type = prog->aux->dst_prog->type;
68226822

6823-
t = btf_type_by_id(btf, t->type);
6823+
t = btf_type_by_id(btf, fn_t->type);
68246824
if (!t || !btf_type_is_func_proto(t)) {
68256825
bpf_log(log, "Invalid type of function %s()\n", tname);
68266826
return -EFAULT;
@@ -6846,7 +6846,35 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
68466846
* Only PTR_TO_CTX and SCALAR are supported atm.
68476847
*/
68486848
for (i = 0; i < nargs; i++) {
6849+
bool is_nonnull = false;
6850+
const char *tag;
6851+
68496852
t = btf_type_by_id(btf, args[i].type);
6853+
6854+
tag = btf_find_decl_tag_value(btf, fn_t, i, "arg:");
6855+
if (IS_ERR(tag) && PTR_ERR(tag) == -ENOENT) {
6856+
tag = NULL;
6857+
} else if (IS_ERR(tag)) {
6858+
bpf_log(log, "arg#%d type's tag fetching failure: %ld\n", i, PTR_ERR(tag));
6859+
return PTR_ERR(tag);
6860+
}
6861+
/* 'arg:<tag>' decl_tag takes precedence over derivation of
6862+
* register type from BTF type itself
6863+
*/
6864+
if (tag) {
6865+
/* disallow arg tags in static subprogs */
6866+
if (!is_global) {
6867+
bpf_log(log, "arg#%d type tag is not supported in static functions\n", i);
6868+
return -EOPNOTSUPP;
6869+
}
6870+
if (strcmp(tag, "ctx") == 0) {
6871+
sub->args[i].arg_type = ARG_PTR_TO_CTX;
6872+
continue;
6873+
}
6874+
if (strcmp(tag, "nonnull") == 0)
6875+
is_nonnull = true;
6876+
}
6877+
68506878
while (btf_type_is_modifier(t))
68516879
t = btf_type_by_id(btf, t->type);
68526880
if (btf_type_is_int(t) || btf_is_any_enum(t)) {
@@ -6870,10 +6898,14 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
68706898
return -EINVAL;
68716899
}
68726900

6873-
sub->args[i].arg_type = ARG_PTR_TO_MEM_OR_NULL;
6901+
sub->args[i].arg_type = is_nonnull ? ARG_PTR_TO_MEM : ARG_PTR_TO_MEM_OR_NULL;
68746902
sub->args[i].mem_size = mem_size;
68756903
continue;
68766904
}
6905+
if (is_nonnull) {
6906+
bpf_log(log, "arg#%d marked as non-null, but is not a pointer type\n", i);
6907+
return -EINVAL;
6908+
}
68776909
bpf_log(log, "Arg#%d type %s in %s() is not supported yet.\n",
68786910
i, btf_type_str(t), tname);
68796911
return -EINVAL;

kernel/bpf/verifier.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9290,9 +9290,12 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
92909290
ret = check_func_arg_reg_off(env, reg, regno, ARG_DONTCARE);
92919291
if (ret < 0)
92929292
return ret;
9293-
92949293
if (check_mem_reg(env, reg, regno, arg->mem_size))
92959294
return -EINVAL;
9295+
if (!(arg->arg_type & PTR_MAYBE_NULL) && (reg->type & PTR_MAYBE_NULL)) {
9296+
bpf_log(log, "arg#%d is expected to be non-NULL\n", i);
9297+
return -EINVAL;
9298+
}
92969299
} else {
92979300
bpf_log(log, "verifier bug: unrecognized arg#%d type %d\n",
92989301
i, arg->arg_type);

0 commit comments

Comments
 (0)