From: Yonghong Song <yonghong.song@linux.dev>
To: bpf@vger.kernel.org
Cc: Alexei Starovoitov <ast@kernel.org>,
Andrii Nakryiko <andrii@kernel.org>,
Daniel Borkmann <daniel@iogearbox.net>,
Eduard Zingerman <eddyz87@gmail.com>,
kernel-team@fb.com
Subject: [PATCH bpf-next v6 07/10] bpf: Enable aggregate return types up to 16 bytes
Date: Sun, 16 Aug 2026 21:22:17 -0700 [thread overview]
Message-ID: <20260817042217.2294438-1-yonghong.song@linux.dev> (raw)
In-Reply-To: <20260817042141.2286086-1-yonghong.song@linux.dev>
Relax btf_distill_func_proto() to accept a by-value struct or union that
the R0:R2 convention added in earlier patches can carry:
- a struct or union larger than 8 and up to 16 bytes, returned in the
R0:R2 register pair, matching what LLVM emits for the BPF target;
- a struct or union up to 8 bytes, returned in R0 alone.
A >8 byte scalar (__int128) was already accepted and is unchanged.
Everything else stays rejected: a return type larger than 16 bytes, and any
type that __get_type_size() cannot return in registers at all (e.g. an
array), which it already reports as ret < 0.
btf_validate_return_type() is relaxed as well, so that it accepts a
by-value struct or union up to 16 bytes in addition to void and scalars.
With btf_distill_func_proto() and btf_validate_return_type() relaxed, the
verifier, JIT, precision-backtracking and live-register support from the
earlier patches becomes reachable: <=16 byte aggregate return values now
work end to end.
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
include/linux/bpf_verifier.h | 2 ++
kernel/bpf/btf.c | 23 +++++++++++++++----
kernel/bpf/verifier.c | 18 +++++++--------
.../selftests/bpf/progs/exceptions_fail.c | 2 +-
4 files changed, 30 insertions(+), 15 deletions(-)
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index f70d5878fbff..938c9a9eb9d2 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -1467,6 +1467,8 @@ 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);
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 2ae7cb9b30f2..ba83fa1d52a3 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -7591,7 +7591,7 @@ int btf_distill_func_proto(struct bpf_verifier_log *log,
return -EINVAL;
}
ret = __get_type_size(btf, func->type, &t);
- if (ret < 0 || btf_type_is_struct(t)) {
+ if (ret < 0 || ret > 16) {
bpf_log(log,
"The function %s return type %s is unsupported.\n",
tname, btf_type_str(t));
@@ -7970,7 +7970,7 @@ static int btf_scan_type_tags(struct bpf_verifier_env *env,
/* 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)
+ const struct btf_type *t, int subprog, bool is_global)
{
u32 tags = 0;
int err;
@@ -7993,6 +7993,19 @@ static int btf_validate_return_type(struct bpf_verifier_env *env, struct btf *bt
if (btf_type_is_void(t) || btf_type_is_int(t) || btf_is_any_enum(t))
return 0;
+ 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.
+ */
+ bool local_func = subprog && !is_global;
+
+ if (local_func || btf_type_is_scalar_struct(env, btf, t, 0))
+ return 0;
+ }
+
return -EOPNOTSUPP;
}
@@ -8080,12 +8093,12 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
return -EINVAL;
}
- err = btf_validate_return_type(env, btf, t, subprog);
+ err = btf_validate_return_type(env, btf, t, subprog, is_global);
if (err) {
if (is_global) {
bpf_log(log,
- "Global function %s() return value not void or scalar. "
- "Only those are supported.\n",
+ "Global function %s() has unsupported return type. "
+ "Only void, scalar, or a scalar-only struct/union up to 16 bytes is supported.\n",
tname);
}
return err;
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index e371b7e27ec9..e5c8d8f7d474 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -11600,9 +11600,9 @@ 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 */
-static bool __btf_type_is_scalar_struct(struct bpf_verifier_env *env,
- const struct btf *btf,
- const struct btf_type *t, int rec)
+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 *member_type;
const struct btf_member *member;
@@ -11620,7 +11620,7 @@ static 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_type_is_scalar_struct(env, btf, member_type, rec + 1))
return false;
continue;
}
@@ -12019,7 +12019,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, 0)) {
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;
@@ -12035,7 +12035,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, 0)) {
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;
@@ -13091,7 +13091,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, 0)) {
enum bpf_reg_type reg2btf_type = lookup_reg2btf_ids(ref_id);
const char *expected_type;
@@ -13629,7 +13629,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, 0)) {
verbose(env, "bpf_percpu_obj_new type ID argument must be of a struct of scalars\n");
return -EINVAL;
}
@@ -14008,7 +14008,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, 0)) {
verbose(env,
"kernel function %s returns %s %s that is not composed of scalars\n",
func_name, btf_type_str(t),
diff --git a/tools/testing/selftests/bpf/progs/exceptions_fail.c b/tools/testing/selftests/bpf/progs/exceptions_fail.c
index ac44d60e5066..9708efb93683 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("Global function exception_cb_bad_ret_type1() return value not void or scalar.")
+__failure __msg("Only void, scalar, or a scalar-only struct/union up to 16 bytes is supported.")
int reject_exception_cb_type_1(struct __sk_buff *ctx)
{
bpf_throw(0);
--
2.53.0-Meta
next prev parent reply other threads:[~2026-08-17 4:22 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-17 4:21 [PATCH bpf-next v6 00/10] bpf: Support aggregate return values up to 16 bytes Yonghong Song
2026-08-17 4:21 ` [PATCH bpf-next v6 01/10] bpf: Factor check_global_ret_scalar_reg() out of the global return check Yonghong Song
2026-08-17 4:21 ` [PATCH bpf-next v6 02/10] bpf: Add helpers to describe the R0:R2 return register pair Yonghong Song
2026-08-17 5:17 ` bot+bpf-ci
2026-08-17 4:21 ` [PATCH bpf-next v6 03/10] bpf: Wire up JIT support for 16-byte kfunc returns Yonghong Song
2026-08-17 4:37 ` sashiko-bot
2026-08-17 4:22 ` [PATCH bpf-next v6 04/10] bpf: Handle R2 as a return register in precision backtracking Yonghong Song
2026-08-17 5:17 ` bot+bpf-ci
2026-08-17 4:22 ` [PATCH bpf-next v6 05/10] bpf: Account R2 of register-pair returns in live register analysis Yonghong Song
2026-08-17 4:43 ` sashiko-bot
2026-08-17 4:22 ` [PATCH bpf-next v6 06/10] bpf: Add verifier support for 16-byte returns in R0:R2 Yonghong Song
2026-08-17 4:42 ` sashiko-bot
2026-08-17 5:17 ` bot+bpf-ci
2026-08-17 4:22 ` Yonghong Song [this message]
2026-08-17 4:42 ` [PATCH bpf-next v6 07/10] bpf: Enable aggregate return types up to 16 bytes sashiko-bot
2026-08-17 4:22 ` [PATCH bpf-next v6 08/10] selftests/bpf: Add C tests for 16-byte returns in R0:R2 Yonghong Song
2026-08-17 4:45 ` sashiko-bot
2026-08-17 4:22 ` [PATCH bpf-next v6 09/10] selftests/bpf: Add inline-asm and subprog tests for R0:R2 returns Yonghong Song
2026-08-17 4:42 ` sashiko-bot
2026-08-17 5:17 ` bot+bpf-ci
2026-08-17 4:22 ` [PATCH bpf-next v6 10/10] Documentation/bpf: Document up to 16-byte kfunc return values in R0:R2 Yonghong Song
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260817042217.2294438-1-yonghong.song@linux.dev \
--to=yonghong.song@linux.dev \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=kernel-team@fb.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox