From dbeb601b2729551dd3395dd7f78c2b7577dce316 Mon Sep 17 00:00:00 2001 From: Yonghong Song Date: Tue, 25 Aug 2026 13:54:17 -0700 Subject: [PATCH 01/10] bpf: Record each half of a paired return value in verifier diagnostics A subprogram returning more than 8 bytes comes back in the R0:R2 register pair, and prepare_func_exit() copies both registers into the caller. The diagnostic modification scope around that copy names only R0, so the write into R2 is never recorded. Fix it by opening a diagnostic modification scope for each return register. This way, both return registers are recorded. Fixes: 0630ad00d96d ("bpf: Add verifier support for 16-byte returns in R0: R2") Acked-by: Eduard Zingerman Signed-off-by: Yonghong Song --- kernel/bpf/verifier.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index e036ae20bf6b9..9aa29c3670087 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -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, From bdc6da2214304ce6a832283af3c4cfd00ad8d32b Mon Sep 17 00:00:00 2001 From: Yonghong Song Date: Tue, 25 Aug 2026 13:54:22 -0700 Subject: [PATCH 02/10] bpf: Drop the recursion depth argument of btf_type_is_scalar_struct() btf_type_is_scalar_struct() recurses into nested struct members and carries the nesting depth in a @rec argument, so every caller has to spell out the 0 that starts the walk. Move the recursion into a static helper that keeps @rec and leave btf_type_is_scalar_struct() as a thin wrapper over it, so callers only name the type they are asking about. No functional change. Signed-off-by: Yonghong Song --- include/linux/bpf_verifier.h | 2 +- kernel/bpf/btf.c | 2 +- kernel/bpf/verifier.c | 24 +++++++++++++++--------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index 004b06785521c..3eb61edc8c5e1 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -1489,7 +1489,7 @@ 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); + const struct btf_type *t); int bpf_find_subprog(struct bpf_verifier_env *env, int off); bool bpf_is_throw_kfunc(struct bpf_insn *insn); diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index 91b8ce77f699a..47d43eb983a50 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -7995,7 +7995,7 @@ static int btf_validate_return_type(struct bpf_verifier_env *env, struct btf *bt */ bool local_func = subprog && !is_global; - if (local_func || btf_type_is_scalar_struct(env, btf, t, 0)) + if (local_func || btf_type_is_scalar_struct(env, btf, t)) return 0; } diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 9aa29c3670087..9799b50b97cde 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -11624,9 +11624,8 @@ static bool is_kfunc_arg_implicit(const struct bpf_call_arg_meta *meta, u32 arg_ } /* 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) +static bool btf_scalar_struct_walk(struct bpf_verifier_env *env, const struct btf *btf, + const struct btf_type *t, int rec) { const struct btf_type *member_type; const struct btf_member *member; @@ -11644,7 +11643,7 @@ bool btf_type_is_scalar_struct(struct bpf_verifier_env *env, verbose(env, "max struct nesting depth exceeded\n"); return false; } - if (!btf_type_is_scalar_struct(env, btf, member_type, rec + 1)) + if (!btf_scalar_struct_walk(env, btf, member_type, rec + 1)) return false; continue; } @@ -11663,6 +11662,13 @@ bool btf_type_is_scalar_struct(struct bpf_verifier_env *env, return true; } +bool btf_type_is_scalar_struct(struct bpf_verifier_env *env, + const struct btf *btf, + const struct btf_type *t) +{ + return btf_scalar_struct_walk(env, btf, t, 0); +} + enum kfunc_ptr_arg_type { KF_ARG_CONST_MEM_SIZE, KF_ARG_MEM_SIZE, @@ -12043,7 +12049,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; @@ -12059,7 +12065,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; @@ -13115,7 +13121,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; @@ -13657,7 +13663,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; } @@ -14036,7 +14042,7 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn, * otherwise a pointer field would be laundered into a scalar * and escape provenance and reference tracking. */ - if (!btf_type_is_scalar_struct(env, desc_btf, t, 0)) { + if (!btf_type_is_scalar_struct(env, desc_btf, t)) { verbose(env, "kernel function %s returns %s %s that is not composed of scalars\n", func_name, btf_type_str(t), From 43a9dfe842a054105f6f7ca9fb7b7e70705c1999 Mon Sep 17 00:00:00 2001 From: Yonghong Song Date: Tue, 25 Aug 2026 13:54:27 -0700 Subject: [PATCH 03/10] bpf: Add btf_type_is_arena_ptr() Simplify btf_scan_type_tags() and added a new helper btf_type_is_arena_ptr(). No functional change. Signed-off-by: Yonghong Song --- include/linux/btf.h | 1 + kernel/bpf/btf.c | 62 ++++++++++++++++----------------------------- 2 files changed, 23 insertions(+), 40 deletions(-) diff --git a/include/linux/btf.h b/include/linux/btf.h index 89d5a5c4f1171..ddd0f4f32d24b 100644 --- a/include/linux/btf.h +++ b/include/linux/btf.h @@ -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); diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index 47d43eb983a50..280530d25886c 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -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) { @@ -7927,51 +7943,19 @@ 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); /* @@ -7979,7 +7963,7 @@ static int btf_validate_return_type(struct bpf_verifier_env *env, struct btf *bt * 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. */ @@ -8106,9 +8090,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)) From 47297972e2b23c7ed86a6bc97b157836714025c2 Mon Sep 17 00:00:00 2001 From: Yonghong Song Date: Tue, 25 Aug 2026 13:54:32 -0700 Subject: [PATCH 04/10] bpf: Let the by-value struct walk take the kinds of member it accepts Use btf_struct_is_composed_of() instead of btf_type_is_scalar_struct() in btf.c so in the future, non scalar member (e.g. arena pointer) can be supported as well. There is no functional change. Signed-off-by: Yonghong Song --- include/linux/bpf_verifier.h | 11 ++++++++-- kernel/bpf/btf.c | 2 +- kernel/bpf/verifier.c | 39 ++++++++++++++++++++++++++---------- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index 3eb61edc8c5e1..be3ec883c08f6 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -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); + +/* 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); diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index 280530d25886c..b1f4ef614d4c4 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -7979,7 +7979,7 @@ static int btf_validate_return_type(struct bpf_verifier_env *env, struct btf *bt */ bool local_func = subprog && !is_global; - if (local_func || btf_type_is_scalar_struct(env, btf, t)) + if (local_func || btf_struct_is_composed_of(env, btf, t, BTF_MEMBER_SCALAR)) return 0; } diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 9799b50b97cde..5ea95e75e726a 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -11623,9 +11623,22 @@ 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 */ -static bool btf_scalar_struct_walk(struct bpf_verifier_env *env, const struct btf *btf, - const struct btf_type *t, int rec) +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, 4 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) { const struct btf_type *member_type; const struct btf_member *member; @@ -11643,7 +11656,7 @@ static bool btf_scalar_struct_walk(struct bpf_verifier_env *env, const struct bt verbose(env, "max struct nesting depth exceeded\n"); return false; } - if (!btf_scalar_struct_walk(env, btf, member_type, rec + 1)) + if (!btf_struct_member_walk(env, btf, member_type, member_kinds, rec + 1)) return false; continue; } @@ -11652,21 +11665,25 @@ static bool btf_scalar_struct_walk(struct bpf_verifier_env *env, const struct bt if (!array->nelems) return false; 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)) + if (!btf_member_kind_allowed(btf, member_type, member_kinds)) return false; } return true; } -bool btf_type_is_scalar_struct(struct bpf_verifier_env *env, +bool btf_struct_is_composed_of(struct bpf_verifier_env *env, const struct btf *btf, - const struct btf_type *t) + const struct btf_type *t, u32 member_kinds) +{ + return btf_struct_member_walk(env, btf, t, member_kinds, 0); +} + +static bool btf_type_is_scalar_struct(struct bpf_verifier_env *env, + const struct btf *btf, + const struct btf_type *t) { - return btf_scalar_struct_walk(env, btf, t, 0); + return btf_struct_is_composed_of(env, btf, t, BTF_MEMBER_SCALAR); } enum kfunc_ptr_arg_type { From 7991223623b5f5770952b0a3e35b06ca3862bdf2 Mon Sep 17 00:00:00 2001 From: Yonghong Song Date: Tue, 25 Aug 2026 13:54:37 -0700 Subject: [PATCH 05/10] bpf: Report which member makes a kfunc return type unsupported A kfunc that returns a struct by value may only return scalars, and the message that rejects one names the type but not the member at fault: kernel function bpf_kfunc_call_test_ret_ptr returns STRUCT prog_test_ret_ptr that is not composed of scalars For a large struct that leaves the reader to find the offending member by inspection. Record the member that made the walk fail and name it, so the verifier also dumps: member 'p' has type PTR What is recorded is a path rather than a single member, because the walk descends up to 4 levels. For struct outer { struct inner { void *p; } in; __u64 tag; }; naming 'p' alone would send the reader looking for a member struct outer does not have, so the message reads "member 'in.p' has type PTR". The detailed diagnostics for this failure: Verification failed: Program Structure: Unsupported kernel function return type Reason: bpf_kfunc_call_test_ret_ptr() returns STRUCT prog_test_ret_ptr by value. Its member 'p' is PTR, not a scalar. Only scalar values, or structs composed of scalar values, are supported as by-value kernel function return types. ... Suggestion: Call a kernel function that returns only scalars by value. A type nested deeper than the walk descends has no single member to blame, so that case reports the depth instead: Reason: bpf_kfunc_call_test_ret_deep() returns STRUCT prog_test_ret_deep by value. It nests structs more than 4 levels deep. ... Signed-off-by: Yonghong Song --- kernel/bpf/verifier.c | 86 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 12 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 5ea95e75e726a..edbc48a1fdc8f 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -11623,6 +11623,15 @@ static bool is_kfunc_arg_implicit(const struct bpf_call_arg_meta *meta, u32 arg_ return argn <= arg_idx; } +#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) { @@ -11635,10 +11644,11 @@ static bool btf_member_kind_allowed(const struct btf *btf, const struct btf_type /* * Returns true if every member of struct @t is of a kind listed in - * @member_kinds, 4 levels of nesting allowed. + * @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) + 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; @@ -11652,31 +11662,42 @@ static bool btf_struct_member_walk(struct bpf_verifier_env *env, const struct bt 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_struct_member_walk(env, btf, member_type, member_kinds, 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_member_kind_allowed(btf, member_type, member_kinds)) - return false; + 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); + return btf_struct_member_walk(env, btf, t, member_kinds, 0, NULL); } static bool btf_type_is_scalar_struct(struct bpf_verifier_env *env, @@ -11686,6 +11707,18 @@ static bool btf_type_is_scalar_struct(struct bpf_verifier_env *env, 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 { KF_ARG_CONST_MEM_SIZE, KF_ARG_MEM_SIZE, @@ -14053,17 +14086,46 @@ 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, ®s[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. */ - if (!btf_type_is_scalar_struct(env, desc_btf, t)) { - 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, 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\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.", 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 by value.", + "%s() returns %s %s by value.%s " + "Only kfuncs returning scalar values, or " + "structures composed of scalar values 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); From a9f812d3dbb6633c4a3a4f1136d8b7d2f6d09ec3 Mon Sep 17 00:00:00 2001 From: Yonghong Song Date: Tue, 25 Aug 2026 13:54:43 -0700 Subject: [PATCH 06/10] bpf: Allow a global function to return arena pointers by value A global function may already return an arena pointer on its own, and check_global_ret_scalar_reg() accepts one in either half of the R0:R2 pair. Let the members of a by-value struct it returns be arena pointers as well, rather than scalars only. Signed-off-by: Yonghong Song --- kernel/bpf/btf.c | 17 +++++++++++------ .../selftests/bpf/progs/exceptions_fail.c | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index b1f4ef614d4c4..70481fadacc0f 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -7972,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 (local_func || btf_struct_is_composed_of(env, btf, t, BTF_MEMBER_SCALAR)) + if (subprog) + member_kinds |= BTF_MEMBER_ARENA_PTR; + + if (local_func || btf_struct_is_composed_of(env, btf, t, member_kinds)) return 0; } @@ -8075,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; diff --git a/tools/testing/selftests/bpf/progs/exceptions_fail.c b/tools/testing/selftests/bpf/progs/exceptions_fail.c index 9708efb93683b..35794329640b7 100644 --- a/tools/testing/selftests/bpf/progs/exceptions_fail.c +++ b/tools/testing/selftests/bpf/progs/exceptions_fail.c @@ -60,7 +60,7 @@ __noinline int exception_cb_ok_arg_small(int a) SEC("?tc") __exception_cb(exception_cb_bad_ret_type1) -__failure __msg("Only void, scalar, or a scalar-only struct/union up to 16 bytes is supported.") +__failure __msg("Only void, a scalar, an arena pointer, or a struct/union of those") int reject_exception_cb_type_1(struct __sk_buff *ctx) { bpf_throw(0); From de5d7cea69f45b6e9509f8fc6f7abd61c49f50c6 Mon Sep 17 00:00:00 2001 From: Yonghong Song Date: Tue, 25 Aug 2026 13:54:48 -0700 Subject: [PATCH 07/10] bpf: Allow arena pointers in a by-value kfunc return A kfunc may already return an arena pointer on its own, which the program casts back into the arena address space to use. Let the members of a by-value struct it returns be arena pointers as well, rather than scalars only. Signed-off-by: Yonghong Song --- kernel/bpf/verifier.c | 23 ++++++++++--------- .../selftests/bpf/progs/aggregate_ret_kfunc.c | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index edbc48a1fdc8f..0c2181d587489 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -14090,12 +14090,12 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn, 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_struct_member_walk(env, desc_btf, t, BTF_MEMBER_SCALAR, 0, &path)) { + 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.", @@ -14106,7 +14106,7 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn, const struct btf_type *bad_type; verbose(env, - "kernel function %s returns %s %s that is not composed of scalars\n", + "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)); @@ -14114,15 +14114,16 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn, 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.", bad_name, - btf_type_str(bad_type)); + 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 by value.", + "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 " - "structures composed of scalar values are " + "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), member_note); diff --git a/tools/testing/selftests/bpf/progs/aggregate_ret_kfunc.c b/tools/testing/selftests/bpf/progs/aggregate_ret_kfunc.c index d6b422ae9784b..f10e5cf6fd890 100644 --- a/tools/testing/selftests/bpf/progs/aggregate_ret_kfunc.c +++ b/tools/testing/selftests/bpf/progs/aggregate_ret_kfunc.c @@ -71,7 +71,7 @@ __naked int aggregate_ret_kfunc_fastcall_fail(void) SEC("tc") __arch_x86_64 __arch_arm64 -__failure __msg("is not composed of scalars") +__failure __msg("is not composed of scalars or arena pointers") __naked int aggregate_ret_kfunc_ptr_fail(void) { asm volatile ( From 3dd21126de78cb66e3d3164a1a0fc7f4949f39e7 Mon Sep 17 00:00:00 2001 From: Yonghong Song Date: Tue, 25 Aug 2026 13:54:53 -0700 Subject: [PATCH 08/10] selftests/bpf: Check the member named for an unsupported kfunc return type Cover the member a rejected by-value kfunc return type is blamed on. The existing case for a struct carrying a pointer now also checks that the verifier names the member, and two cases are added: a pointer inside a nested member struct, which has to be named by its path rather than by its own name, and a type nested deeper than the walk descends, which has no single member to blame and reports the depth instead. Signed-off-by: Yonghong Song --- .../selftests/bpf/progs/aggregate_ret_kfunc.c | 34 +++++++++++++++++++ .../selftests/bpf/test_kmods/bpf_testmod.c | 16 +++++++++ .../bpf/test_kmods/bpf_testmod_kfunc.h | 21 ++++++++++++ 3 files changed, 71 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/aggregate_ret_kfunc.c b/tools/testing/selftests/bpf/progs/aggregate_ret_kfunc.c index f10e5cf6fd890..e9c82df8efb26 100644 --- a/tools/testing/selftests/bpf/progs/aggregate_ret_kfunc.c +++ b/tools/testing/selftests/bpf/progs/aggregate_ret_kfunc.c @@ -18,6 +18,8 @@ void __kfunc_btf_root(void) : "r"(&bpf_kfunc_call_test_i128), "r"(&bpf_kfunc_call_test_ret_fastcall), "r"(&bpf_kfunc_call_test_ret_ptr), + "r"(&bpf_kfunc_call_test_ret_nested), + "r"(&bpf_kfunc_call_test_ret_deep), "r"(&bpf_kfunc_call_test_ret_ii), "r"(&bpf_kfunc_call_test_ret_big)); } @@ -72,6 +74,7 @@ __naked int aggregate_ret_kfunc_fastcall_fail(void) SEC("tc") __arch_x86_64 __arch_arm64 __failure __msg("is not composed of scalars or arena pointers") +__msg("member 'p' has type PTR") __naked int aggregate_ret_kfunc_ptr_fail(void) { asm volatile ( @@ -84,6 +87,37 @@ __naked int aggregate_ret_kfunc_ptr_fail(void) : __clobber_all); } +SEC("tc") +__arch_x86_64 __arch_arm64 +__failure __msg("is not composed of scalars or arena pointers") +__msg("member 'in.p' has type PTR") +__naked int aggregate_ret_kfunc_nested_ptr_fail(void) +{ + asm volatile ( + "r1 = 0;" + "call %[bpf_kfunc_call_test_ret_nested];" + "r0 = 0;" + "exit;" + : + : __imm(bpf_kfunc_call_test_ret_nested) + : __clobber_all); +} + +SEC("tc") +__arch_x86_64 __arch_arm64 +__failure __msg("max struct nesting depth exceeded") +__naked int aggregate_ret_kfunc_too_deep_fail(void) +{ + asm volatile ( + "r1 = 0;" + "call %[bpf_kfunc_call_test_ret_deep];" + "r0 = 0;" + "exit;" + : + : __imm(bpf_kfunc_call_test_ret_deep) + : __clobber_all); +} + SEC("tc") __arch_x86_64 __arch_arm64 __failure __msg("R2 !read_ok") diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c index 850cf4f830c48..76acbe29054a2 100644 --- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c +++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c @@ -981,6 +981,20 @@ __bpf_kfunc struct prog_test_ret_ptr bpf_kfunc_call_test_ret_ptr(u64 tag) return r; } +__bpf_kfunc struct prog_test_ret_nested bpf_kfunc_call_test_ret_nested(u64 tag) +{ + struct prog_test_ret_nested r = { .in = { .p = NULL }, .tag = tag }; + + return r; +} + +__bpf_kfunc struct prog_test_ret_deep bpf_kfunc_call_test_ret_deep(u64 v) +{ + struct prog_test_ret_deep r = { .l1 = { .l2 = { .l3 = { .l4 = { .v = v } } } } }; + + return r; +} + __bpf_kfunc struct prog_test_ret_ii bpf_kfunc_call_test_ret_ii(int a, int b) { struct prog_test_ret_ii r = { .a = a, .b = b }; @@ -1539,6 +1553,8 @@ BTF_ID_FLAGS(func, bpf_kfunc_call_test_i128) BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_pair) BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_fastcall, KF_FASTCALL) BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_ptr) +BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_nested) +BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_deep) BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_ii) #endif BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_big) diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h index 65e693ada7364..52227129a49e1 100644 --- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h +++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h @@ -70,6 +70,25 @@ struct prog_test_ret_ptr { /* 16 bytes: contains a pointer */ __u64 tag; }; +struct prog_test_ret_nested { /* 16 bytes: the pointer hides one level down */ + struct { + void *p; + } in; + __u64 tag; +}; + +struct prog_test_ret_deep { /* 8 bytes, but nested past the 4-level walk limit */ + struct { + struct { + struct { + struct { + __u64 v; + } l4; + } l3; + } l2; + } l1; +}; + struct prog_test_ret_big { /* 24 bytes: too large for R0:R2 */ __u64 a; __u64 b; @@ -159,6 +178,8 @@ struct prog_test_ret_pair bpf_kfunc_call_test_ret_pair(__u64 a, __u64 b) __ksym; struct prog_test_ret_pair bpf_kfunc_call_test_ret_fastcall(__u64 a, __u64 b) __ksym; struct prog_test_ret_ii bpf_kfunc_call_test_ret_ii(int a, int b) __ksym; struct prog_test_ret_ptr bpf_kfunc_call_test_ret_ptr(__u64 tag) __ksym; +struct prog_test_ret_nested bpf_kfunc_call_test_ret_nested(__u64 tag) __ksym; +struct prog_test_ret_deep bpf_kfunc_call_test_ret_deep(__u64 v) __ksym; struct prog_test_ret_big bpf_kfunc_call_test_ret_big(void) __ksym; __u64 bpf_kfunc_call_stack_arg(__u64 a, __u64 b, __u64 c, __u64 d, __u64 e, __u64 f, __u64 g, __u64 h, From 09daf34319c97d072dba468e9fdfe2b22c900349 Mon Sep 17 00:00:00 2001 From: Yonghong Song Date: Tue, 25 Aug 2026 13:54:58 -0700 Subject: [PATCH 09/10] selftests/bpf: Test global functions returning arena pointers by value Cover the by-value struct returns a global function may now make: two arena pointers filling R0:R2, an arena pointer beside a scalar, an array of them, and an eight byte struct returned in R0 alone. The existing cases for a struct and a union carrying a plain pointer stay rejected. Signed-off-by: Yonghong Song --- .../selftests/bpf/progs/aggregate_ret_func.c | 118 ++++++++++++++++++ .../selftests/bpf/progs/verifier_arena.c | 37 ++++++ 2 files changed, 155 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/aggregate_ret_func.c b/tools/testing/selftests/bpf/progs/aggregate_ret_func.c index 6f66fc822cede..237adb8e5ee1e 100644 --- a/tools/testing/selftests/bpf/progs/aggregate_ret_func.c +++ b/tools/testing/selftests/bpf/progs/aggregate_ret_func.c @@ -2,6 +2,7 @@ /* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ #include #include +#include #include "bpf_misc.h" typedef unsigned __int128 u128; @@ -234,4 +235,121 @@ __naked int aggregate_ret_global_union_ptr_fail(void) #endif +/* + * gcc returns a by-value struct through a hidden pointer, and emits the + * 'r0 = r1' returning it after the __naked body's exit, leaving the + * subprogram falling through. Build these with clang only. + */ +#if defined(__clang__) + +struct arena_pair { + void __arena *lo; + void __arena *hi; +}; + +struct arena_and_scalar { + void __arena *p; + __u64 x; +}; + +struct arena_array { + void __arena *p[2]; +}; + +struct arena_single { + void __arena *p; +}; + +__naked struct arena_pair global_ret_arena_pair(void) +{ + asm volatile ( + "r0 = 0;" + "r2 = 0;" + "exit;" + ); +} + +SEC("tc") +__load_if_JITed() +__success __retval(0) +__naked int aggregate_ret_global_arena_pair(void) +{ + asm volatile ( + "call %[global_ret_arena_pair];" + "r0 = 0;" + "exit;" + : + : __imm(global_ret_arena_pair) + : __clobber_all); +} + +__naked struct arena_and_scalar global_ret_arena_and_scalar(void) +{ + asm volatile ( + "r0 = 0;" + "r2 = 0;" + "exit;" + ); +} + +SEC("tc") +__load_if_JITed() +__success __retval(0) +__naked int aggregate_ret_global_arena_and_scalar(void) +{ + asm volatile ( + "call %[global_ret_arena_and_scalar];" + "r0 = 0;" + "exit;" + : + : __imm(global_ret_arena_and_scalar) + : __clobber_all); +} + +__naked struct arena_array global_ret_arena_array(void) +{ + asm volatile ( + "r0 = 0;" + "r2 = 0;" + "exit;" + ); +} + +SEC("tc") +__load_if_JITed() +__success __retval(0) +__naked int aggregate_ret_global_arena_array(void) +{ + asm volatile ( + "call %[global_ret_arena_array];" + "r0 = 0;" + "exit;" + : + : __imm(global_ret_arena_array) + : __clobber_all); +} + +__naked struct arena_single global_ret_arena_single(void) +{ + asm volatile ( + "r0 = 0;" + "exit;" + ); +} + +SEC("tc") +__success __retval(0) +__naked int aggregate_ret_global_arena_single(void) +{ + asm volatile ( + "call %[global_ret_arena_single];" + "r0 = 0;" + "exit;" + : + : __imm(global_ret_arena_single) + : __clobber_all); +} + +#endif + char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/verifier_arena.c b/tools/testing/selftests/bpf/progs/verifier_arena.c index 815f342eb4b05..d37424d1161a5 100644 --- a/tools/testing/selftests/bpf/progs/verifier_arena.c +++ b/tools/testing/selftests/bpf/progs/verifier_arena.c @@ -734,4 +734,41 @@ int check_arena_arg_ret(void *ctx) return 0; } +#if defined(__clang_major__) && __clang_major__ >= 23 + +struct arena_page_pair { + u32 __arena *first; + u32 __arena *second; +}; + +__weak struct arena_page_pair split_arena_page(u32 __arena *page) +{ + struct arena_page_pair pair; + + pair.first = page; + pair.second = page + 1; + + return pair; +} + +SEC("syscall") +__load_if_JITed() +__success __retval(0) +int check_arena_struct_ret(void *ctx) +{ + u32 __arena *page = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0); + struct arena_page_pair pair; + + if (!page) + return 1; + + pair = split_arena_page(page); + if (!pair.first || !pair.second) + return 2; + + return 0; +} + +#endif + char _license[] SEC("license") = "GPL"; From 65331272e8cd105fd5111196c62ad37d9d0947c3 Mon Sep 17 00:00:00 2001 From: Yonghong Song Date: Tue, 25 Aug 2026 13:55:03 -0700 Subject: [PATCH 10/10] selftests/bpf: Test kfuncs returning arena pointers by value Cover the by-value struct returns a kfunc may now make: two arena pointers filling R0:R2, and an arena pointer beside a scalar. The existing cases for a struct and a nested struct carrying a plain pointer stay rejected. Signed-off-by: Yonghong Song --- .../selftests/bpf/prog_tests/aggregate_ret.c | 42 +++++++++++++++++ .../bpf/progs/aggregate_ret_kfunc_arena.c | 47 +++++++++++++++++++ .../selftests/bpf/test_kmods/bpf_testmod.c | 16 +++++++ .../bpf/test_kmods/bpf_testmod_kfunc.h | 18 +++++++ 4 files changed, 123 insertions(+) create mode 100644 tools/testing/selftests/bpf/progs/aggregate_ret_kfunc_arena.c diff --git a/tools/testing/selftests/bpf/prog_tests/aggregate_ret.c b/tools/testing/selftests/bpf/prog_tests/aggregate_ret.c index e0b94ed10f941..07d9d6e1d6b88 100644 --- a/tools/testing/selftests/bpf/prog_tests/aggregate_ret.c +++ b/tools/testing/selftests/bpf/prog_tests/aggregate_ret.c @@ -1,11 +1,53 @@ // SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ #include +#include #include "aggregate_ret_func.skel.h" #include "aggregate_ret_kfunc.skel.h" +#include "aggregate_ret_kfunc_arena.skel.h" + +static bool testmod_has_arena_tagged_member(void) +{ + struct btf *vmlinux_btf, *module_btf = NULL; + const struct btf_type *t; + bool tagged = false; + __s32 id; + + vmlinux_btf = btf__load_vmlinux_btf(); + if (!vmlinux_btf) + return false; + + module_btf = btf__load_module_btf("bpf_testmod", vmlinux_btf); + if (!module_btf) + goto out; + + /* prog_test_ret_arena::a is 'void __arena_tag *': PTR -> TYPE_TAG -> void */ + id = btf__find_by_name_kind(module_btf, "prog_test_ret_arena", BTF_KIND_STRUCT); + if (id <= 0) + goto out; + + t = btf__type_by_id(module_btf, btf_members(btf__type_by_id(module_btf, id))[0].type); + if (!t || !btf_is_ptr(t)) + goto out; + + t = btf__type_by_id(module_btf, t->type); + tagged = t && btf_is_type_tag(t) && + !strcmp(btf__name_by_offset(module_btf, t->name_off), "arena"); + +out: + btf__free(module_btf); + btf__free(vmlinux_btf); + + return tagged; +} void test_aggregate_ret(void) { RUN_TESTS(aggregate_ret_func); RUN_TESTS(aggregate_ret_kfunc); + + if (testmod_has_arena_tagged_member()) + RUN_TESTS(aggregate_ret_kfunc_arena); + else + test__skip(); } diff --git a/tools/testing/selftests/bpf/progs/aggregate_ret_kfunc_arena.c b/tools/testing/selftests/bpf/progs/aggregate_ret_kfunc_arena.c new file mode 100644 index 0000000000000..f68deae6c900e --- /dev/null +++ b/tools/testing/selftests/bpf/progs/aggregate_ret_kfunc_arena.c @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ +#include +#include +#include "bpf_misc.h" +#include "../test_kmods/bpf_testmod_kfunc.h" + +void __kfunc_btf_root(void) +{ + asm volatile ("" + : + : "r"(&bpf_kfunc_call_test_ret_arena), + "r"(&bpf_kfunc_call_test_ret_arena_mixed)); +} + +SEC("tc") +__arch_x86_64 __arch_arm64 +__load_if_JITed() +__success __retval(0) +__naked int aggregate_ret_kfunc_arena(void) +{ + asm volatile ( + "call %[bpf_kfunc_call_test_ret_arena];" + "r0 = 0;" + "exit;" + : + : __imm(bpf_kfunc_call_test_ret_arena) + : __clobber_all); +} + +SEC("tc") +__arch_x86_64 __arch_arm64 +__load_if_JITed() +__success __retval(0) +__naked int aggregate_ret_kfunc_arena_mixed(void) +{ + asm volatile ( + "r1 = 0;" + "call %[bpf_kfunc_call_test_ret_arena_mixed];" + "r0 = 0;" + "exit;" + : + : __imm(bpf_kfunc_call_test_ret_arena_mixed) + : __clobber_all); +} + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c index 76acbe29054a2..81fb93ea466e5 100644 --- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c +++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c @@ -981,6 +981,20 @@ __bpf_kfunc struct prog_test_ret_ptr bpf_kfunc_call_test_ret_ptr(u64 tag) return r; } +__bpf_kfunc struct prog_test_ret_arena bpf_kfunc_call_test_ret_arena(void) +{ + struct prog_test_ret_arena r = { .a = NULL, .b = NULL }; + + return r; +} + +__bpf_kfunc struct prog_test_ret_arena_mixed bpf_kfunc_call_test_ret_arena_mixed(u64 tag) +{ + struct prog_test_ret_arena_mixed r = { .p = NULL, .tag = tag }; + + return r; +} + __bpf_kfunc struct prog_test_ret_nested bpf_kfunc_call_test_ret_nested(u64 tag) { struct prog_test_ret_nested r = { .in = { .p = NULL }, .tag = tag }; @@ -1553,6 +1567,8 @@ BTF_ID_FLAGS(func, bpf_kfunc_call_test_i128) BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_pair) BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_fastcall, KF_FASTCALL) BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_ptr) +BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_arena) +BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_arena_mixed) BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_nested) BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_deep) BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_ii) diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h index 52227129a49e1..aebf88102dc6b 100644 --- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h +++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h @@ -26,6 +26,12 @@ struct prog_test_ref_kfunc { }; #endif +#if __has_attribute(btf_type_tag) +#define __arena_tag __attribute__((btf_type_tag("arena"))) +#else +#define __arena_tag +#endif + struct bpf_iter_testmod_seq; struct prog_test_pass1 { @@ -70,6 +76,16 @@ struct prog_test_ret_ptr { /* 16 bytes: contains a pointer */ __u64 tag; }; +struct prog_test_ret_arena { /* 16 bytes: two arena pointers */ + void __arena_tag *a; + void __arena_tag *b; +}; + +struct prog_test_ret_arena_mixed { /* 16 bytes: an arena pointer and a scalar */ + void __arena_tag *p; + __u64 tag; +}; + struct prog_test_ret_nested { /* 16 bytes: the pointer hides one level down */ struct { void *p; @@ -179,6 +195,8 @@ struct prog_test_ret_pair bpf_kfunc_call_test_ret_fastcall(__u64 a, __u64 b) __k struct prog_test_ret_ii bpf_kfunc_call_test_ret_ii(int a, int b) __ksym; struct prog_test_ret_ptr bpf_kfunc_call_test_ret_ptr(__u64 tag) __ksym; struct prog_test_ret_nested bpf_kfunc_call_test_ret_nested(__u64 tag) __ksym; +struct prog_test_ret_arena bpf_kfunc_call_test_ret_arena(void) __ksym; +struct prog_test_ret_arena_mixed bpf_kfunc_call_test_ret_arena_mixed(__u64 tag) __ksym; struct prog_test_ret_deep bpf_kfunc_call_test_ret_deep(__u64 v) __ksym; struct prog_test_ret_big bpf_kfunc_call_test_ret_big(void) __ksym; __u64 bpf_kfunc_call_stack_arg(__u64 a, __u64 b, __u64 c, __u64 d,