mirror of
https://github.com/torvalds/linux.git
synced 2024-11-26 22:21:42 +00:00
Merge branch 'trusted-ptr_to_btf_id-arg-support-in-global-subprogs'
Andrii Nakryiko says: ==================== Trusted PTR_TO_BTF_ID arg support in global subprogs This patch set follows recent changes that added btf_decl_tag-based argument annotation support for global subprogs. This time we add ability to pass PTR_TO_BTF_ID (BTF-aware kernel pointers) arguments into global subprograms. We support explicitly trusted arguments only, for now. Patch #1 adds logic for arg:trusted tag support on the verifier side. Default semantic of such arguments is non-NULL, enforced on caller side. But patch #2 adds arg:nullable tag that can be combined with arg:trusted to make callee explicitly do the NULL check, which helps implement "optional" PTR_TO_BTF_ID arguments. Patch #3 adds libbpf-side __arg_trusted and __arg_nullable macros. Patch #4 adds a bunch of tests validating __arg_trusted in combination with __arg_nullable. v2->v3: - went back to arg:nullable and __arg_nullable naming; - rebased on latest bpf-next after prepartory patches landed; v1->v2: - added fix up to type enforcement changes, landed earlier; - dropped bpf_core_cast() changes, will post them separately, as they now are not used in added tests; - dropped arg:untrusted support (Alexei); - renamed arg:nullable to arg:maybe_null (Alexei); - and also added task_struct___local flavor tests (Alexei). ==================== Link: https://lore.kernel.org/r/20240130000648.2144827-1-andrii@kernel.org Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
commit
4d8ebe1304
@ -610,6 +610,7 @@ struct bpf_subprog_arg_info {
|
||||
enum bpf_arg_type arg_type;
|
||||
union {
|
||||
u32 mem_size;
|
||||
u32 btf_id;
|
||||
};
|
||||
};
|
||||
|
||||
|
109
kernel/bpf/btf.c
109
kernel/bpf/btf.c
@ -6985,9 +6985,78 @@ 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,
|
||||
ARG_TAG_NULLABLE = 0x8,
|
||||
};
|
||||
|
||||
/* Process BTF of a function to produce high-level expectation of function
|
||||
@ -7089,8 +7158,12 @@ 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 if (strcmp(tag, "nullable") == 0) {
|
||||
tags |= ARG_TAG_NULLABLE;
|
||||
} else {
|
||||
bpf_log(log, "arg#%d has unsupported set of tags\n", i);
|
||||
return -EOPNOTSUPP;
|
||||
@ -7127,9 +7200,32 @@ 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;
|
||||
if (tags & ARG_TAG_NULLABLE)
|
||||
sub->args[i].arg_type |= PTR_MAYBE_NULL;
|
||||
sub->args[i].btf_id = kern_type_id;
|
||||
continue;
|
||||
}
|
||||
if (is_global) { /* generic user data pointer */
|
||||
u32 mem_size;
|
||||
|
||||
if (tags & ARG_TAG_NULLABLE) {
|
||||
bpf_log(log, "arg#%d has invalid combination of tags\n", i);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
t = btf_type_skip_modifiers(btf, t->type, NULL);
|
||||
ref_t = btf_resolve_size(btf, t, &mem_size);
|
||||
if (IS_ERR(ref_t)) {
|
||||
@ -8229,17 +8325,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 +8345,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)
|
||||
|
@ -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);
|
||||
|
@ -190,6 +190,8 @@ enum libbpf_tristate {
|
||||
|
||||
#define __arg_ctx __attribute__((btf_decl_tag("arg:ctx")))
|
||||
#define __arg_nonnull __attribute((btf_decl_tag("arg:nonnull")))
|
||||
#define __arg_nullable __attribute((btf_decl_tag("arg:nullable")))
|
||||
#define __arg_trusted __attribute((btf_decl_tag("arg:trusted")))
|
||||
|
||||
#ifndef ___bpf_concat
|
||||
#define ___bpf_concat(a, b) a ## b
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "verifier_div0.skel.h"
|
||||
#include "verifier_div_overflow.skel.h"
|
||||
#include "verifier_global_subprogs.skel.h"
|
||||
#include "verifier_global_ptr_args.skel.h"
|
||||
#include "verifier_gotol.skel.h"
|
||||
#include "verifier_helper_access_var_len.skel.h"
|
||||
#include "verifier_helper_packet_access.skel.h"
|
||||
@ -140,6 +141,7 @@ void test_verifier_direct_stack_access_wraparound(void) { RUN(verifier_direct_st
|
||||
void test_verifier_div0(void) { RUN(verifier_div0); }
|
||||
void test_verifier_div_overflow(void) { RUN(verifier_div_overflow); }
|
||||
void test_verifier_global_subprogs(void) { RUN(verifier_global_subprogs); }
|
||||
void test_verifier_global_ptr_args(void) { RUN(verifier_global_ptr_args); }
|
||||
void test_verifier_gotol(void) { RUN(verifier_gotol); }
|
||||
void test_verifier_helper_access_var_len(void) { RUN(verifier_helper_access_var_len); }
|
||||
void test_verifier_helper_packet_access(void) { RUN(verifier_helper_packet_access); }
|
||||
|
156
tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c
Normal file
156
tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c
Normal file
@ -0,0 +1,156 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
|
||||
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include "bpf_misc.h"
|
||||
#include "xdp_metadata.h"
|
||||
#include "bpf_kfuncs.h"
|
||||
|
||||
extern struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym __weak;
|
||||
extern void bpf_task_release(struct task_struct *p) __ksym __weak;
|
||||
|
||||
__weak int subprog_trusted_task_nullable(struct task_struct *task __arg_trusted __arg_nullable)
|
||||
{
|
||||
if (!task)
|
||||
return 0;
|
||||
return task->pid + task->tgid;
|
||||
}
|
||||
|
||||
SEC("?kprobe")
|
||||
__success __log_level(2)
|
||||
__msg("Validating subprog_trusted_task_nullable() func#1...")
|
||||
__msg(": R1=trusted_ptr_or_null_task_struct(")
|
||||
int trusted_task_arg_nullable(void *ctx)
|
||||
{
|
||||
struct task_struct *t = bpf_get_current_task_btf();
|
||||
|
||||
return subprog_trusted_task_nullable(t) + subprog_trusted_task_nullable(NULL);
|
||||
}
|
||||
|
||||
__weak int subprog_trusted_task_nonnull(struct task_struct *task __arg_trusted)
|
||||
{
|
||||
return task->pid + task->tgid;
|
||||
}
|
||||
|
||||
SEC("?kprobe")
|
||||
__failure __log_level(2)
|
||||
__msg("R1 type=scalar expected=ptr_, trusted_ptr_, rcu_ptr_")
|
||||
__msg("Caller passes invalid args into func#1 ('subprog_trusted_task_nonnull')")
|
||||
int trusted_task_arg_nonnull_fail1(void *ctx)
|
||||
{
|
||||
return subprog_trusted_task_nonnull(NULL);
|
||||
}
|
||||
|
||||
SEC("?tp_btf/task_newtask")
|
||||
__failure __log_level(2)
|
||||
__msg("R1 type=ptr_or_null_ expected=ptr_, trusted_ptr_, rcu_ptr_")
|
||||
__msg("Caller passes invalid args into func#1 ('subprog_trusted_task_nonnull')")
|
||||
int trusted_task_arg_nonnull_fail2(void *ctx)
|
||||
{
|
||||
struct task_struct *t = bpf_get_current_task_btf();
|
||||
struct task_struct *nullable;
|
||||
int res;
|
||||
|
||||
nullable = bpf_task_acquire(t);
|
||||
|
||||
/* should fail, PTR_TO_BTF_ID_OR_NULL */
|
||||
res = subprog_trusted_task_nonnull(nullable);
|
||||
|
||||
if (nullable)
|
||||
bpf_task_release(nullable);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
SEC("?kprobe")
|
||||
__success __log_level(2)
|
||||
__msg("Validating subprog_trusted_task_nonnull() func#1...")
|
||||
__msg(": R1=trusted_ptr_task_struct(")
|
||||
int trusted_task_arg_nonnull(void *ctx)
|
||||
{
|
||||
struct task_struct *t = bpf_get_current_task_btf();
|
||||
|
||||
return subprog_trusted_task_nonnull(t);
|
||||
}
|
||||
|
||||
struct task_struct___local {} __attribute__((preserve_access_index));
|
||||
|
||||
__weak int subprog_nullable_task_flavor(
|
||||
struct task_struct___local *task __arg_trusted __arg_nullable)
|
||||
{
|
||||
char buf[16];
|
||||
|
||||
if (!task)
|
||||
return 0;
|
||||
|
||||
return bpf_copy_from_user_task(&buf, sizeof(buf), NULL, (void *)task, 0);
|
||||
}
|
||||
|
||||
SEC("?uprobe.s")
|
||||
__success __log_level(2)
|
||||
__msg("Validating subprog_nullable_task_flavor() func#1...")
|
||||
__msg(": R1=trusted_ptr_or_null_task_struct(")
|
||||
int flavor_ptr_nullable(void *ctx)
|
||||
{
|
||||
struct task_struct___local *t = (void *)bpf_get_current_task_btf();
|
||||
|
||||
return subprog_nullable_task_flavor(t);
|
||||
}
|
||||
|
||||
__weak int subprog_nonnull_task_flavor(struct task_struct___local *task __arg_trusted)
|
||||
{
|
||||
char buf[16];
|
||||
|
||||
return bpf_copy_from_user_task(&buf, sizeof(buf), NULL, (void *)task, 0);
|
||||
}
|
||||
|
||||
SEC("?uprobe.s")
|
||||
__success __log_level(2)
|
||||
__msg("Validating subprog_nonnull_task_flavor() func#1...")
|
||||
__msg(": R1=trusted_ptr_task_struct(")
|
||||
int flavor_ptr_nonnull(void *ctx)
|
||||
{
|
||||
struct task_struct *t = bpf_get_current_task_btf();
|
||||
|
||||
return subprog_nonnull_task_flavor((void *)t);
|
||||
}
|
||||
|
||||
__weak int subprog_trusted_destroy(struct task_struct *task __arg_trusted)
|
||||
{
|
||||
bpf_task_release(task); /* should be rejected */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("?tp_btf/task_newtask")
|
||||
__failure __log_level(2)
|
||||
__msg("release kernel function bpf_task_release expects refcounted PTR_TO_BTF_ID")
|
||||
int BPF_PROG(trusted_destroy_fail, struct task_struct *task, u64 clone_flags)
|
||||
{
|
||||
return subprog_trusted_destroy(task);
|
||||
}
|
||||
|
||||
__weak int subprog_trusted_acq_rel(struct task_struct *task __arg_trusted)
|
||||
{
|
||||
struct task_struct *owned;
|
||||
|
||||
owned = bpf_task_acquire(task);
|
||||
if (!owned)
|
||||
return 0;
|
||||
|
||||
bpf_task_release(owned); /* this one is OK, we acquired it locally */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("?tp_btf/task_newtask")
|
||||
__success __log_level(2)
|
||||
int BPF_PROG(trusted_acq_rel, struct task_struct *task, u64 clone_flags)
|
||||
{
|
||||
return subprog_trusted_acq_rel(task);
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
Loading…
Reference in New Issue
Block a user