Skip to content
Closed
11 changes: 9 additions & 2 deletions include/linux/bpf_verifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -1488,8 +1488,15 @@ int bpf_jmp_offset(struct bpf_insn *insn);
struct bpf_iarray *bpf_insn_successors(struct bpf_verifier_env *env, u32 idx);
void bpf_fmt_stack_mask(char *buf, ssize_t buf_sz, u64 stack_mask);
bool bpf_subprog_is_global(const struct bpf_verifier_env *env, int subprog);
bool btf_type_is_scalar_struct(struct bpf_verifier_env *env, const struct btf *btf,
const struct btf_type *t, int rec);

/* Kinds of member a by-value struct or union may be composed of. */
enum btf_member_kind {
BTF_MEMBER_SCALAR = BIT(0), /* an int or an enum, or an array of them */
BTF_MEMBER_ARENA_PTR = BIT(1), /* a pointer carrying the "arena" type tag */
};

bool btf_struct_is_composed_of(struct bpf_verifier_env *env, const struct btf *btf,
const struct btf_type *t, u32 member_kinds);

int bpf_find_subprog(struct bpf_verifier_env *env, int off);
bool bpf_is_throw_kfunc(struct bpf_insn *insn);
Expand Down
1 change: 1 addition & 0 deletions include/linux/btf.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ struct btf_record *btf_parse_fields(const struct btf *btf, const struct btf_type
u32 field_mask, u32 value_size);
int btf_check_and_fixup_fields(const struct btf *btf, struct btf_record *rec);
bool btf_type_is_void(const struct btf_type *t);
bool btf_type_is_arena_ptr(const struct btf *btf, const struct btf_type *t);
s32 btf_find_by_name_kind(const struct btf *btf, const char *name, u8 kind);
s32 bpf_find_btf_id(const char *name, u32 kind, struct btf **btf_p);
struct btf *btf_get_module_btf(const struct module *module);
Expand Down
79 changes: 33 additions & 46 deletions kernel/bpf/btf.c
Original file line number Diff line number Diff line change
Expand Up @@ -3523,6 +3523,22 @@ static int btf_type_tag_walk(const struct btf *btf,
return 0;
}

bool btf_type_is_arena_ptr(const struct btf *btf, const struct btf_type *t)
{
if (!btf_type_is_ptr(t))
return false;

for (t = btf_type_by_id(btf, t->type); btf_type_is_modifier(t);
t = btf_type_by_id(btf, t->type)) {
if (!btf_type_is_type_tag(t) || btf_type_kflag(t))
continue;
if (!strcmp(__btf_name_by_offset(btf, t->name_off), "arena"))
return true;
}

return false;
}

static int btf_find_kptr(const struct btf *btf, const struct btf_type *t,
u32 off, int sz, struct btf_field_info *info, u32 field_mask)
{
Expand Down Expand Up @@ -7927,59 +7943,27 @@ static int btf_scan_decl_tags(struct bpf_verifier_env *env,
return 0;
}

static int btf_scan_type_tags(struct bpf_verifier_env *env,
const struct btf *btf, u32 type_id,
u32 *tags)
static void btf_scan_type_tags(const struct btf *btf, u32 type_id, u32 *tags)
{
static const struct btf_type_tag_match func_type_tags[] = {
{ "arena", ARG_TAG_ARENA },
};
struct btf_type_tag_walk_ctx ctx;
const struct btf_type *t;
int err;

/* Find the first pointer type in the chain. */
t = btf_type_skip_modifiers(btf, type_id, NULL);
const struct btf_type *t = btf_type_skip_modifiers(btf, type_id, NULL);

/*
* We currently reject type tags on non-pointer types,
* which neither LLVM nor GCC support anyway.
*/
if (!t || !btf_type_is_ptr(t))
return 0;

ctx.t = t;
err = btf_type_tag_walk(btf, &ctx, func_type_tags,
ARRAY_SIZE(func_type_tags));
if (err) {
bpf_log(&env->log,
"function signature member has multiple type tags\n");
return err;
}
*tags |= ctx.res;

return 0;
if (btf_type_is_arena_ptr(btf, t))
*tags |= ARG_TAG_ARENA;
}

/* Check whether the type is a valid return type. */
static int btf_validate_return_type(struct bpf_verifier_env *env, struct btf *btf,
const struct btf_type *t, int subprog, bool is_global)
{
u32 tags = 0;
int err;

err = btf_scan_type_tags(env, btf, t->type, &tags);
if (err)
return err;

t = btf_type_skip_modifiers(btf, t->type, NULL);

/*
* We allow all subprogs except for the main one to return any kind of arena pointer.
* General arena variables are not allowed, since it makes no sense to return by value
* a variable that's on the heap in the first place.
*/
if (subprog && (tags & ARG_TAG_ARENA) && btf_type_is_ptr(t))
if (subprog && btf_type_is_arena_ptr(btf, t))
return 0;

/* We always accept void or scalars. */
Expand All @@ -7988,14 +7972,18 @@ static int btf_validate_return_type(struct bpf_verifier_env *env, struct btf *bt

if (btf_type_is_struct(t) && t->size <= 16) {
/*
* A global function's caller models the return as an opaque
* scalar pair, so it may only return scalars by value. A local
* function is verified inline, so a pointer field stays tracked
* and needs no such restriction.
* A global function may return a struct with scalar(s) or arena
* pointer(s) as its members. A local function is verified inline,
* so its caller receives the real register state and any member
* is fine.
*/
bool local_func = subprog && !is_global;
u32 member_kinds = BTF_MEMBER_SCALAR;

if (subprog)
member_kinds |= BTF_MEMBER_ARENA_PTR;

if (local_func || btf_type_is_scalar_struct(env, btf, t, 0))
if (local_func || btf_struct_is_composed_of(env, btf, t, member_kinds))
return 0;
}

Expand Down Expand Up @@ -8091,7 +8079,8 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
if (is_global) {
bpf_log(log,
"Global function %s() has unsupported return type. "
"Only void, scalar, or a scalar-only struct/union up to 16 bytes is supported.\n",
"Only void, a scalar, an arena pointer, or a struct/union of "
"those up to 16 bytes is supported.\n",
tname);
}
return err;
Expand All @@ -8106,9 +8095,7 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
if (err)
return err;

err = btf_scan_type_tags(env, btf, args[i].type, &tags);
if (err)
return err;
btf_scan_type_tags(btf, args[i].type, &tags);

t = btf_type_by_id(btf, args[i].type);
while (btf_type_is_modifier(t))
Expand Down
148 changes: 119 additions & 29 deletions kernel/bpf/verifier.c
Original file line number Diff line number Diff line change
Expand Up @@ -10403,10 +10403,14 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
* return to the caller whatever the callee had in the
* return register(s)
*/
bpf_diag_mod_begin(env, &caller->regs[BPF_REG_0], r0, BPF_DIAG_MOD_WRITE);
for (i = 0; i < nregs; i++)
caller->regs[ret_regs[i]] = callee->regs[ret_regs[i]];
bpf_diag_mod_end(env);
for (i = 0; i < nregs; i++) {
u32 regno = ret_regs[i];

bpf_diag_mod_begin(env, &caller->regs[regno], &callee->regs[regno],
BPF_DIAG_MOD_WRITE);
caller->regs[regno] = callee->regs[regno];
bpf_diag_mod_end(env);
}
}

/* for callbacks like bpf_loop or bpf_for_each_map_elem go back to callsite,
Expand Down Expand Up @@ -11619,10 +11623,32 @@ static bool is_kfunc_arg_implicit(const struct bpf_call_arg_meta *meta, u32 arg_
return argn <= arg_idx;
}

/* Returns true if struct is composed of scalars, 4 levels of nesting allowed */
bool btf_type_is_scalar_struct(struct bpf_verifier_env *env,
const struct btf *btf,
const struct btf_type *t, int rec)
#define BTF_MEMBER_MAX_DEPTH 4
#define BTF_MEMBER_PATH_LEN 64

struct btf_member_path {
const struct btf_member *member[BTF_MEMBER_MAX_DEPTH];
int depth;
bool too_deep;
};

static bool btf_member_kind_allowed(const struct btf *btf, const struct btf_type *t,
u32 member_kinds)
{
if ((member_kinds & BTF_MEMBER_SCALAR) && btf_type_is_scalar(t))
return true;
if ((member_kinds & BTF_MEMBER_ARENA_PTR) && btf_type_is_arena_ptr(btf, t))
return true;
return false;
}

/*
* Returns true if every member of struct @t is of a kind listed in
* @member_kinds, BTF_MEMBER_MAX_DEPTH levels of nesting allowed.
*/
static bool btf_struct_member_walk(struct bpf_verifier_env *env, const struct btf *btf,
const struct btf_type *t, u32 member_kinds, int rec,
struct btf_member_path *path)
{
const struct btf_type *member_type;
const struct btf_member *member;
Expand All @@ -11636,27 +11662,61 @@ bool btf_type_is_scalar_struct(struct bpf_verifier_env *env,

member_type = btf_type_skip_modifiers(btf, member->type, NULL);
if (btf_type_is_struct(member_type)) {
if (rec >= 3) {
if (rec >= BTF_MEMBER_MAX_DEPTH - 1) {
verbose(env, "max struct nesting depth exceeded\n");
if (path)
path->too_deep = true;
return false;
}
if (!btf_type_is_scalar_struct(env, btf, member_type, rec + 1))
return false;
if (!btf_struct_member_walk(env, btf, member_type, member_kinds,
rec + 1, path))
goto bad_path;
continue;
}
if (btf_type_is_array(member_type)) {
array = btf_array(member_type);
if (!array->nelems)
return false;
goto bad_member;
member_type = btf_type_skip_modifiers(btf, array->type, NULL);
if (!btf_type_is_scalar(member_type))
return false;
continue;
}
if (!btf_type_is_scalar(member_type))
return false;
if (!btf_member_kind_allowed(btf, member_type, member_kinds))
goto bad_member;
}
return true;

bad_member:
if (path)
path->depth = rec + 1;
bad_path:
if (path && path->depth)
path->member[rec] = member;
return false;
}

bool btf_struct_is_composed_of(struct bpf_verifier_env *env,
const struct btf *btf,
const struct btf_type *t, u32 member_kinds)
{
return btf_struct_member_walk(env, btf, t, member_kinds, 0, NULL);
}

static bool btf_type_is_scalar_struct(struct bpf_verifier_env *env,
const struct btf *btf,
const struct btf_type *t)
{
return btf_struct_is_composed_of(env, btf, t, BTF_MEMBER_SCALAR);
}

static void btf_member_path_str(const struct btf *btf, const struct btf_member_path *path,
char *buf, size_t buf_sz)
{
size_t len = 0;
int i;

buf[0] = '\0';
for (i = 0; i < path->depth; i++)
len += scnprintf(buf + len, buf_sz - len, "%s%s", i ? "." : "",
btf_name_by_offset(btf, path->member[i]->name_off));
}

enum kfunc_ptr_arg_type {
Expand Down Expand Up @@ -12039,7 +12099,7 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
(is_kfunc_arg_mem_size(meta->btf, &args[arg + 1]) ||
is_kfunc_arg_const_mem_size(meta->btf, &args[arg + 1]))) {
if (!btf_type_is_void(ref_t) && !btf_type_is_scalar(ref_t) &&
!btf_type_is_scalar_struct(env, meta->btf, ref_t, 0)) {
!btf_type_is_scalar_struct(env, meta->btf, ref_t)) {
verbose(env, "%s pointer type %s %s must point to void, scalar, or struct with scalar\n",
reg_arg_name(env, argno), btf_type_str(ref_t), ref_tname);
return -EINVAL;
Expand All @@ -12055,7 +12115,7 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
* scalars. The access size is derived from the pointed-to BTF type.
*/
if (!btf_type_is_scalar(ref_t) &&
!btf_type_is_scalar_struct(env, meta->btf, ref_t, 0)) {
!btf_type_is_scalar_struct(env, meta->btf, ref_t)) {
verbose(env, "%s pointer type %s %s must point to scalar, or struct with scalar\n",
reg_arg_name(env, argno), btf_type_str(ref_t), ref_tname);
return -EINVAL;
Expand Down Expand Up @@ -13111,7 +13171,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
break;
}

if (!btf_type_is_scalar_struct(env, meta->btf, ref_t, 0)) {
if (!btf_type_is_scalar_struct(env, meta->btf, ref_t)) {
enum bpf_reg_type reg2btf_type = lookup_reg2btf_ids(ref_id);
const char *expected_type;

Expand Down Expand Up @@ -13653,7 +13713,7 @@ static int check_special_kfunc(struct bpf_verifier_env *env, struct bpf_call_arg

struct_meta = btf_find_struct_meta(ret_btf, ret_btf_id);
if (is_bpf_percpu_obj_new_kfunc(meta->func_id)) {
if (!btf_type_is_scalar_struct(env, ret_btf, ret_t, 0)) {
if (!btf_type_is_scalar_struct(env, ret_btf, ret_t)) {
verbose(env, "bpf_percpu_obj_new type ID argument must be of a struct of scalars\n");
return -EINVAL;
}
Expand Down Expand Up @@ -14026,17 +14086,47 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
meta.func_id == special_kfunc_list[KF_bpf_res_spin_lock_irqsave]))
__mark_reg_const_zero(env, &regs[BPF_REG_0]);
} else if (btf_type_is_struct(t)) {
struct btf_member_path path = {};
const char *member_note = "";

/*
* The returned struct comes back as raw register bits modeled
* as an unknown scalar, so it must contain only scalars:
* otherwise a pointer field would be laundered into a scalar
* and escape provenance and reference tracking.
* The returned struct may only contain scalars and arena pointers
* as its members. Otherwise, any other pointer would be laundered
* into a scalar and escape provenance and reference tracking.
*/
if (!btf_type_is_scalar_struct(env, desc_btf, t, 0)) {
verbose(env,
"kernel function %s returns %s %s that is not composed of scalars\n",
if (!btf_struct_member_walk(env, desc_btf, t,
BTF_MEMBER_SCALAR | BTF_MEMBER_ARENA_PTR, 0, &path)) {
if (path.too_deep) {
member_note = bpf_diag_fmt(
env, " It nests structs more than %d levels deep.",
BTF_MEMBER_MAX_DEPTH);
} else if (path.depth) {
const struct btf_member *bad = path.member[path.depth - 1];
char bad_name[BTF_MEMBER_PATH_LEN];
const struct btf_type *bad_type;

verbose(env,
"kernel function %s returns %s %s that is not composed of scalars or arena pointers\n",
func_name, btf_type_str(t),
btf_name_by_offset(desc_btf, t->name_off));
btf_member_path_str(desc_btf, &path, bad_name, sizeof(bad_name));
bad_type = btf_type_skip_modifiers(desc_btf, bad->type, NULL);
verbose(env, "member '%s' has type %s\n", bad_name,
btf_type_str(bad_type));
member_note = bpf_diag_fmt(
env,
" Its member '%s' is %s, not a scalar or an arena pointer.",
bad_name, btf_type_str(bad_type));
}
bpf_diag_program_structure(
env, insn_idx, "unsupported kernel function return type",
"Call a kernel function that returns only scalars or arena pointers by value.",
"%s() returns %s %s by value.%s "
"Only kfuncs returning scalar values or arena pointers, or "
"structures composed of scalar values and arena pointers are "
"supported.",
func_name, btf_type_str(t),
btf_name_by_offset(desc_btf, t->name_off));
btf_name_by_offset(desc_btf, t->name_off), member_note);
return -EINVAL;
}
mark_kfunc_ret_regs(env, regs, t->size);
Expand Down
Loading
Loading