bpf: add __arg_trusted global func arg tag

Add support for passing PTR_TO_BTF_ID registers to global subprogs.
Currently only PTR_TRUSTED flavor of PTR_TO_BTF_ID is supported.
Non-NULL semantics is assumed, so caller will be forced to prove
PTR_TO_BTF_ID can't be NULL.

Note, we disallow global subprogs to destroy passed in PTR_TO_BTF_ID
arguments, even the trusted one. We achieve that by not setting
ref_obj_id when validating subprog code. This basically enforces (in
Rust terms) borrowing semantics vs move semantics. Borrowing semantics
seems to be a better fit for isolated global subprog validation
approach.

Implementation-wise, we utilize existing logic for matching
user-provided BTF type to kernel-side BTF type, used by BPF CO-RE logic
and following same matching rules. We enforce a unique match for types.

Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/r/20240130000648.2144827-2-andrii@kernel.org
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Andrii Nakryiko 2024-01-29 16:06:45 -08:00 committed by Alexei Starovoitov
parent 2421905680
commit e2b3c4ff5d
3 changed files with 111 additions and 13 deletions

View file

@ -610,6 +610,7 @@ struct bpf_subprog_arg_info {
enum bpf_arg_type arg_type;
union {
u32 mem_size;
u32 btf_id;
};
};

View file

@ -6985,9 +6985,77 @@ static bool btf_is_dynptr_ptr(const struct btf *btf, const struct btf_type *t)
return false;
}
struct bpf_cand_cache {
const char *name;
u32 name_len;
u16 kind;
u16 cnt;
struct {
const struct btf *btf;
u32 id;
} cands[];
};
static DEFINE_MUTEX(cand_cache_mutex);
static struct bpf_cand_cache *
bpf_core_find_cands(struct bpf_core_ctx *ctx, u32 local_type_id);
static int btf_get_ptr_to_btf_id(struct bpf_verifier_log *log, int arg_idx,
const struct btf *btf, const struct btf_type *t)
{
struct bpf_cand_cache *cc;
struct bpf_core_ctx ctx = {
.btf = btf,
.log = log,
};
u32 kern_type_id, type_id;
int err = 0;
/* skip PTR and modifiers */
type_id = t->type;
t = btf_type_by_id(btf, t->type);
while (btf_type_is_modifier(t)) {
type_id = t->type;
t = btf_type_by_id(btf, t->type);
}
mutex_lock(&cand_cache_mutex);
cc = bpf_core_find_cands(&ctx, type_id);
if (IS_ERR(cc)) {
err = PTR_ERR(cc);
bpf_log(log, "arg#%d reference type('%s %s') candidate matching error: %d\n",
arg_idx, btf_type_str(t), __btf_name_by_offset(btf, t->name_off),
err);
goto cand_cache_unlock;
}
if (cc->cnt != 1) {
bpf_log(log, "arg#%d reference type('%s %s') %s\n",
arg_idx, btf_type_str(t), __btf_name_by_offset(btf, t->name_off),
cc->cnt == 0 ? "has no matches" : "is ambiguous");
err = cc->cnt == 0 ? -ENOENT : -ESRCH;
goto cand_cache_unlock;
}
if (btf_is_module(cc->cands[0].btf)) {
bpf_log(log, "arg#%d reference type('%s %s') points to kernel module type (unsupported)\n",
arg_idx, btf_type_str(t), __btf_name_by_offset(btf, t->name_off));
err = -EOPNOTSUPP;
goto cand_cache_unlock;
}
kern_type_id = cc->cands[0].id;
cand_cache_unlock:
mutex_unlock(&cand_cache_mutex);
if (err)
return err;
return kern_type_id;
}
enum btf_arg_tag {
ARG_TAG_CTX = 0x1,
ARG_TAG_NONNULL = 0x2,
ARG_TAG_TRUSTED = 0x4,
};
/* Process BTF of a function to produce high-level expectation of function
@ -7089,6 +7157,8 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
if (strcmp(tag, "ctx") == 0) {
tags |= ARG_TAG_CTX;
} else if (strcmp(tag, "trusted") == 0) {
tags |= ARG_TAG_TRUSTED;
} else if (strcmp(tag, "nonnull") == 0) {
tags |= ARG_TAG_NONNULL;
} else {
@ -7127,6 +7197,22 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
sub->args[i].arg_type = ARG_PTR_TO_DYNPTR | MEM_RDONLY;
continue;
}
if (tags & ARG_TAG_TRUSTED) {
int kern_type_id;
if (tags & ARG_TAG_NONNULL) {
bpf_log(log, "arg#%d has invalid combination of tags\n", i);
return -EINVAL;
}
kern_type_id = btf_get_ptr_to_btf_id(log, i, btf, t);
if (kern_type_id < 0)
return kern_type_id;
sub->args[i].arg_type = ARG_PTR_TO_BTF_ID | PTR_TRUSTED;
sub->args[i].btf_id = kern_type_id;
continue;
}
if (is_global) { /* generic user data pointer */
u32 mem_size;
@ -8229,17 +8315,6 @@ size_t bpf_core_essential_name_len(const char *name)
return n;
}
struct bpf_cand_cache {
const char *name;
u32 name_len;
u16 kind;
u16 cnt;
struct {
const struct btf *btf;
u32 id;
} cands[];
};
static void bpf_free_cands(struct bpf_cand_cache *cands)
{
if (!cands->cnt)
@ -8260,8 +8335,6 @@ static struct bpf_cand_cache *vmlinux_cand_cache[VMLINUX_CAND_CACHE_SIZE];
#define MODULE_CAND_CACHE_SIZE 31
static struct bpf_cand_cache *module_cand_cache[MODULE_CAND_CACHE_SIZE];
static DEFINE_MUTEX(cand_cache_mutex);
static void __print_cand_cache(struct bpf_verifier_log *log,
struct bpf_cand_cache **cache,
int cache_size)

View file

@ -9336,6 +9336,18 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
ret = process_dynptr_func(env, regno, -1, arg->arg_type, 0);
if (ret)
return ret;
} else if (base_type(arg->arg_type) == ARG_PTR_TO_BTF_ID) {
struct bpf_call_arg_meta meta;
int err;
if (register_is_null(reg) && type_may_be_null(arg->arg_type))
continue;
memset(&meta, 0, sizeof(meta)); /* leave func_id as zero */
err = check_reg_type(env, regno, arg->arg_type, &arg->btf_id, &meta);
err = err ?: check_func_arg_reg_off(env, reg, regno, arg->arg_type);
if (err)
return err;
} else {
bpf_log(log, "verifier bug: unrecognized arg#%d type %d\n",
i, arg->arg_type);
@ -20137,6 +20149,18 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
mark_reg_known_zero(env, regs, i);
reg->mem_size = arg->mem_size;
reg->id = ++env->id_gen;
} else if (base_type(arg->arg_type) == ARG_PTR_TO_BTF_ID) {
reg->type = PTR_TO_BTF_ID;
if (arg->arg_type & PTR_MAYBE_NULL)
reg->type |= PTR_MAYBE_NULL;
if (arg->arg_type & PTR_UNTRUSTED)
reg->type |= PTR_UNTRUSTED;
if (arg->arg_type & PTR_TRUSTED)
reg->type |= PTR_TRUSTED;
mark_reg_known_zero(env, regs, i);
reg->btf = bpf_get_btf_vmlinux(); /* can't fail at this point */
reg->btf_id = arg->btf_id;
reg->id = ++env->id_gen;
} else {
WARN_ONCE(1, "BUG: unhandled arg#%d type %d\n",
i - BPF_REG_1, arg->arg_type);