BPF List
 help / color / mirror / Atom feed
* [PATCH bpf-next v2 00/10] bpf: Allow arena pointers in by-value returns
@ 2026-08-25 20:54 Yonghong Song
  2026-08-25 20:54 ` [PATCH bpf-next v2 01/10] bpf: Record each half of a paired return value in verifier diagnostics Yonghong Song
                   ` (9 more replies)
  0 siblings, 10 replies; 27+ messages in thread
From: Yonghong Song @ 2026-08-25 20:54 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, kernel-team

A function returning a struct by value may only return one whose members
are all scalars. That is stricter than it needs to be: an arena pointer
is safe to hand over as raw register bits, and both a global function
and a kfunc can already return one on its own.

This patch set allows returning arena pointer(s) (as member(s) of a
struct) for global functions and kfuncs. Any other pointer member stays
rejected, as it would be laundered into a scalar and escape provenance
and reference tracking.

Patch 1 fixes a diagnostics bug. Patches 2-4 are refactoring with no
functional change. Patch 5 improves the diagnostics for an unsupported
return type, and patches 6-7 allow arena pointer members. Patches 8-10
are selftests.

Changelog:
  v1 -> v2:
    - v1: https://lore.kernel.org/bpf/20260824144943.991316-1-yonghong.song@linux.dev/
    - Add nested struct field names for diagnostics, and report the
      nesting depth for a type nested past the walk limit.
    - Allow to return arena pointers for global functions and kfuncs.
    - Necessary selftests for newly supported arena pointers.

Yonghong Song (10):
  bpf: Record each half of a paired return value in verifier diagnostics
  bpf: Drop the recursion depth argument of btf_type_is_scalar_struct()
  bpf: Add btf_type_is_arena_ptr()
  bpf: Let the by-value struct walk take the kinds of member it accepts
  bpf: Report which member makes a kfunc return type unsupported
  bpf: Allow a global function to return arena pointers by value
  bpf: Allow arena pointers in a by-value kfunc return
  selftests/bpf: Check the member named for an unsupported kfunc return
    type
  selftests/bpf: Test global functions returning arena pointers by value
  selftests/bpf: Test kfuncs returning arena pointers by value

 include/linux/bpf_verifier.h                  |  11 +-
 include/linux/btf.h                           |   1 +
 kernel/bpf/btf.c                              |  79 ++++------
 kernel/bpf/verifier.c                         | 148 ++++++++++++++----
 .../selftests/bpf/prog_tests/aggregate_ret.c  |  42 +++++
 .../selftests/bpf/progs/aggregate_ret_func.c  | 118 ++++++++++++++
 .../selftests/bpf/progs/aggregate_ret_kfunc.c |  36 ++++-
 .../bpf/progs/aggregate_ret_kfunc_arena.c     |  47 ++++++
 .../selftests/bpf/progs/exceptions_fail.c     |   2 +-
 .../selftests/bpf/progs/verifier_arena.c      |  37 +++++
 .../selftests/bpf/test_kmods/bpf_testmod.c    |  32 ++++
 .../bpf/test_kmods/bpf_testmod_kfunc.h        |  39 +++++
 12 files changed, 513 insertions(+), 79 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/progs/aggregate_ret_kfunc_arena.c

-- 
2.53.0-Meta


^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH bpf-next v2 01/10] bpf: Record each half of a paired return value in verifier diagnostics
  2026-08-25 20:54 [PATCH bpf-next v2 00/10] bpf: Allow arena pointers in by-value returns Yonghong Song
@ 2026-08-25 20:54 ` Yonghong Song
  2026-08-25 21:59   ` bot+bpf-ci
  2026-08-25 20:54 ` [PATCH bpf-next v2 02/10] bpf: Drop the recursion depth argument of btf_type_is_scalar_struct() Yonghong Song
                   ` (8 subsequent siblings)
  9 siblings, 1 reply; 27+ messages in thread
From: Yonghong Song @ 2026-08-25 20:54 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, kernel-team

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 <eddyz87@gmail.com>
Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
 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 e036ae20bf6b..9aa29c367008 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,
-- 
2.53.0-Meta


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH bpf-next v2 02/10] bpf: Drop the recursion depth argument of btf_type_is_scalar_struct()
  2026-08-25 20:54 [PATCH bpf-next v2 00/10] bpf: Allow arena pointers in by-value returns Yonghong Song
  2026-08-25 20:54 ` [PATCH bpf-next v2 01/10] bpf: Record each half of a paired return value in verifier diagnostics Yonghong Song
@ 2026-08-25 20:54 ` Yonghong Song
  2026-08-25 20:54 ` [PATCH bpf-next v2 03/10] bpf: Add btf_type_is_arena_ptr() Yonghong Song
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 27+ messages in thread
From: Yonghong Song @ 2026-08-25 20:54 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, kernel-team

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 <yonghong.song@linux.dev>
---
 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 004b06785521..3eb61edc8c5e 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 91b8ce77f699..47d43eb983a5 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 9aa29c367008..9799b50b97cd 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),
-- 
2.53.0-Meta


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH bpf-next v2 03/10] bpf: Add btf_type_is_arena_ptr()
  2026-08-25 20:54 [PATCH bpf-next v2 00/10] bpf: Allow arena pointers in by-value returns Yonghong Song
  2026-08-25 20:54 ` [PATCH bpf-next v2 01/10] bpf: Record each half of a paired return value in verifier diagnostics Yonghong Song
  2026-08-25 20:54 ` [PATCH bpf-next v2 02/10] bpf: Drop the recursion depth argument of btf_type_is_scalar_struct() Yonghong Song
@ 2026-08-25 20:54 ` Yonghong Song
  2026-08-25 21:59   ` bot+bpf-ci
  2026-08-25 20:54 ` [PATCH bpf-next v2 04/10] bpf: Let the by-value struct walk take the kinds of member it accepts Yonghong Song
                   ` (6 subsequent siblings)
  9 siblings, 1 reply; 27+ messages in thread
From: Yonghong Song @ 2026-08-25 20:54 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, kernel-team

Simplify btf_scan_type_tags() and added a new helper
btf_type_is_arena_ptr(). No functional change.

Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
 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 89d5a5c4f117..ddd0f4f32d24 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 47d43eb983a5..280530d25886 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))
-- 
2.53.0-Meta


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH bpf-next v2 04/10] bpf: Let the by-value struct walk take the kinds of member it accepts
  2026-08-25 20:54 [PATCH bpf-next v2 00/10] bpf: Allow arena pointers in by-value returns Yonghong Song
                   ` (2 preceding siblings ...)
  2026-08-25 20:54 ` [PATCH bpf-next v2 03/10] bpf: Add btf_type_is_arena_ptr() Yonghong Song
@ 2026-08-25 20:54 ` Yonghong Song
  2026-08-25 21:59   ` bot+bpf-ci
  2026-08-25 20:54 ` [PATCH bpf-next v2 05/10] bpf: Report which member makes a kfunc return type unsupported Yonghong Song
                   ` (5 subsequent siblings)
  9 siblings, 1 reply; 27+ messages in thread
From: Yonghong Song @ 2026-08-25 20:54 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, kernel-team

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 <yonghong.song@linux.dev>
---
 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 3eb61edc8c5e..be3ec883c08f 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 280530d25886..b1f4ef614d4c 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 9799b50b97cd..5ea95e75e726 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 {
-- 
2.53.0-Meta


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH bpf-next v2 05/10] bpf: Report which member makes a kfunc return type unsupported
  2026-08-25 20:54 [PATCH bpf-next v2 00/10] bpf: Allow arena pointers in by-value returns Yonghong Song
                   ` (3 preceding siblings ...)
  2026-08-25 20:54 ` [PATCH bpf-next v2 04/10] bpf: Let the by-value struct walk take the kinds of member it accepts Yonghong Song
@ 2026-08-25 20:54 ` Yonghong Song
  2026-08-25 21:59   ` bot+bpf-ci
  2026-08-25 20:54 ` [PATCH bpf-next v2 06/10] bpf: Allow a global function to return arena pointers by value Yonghong Song
                   ` (4 subsequent siblings)
  9 siblings, 1 reply; 27+ messages in thread
From: Yonghong Song @ 2026-08-25 20:54 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, kernel-team

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 <yonghong.song@linux.dev>
---
 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 5ea95e75e726..edbc48a1fdc8 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, &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.
 		 */
-		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);
-- 
2.53.0-Meta


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH bpf-next v2 06/10] bpf: Allow a global function to return arena pointers by value
  2026-08-25 20:54 [PATCH bpf-next v2 00/10] bpf: Allow arena pointers in by-value returns Yonghong Song
                   ` (4 preceding siblings ...)
  2026-08-25 20:54 ` [PATCH bpf-next v2 05/10] bpf: Report which member makes a kfunc return type unsupported Yonghong Song
@ 2026-08-25 20:54 ` Yonghong Song
  2026-08-25 21:12   ` sashiko-bot
  2026-08-25 20:54 ` [PATCH bpf-next v2 07/10] bpf: Allow arena pointers in a by-value kfunc return Yonghong Song
                   ` (3 subsequent siblings)
  9 siblings, 1 reply; 27+ messages in thread
From: Yonghong Song @ 2026-08-25 20:54 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, kernel-team

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 <yonghong.song@linux.dev>
---
 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 b1f4ef614d4c..70481fadacc0 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 9708efb93683..35794329640b 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);
-- 
2.53.0-Meta


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH bpf-next v2 07/10] bpf: Allow arena pointers in a by-value kfunc return
  2026-08-25 20:54 [PATCH bpf-next v2 00/10] bpf: Allow arena pointers in by-value returns Yonghong Song
                   ` (5 preceding siblings ...)
  2026-08-25 20:54 ` [PATCH bpf-next v2 06/10] bpf: Allow a global function to return arena pointers by value Yonghong Song
@ 2026-08-25 20:54 ` Yonghong Song
  2026-08-25 22:13   ` bot+bpf-ci
  2026-08-25 20:54 ` [PATCH bpf-next v2 08/10] selftests/bpf: Check the member named for an unsupported kfunc return type Yonghong Song
                   ` (2 subsequent siblings)
  9 siblings, 1 reply; 27+ messages in thread
From: Yonghong Song @ 2026-08-25 20:54 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, kernel-team

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 <yonghong.song@linux.dev>
---
 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 edbc48a1fdc8..0c2181d58748 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 d6b422ae9784..f10e5cf6fd89 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 (
-- 
2.53.0-Meta


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH bpf-next v2 08/10] selftests/bpf: Check the member named for an unsupported kfunc return type
  2026-08-25 20:54 [PATCH bpf-next v2 00/10] bpf: Allow arena pointers in by-value returns Yonghong Song
                   ` (6 preceding siblings ...)
  2026-08-25 20:54 ` [PATCH bpf-next v2 07/10] bpf: Allow arena pointers in a by-value kfunc return Yonghong Song
@ 2026-08-25 20:54 ` Yonghong Song
  2026-08-25 20:54 ` [PATCH bpf-next v2 09/10] selftests/bpf: Test global functions returning arena pointers by value Yonghong Song
  2026-08-25 20:55 ` [PATCH bpf-next v2 10/10] selftests/bpf: Test kfuncs " Yonghong Song
  9 siblings, 0 replies; 27+ messages in thread
From: Yonghong Song @ 2026-08-25 20:54 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, kernel-team

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 <yonghong.song@linux.dev>
---
 .../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 f10e5cf6fd89..e9c82df8efb2 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 850cf4f830c4..76acbe29054a 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 65e693ada736..52227129a49e 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,
-- 
2.53.0-Meta


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH bpf-next v2 09/10] selftests/bpf: Test global functions returning arena pointers by value
  2026-08-25 20:54 [PATCH bpf-next v2 00/10] bpf: Allow arena pointers in by-value returns Yonghong Song
                   ` (7 preceding siblings ...)
  2026-08-25 20:54 ` [PATCH bpf-next v2 08/10] selftests/bpf: Check the member named for an unsupported kfunc return type Yonghong Song
@ 2026-08-25 20:54 ` Yonghong Song
  2026-08-25 21:59   ` bot+bpf-ci
  2026-08-25 20:55 ` [PATCH bpf-next v2 10/10] selftests/bpf: Test kfuncs " Yonghong Song
  9 siblings, 1 reply; 27+ messages in thread
From: Yonghong Song @ 2026-08-25 20:54 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, kernel-team

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 <yonghong.song@linux.dev>
---
 .../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 6f66fc822ced..237adb8e5ee1 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 <linux/bpf.h>
 #include <bpf/bpf_helpers.h>
+#include <bpf_arena_common.h>
 #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 815f342eb4b0..d37424d1161a 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";
-- 
2.53.0-Meta


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH bpf-next v2 10/10] selftests/bpf: Test kfuncs returning arena pointers by value
  2026-08-25 20:54 [PATCH bpf-next v2 00/10] bpf: Allow arena pointers in by-value returns Yonghong Song
                   ` (8 preceding siblings ...)
  2026-08-25 20:54 ` [PATCH bpf-next v2 09/10] selftests/bpf: Test global functions returning arena pointers by value Yonghong Song
@ 2026-08-25 20:55 ` Yonghong Song
  2026-08-25 21:59   ` bot+bpf-ci
  9 siblings, 1 reply; 27+ messages in thread
From: Yonghong Song @ 2026-08-25 20:55 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, kernel-team

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 <yonghong.song@linux.dev>
---
 .../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 e0b94ed10f94..07d9d6e1d6b8 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 <test_progs.h>
+#include <bpf/btf.h>
 #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 000000000000..f68deae6c900
--- /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 <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#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 76acbe29054a..81fb93ea466e 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 52227129a49e..aebf88102dc6 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,
-- 
2.53.0-Meta


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: [PATCH bpf-next v2 06/10] bpf: Allow a global function to return arena pointers by value
  2026-08-25 20:54 ` [PATCH bpf-next v2 06/10] bpf: Allow a global function to return arena pointers by value Yonghong Song
@ 2026-08-25 21:12   ` sashiko-bot
  2026-08-26 18:40     ` Yonghong Song
  0 siblings, 1 reply; 27+ messages in thread
From: sashiko-bot @ 2026-08-25 21:12 UTC (permalink / raw)
  To: Yonghong Song; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] BPF_PROG_TYPE_EXT (freplace) programs are erroneously rejected when replacing a global function that returns an arena pointer or a struct containing an arena pointer.
- [High] Callers of global functions returning arena pointers receive a `SCALAR_VALUE` instead of `PTR_TO_ARENA`, causing valid dereferences to be rejected.
--

commit 9797a7277b67bb03067fdf11db423996a277abbd
Author: Yonghong Song <yonghong.song@linux.dev>

bpf: Allow a global function to return arena pointers by value

This commit relaxes BTF verification to allow global functions to return
arena pointers or structures containing arena pointers. It updates the
verifier's return type validation to recognize these types when processing
subprograms.

> 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;

[Severity: High]
Does this subprog condition inadvertently reject valid BPF_PROG_TYPE_EXT
(freplace) programs?

When replacing a global function that returns an arena pointer, the
extension's main program must have an identical BTF signature to the target.
During verification of the extension's main program (where subprog is 0),
btf_prepare_func_args() is called. Since subprog evaluates to false,
BTF_MEMBER_ARENA_PTR isn't added to member_kinds, causing the verifier to
fall back and return -EOPNOTSUPP.

[Severity: High]
Even though this patch allows global functions to return arena pointers,
won't callers still be unable to dereference them?

In check_func_call() inside kernel/bpf/verifier.c, the return registers of
global functions are unconditionally marked as unknown scalars:

	if (!returns_void) {
		nregs = bpf_ret_reg_pair(env, subprog) ? 2 : 1;
		for (i = 0; i < nregs; i++)
			mark_reg_unknown(env, caller->regs, ret_regs[i]);
		bpf_diag_mod_end(env);
	}

Since the C type returned by the function (void __arena *) matches the type
in the caller, the compiler doesn't emit an addr_space_cast instruction.
Without this cast, the scalar is never promoted back to a PTR_TO_ARENA,
causing check_mem_access() to reject subsequent dereferences with an "invalid
mem access 'scalar'" error.

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

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260825205412.1320099-1-yonghong.song@linux.dev?part=6

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH bpf-next v2 01/10] bpf: Record each half of a paired return value in verifier diagnostics
  2026-08-25 20:54 ` [PATCH bpf-next v2 01/10] bpf: Record each half of a paired return value in verifier diagnostics Yonghong Song
@ 2026-08-25 21:59   ` bot+bpf-ci
  2026-08-26 17:08     ` Yonghong Song
  0 siblings, 1 reply; 27+ messages in thread
From: bot+bpf-ci @ 2026-08-25 21:59 UTC (permalink / raw)
  To: yonghong.song, bpf
  Cc: ast, andrii, daniel, eddyz87, kernel-team, ast, andrii, daniel,
	martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 2507 bytes --]

> 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,

This fix looks correct for prepare_func_exit(). Should check_func_call()
and check_kfunc_call() get the same per-return-register diagnostic scope,
or is R2 intentionally left out there?

Commit 0630ad00d96d introduced the same pattern in three places, and this
patch addresses only prepare_func_exit().

In check_func_call(), the global-subprog path has:

    bpf_diag_mod_begin(env, &caller->regs[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);
    ...
    nregs = bpf_ret_reg_pair(env, subprog) ? 2 : 1;
    for (i = 0; i < nregs; i++)
            mark_reg_unknown(env, caller->regs, ret_regs[i]);
    bpf_diag_mod_end(env);

When nregs is 2, mark_reg_unknown() writes into caller->regs[BPF_REG_2],
but the scope target is caller->regs[BPF_REG_0], so the write emits no
BPF_DIAG_HISTORY_MOD event.

Similarly in check_kfunc_call():

    bpf_diag_mod_begin(env, &regs[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);
    ...
    mark_kfunc_ret_regs(env, regs, t->size);   /* writes R0 and, for 8 < size <= 16, R2 */
    ...
    bpf_diag_mod_end(env);

When t->size is between 8 and 16 bytes, mark_kfunc_ret_regs() writes the
second half into R2, but again with no MOD event.

In both cases R2 does get a BPF_DIAG_MOD_CALLER_SAVED scrub record, so
the history shows R2 as clobbered by the call but never shows it being
written with the second half of the return value.


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32899532405

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH bpf-next v2 03/10] bpf: Add btf_type_is_arena_ptr()
  2026-08-25 20:54 ` [PATCH bpf-next v2 03/10] bpf: Add btf_type_is_arena_ptr() Yonghong Song
@ 2026-08-25 21:59   ` bot+bpf-ci
  2026-08-26 17:28     ` Yonghong Song
  0 siblings, 1 reply; 27+ messages in thread
From: bot+bpf-ci @ 2026-08-25 21:59 UTC (permalink / raw)
  To: yonghong.song, bpf
  Cc: ast, andrii, daniel, eddyz87, kernel-team, ast, andrii, daniel,
	martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 2277 bytes --]

> 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.

This isn't a bug, but could the changelog use the imperative mood
consistently (e.g., "Simplify ... and add ...")?  Also, could it explain
why the helper needs external linkage, such as noting that a later patch
calls it from verifier.c?

> 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;
> +}

This isn't a bug, but since btf_type_tag_walk() sits right above this and
is still used by btf_find_kptr() and friends, could btf_type_is_arena_ptr()
be expressed in terms of it rather than repeating the modifier/type-tag
walk?

[ ... ]


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32899532405

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH bpf-next v2 04/10] bpf: Let the by-value struct walk take the kinds of member it accepts
  2026-08-25 20:54 ` [PATCH bpf-next v2 04/10] bpf: Let the by-value struct walk take the kinds of member it accepts Yonghong Song
@ 2026-08-25 21:59   ` bot+bpf-ci
  2026-08-26 17:39     ` Yonghong Song
  0 siblings, 1 reply; 27+ messages in thread
From: bot+bpf-ci @ 2026-08-25 21:59 UTC (permalink / raw)
  To: yonghong.song, bpf
  Cc: ast, andrii, daniel, eddyz87, kernel-team, ast, andrii, daniel,
	martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 2076 bytes --]

> 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 <yonghong.song@linux.dev>

This isn't a bug, but the changelog only describes the one-line call swap
in btf.c, while two other parts of the diff are what the "no functional
change" claim rests on.

The first is a new enumeration in a global header:

> diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
> +/* 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 */
> +};

The second is the array-member branch of the walk losing its trailing
continue and falling through into the shared kind check:

> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>  			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;

Equivalence here holds because btf_member_kind_allowed() collapses to
btf_type_is_scalar() when the mask is BTF_MEMBER_SCALAR alone, and because
the shared check is the last statement of the for_each_member() body, so no
member is skipped or checked twice.

Would it help to mention the new enum btf_member_kind in bpf_verifier.h and
this array-branch fall-through in the changelog, since those are what the
"no functional change" claim rests on?


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32899532405

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH bpf-next v2 05/10] bpf: Report which member makes a kfunc return type unsupported
  2026-08-25 20:54 ` [PATCH bpf-next v2 05/10] bpf: Report which member makes a kfunc return type unsupported Yonghong Song
@ 2026-08-25 21:59   ` bot+bpf-ci
  2026-08-26 17:59     ` Yonghong Song
  0 siblings, 1 reply; 27+ messages in thread
From: bot+bpf-ci @ 2026-08-25 21:59 UTC (permalink / raw)
  To: yonghong.song, bpf
  Cc: ast, andrii, daniel, eddyz87, kernel-team, ast, andrii, daniel,
	martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 8955 bytes --]

> 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.

Does the Reason text shown here match what the verifier actually emits?

The code at verifier.c:14120-14128 uses this format string:

  "%s() returns %s %s by value.%s "
  "Only kfuncs returning scalar values, or "
  "structures composed of scalar values are "
  "supported."

which would produce "... Only kfuncs returning scalar values, or
structures composed of scalar values are supported." but the quoted
output says "... Only scalar values, or structs composed of scalar
values, are supported as by-value kernel function return types."

The first two sentences of the Reason and the Suggestion match what the
code emits, but the third sentence appears different. Looking at the
forward range through 6ab11e51f544, commit 37ffe60f491e5 changes it again
to "... or arena pointers, or structures composed of scalar values and
arena pointers are supported." but still not the wording above.

Could the changelog be updated with the actual verifier output?

>   ...
>   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. ...

> 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

[ ... ]

> @@ -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)

All external callers (btf_struct_is_composed_of() and check_kfunc_call())
pass rec=0. Could the rec parameter be removed from the public API and
initialized internally?

This was raised by Eduard Zingerman in v1 and acknowledged with "Ack, will
do" but the parameter remains in v2.

>  {
>  	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;
>  }

[ ... ]

> @@ -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));
> +}

Can this handle anonymous struct/union members correctly?

BTF allows struct members to have name_off == 0 for anonymous members.
When btf_name_by_offset() is called with name_off == 0, it returns the
empty string (the start of the string table). The walk descends into
anonymous struct/union members since btf_type_is_struct() at
verifier.c:11664 returns true for both BTF_KIND_STRUCT and BTF_KIND_UNION.

For a kfunc like:

  struct ret_anon {
          union { void *p; __u64 v; };  /* anonymous union */
          __u64 tag;
  };
  __bpf_kfunc struct ret_anon bpf_kfunc_ret_anon(void);

the walk would fail on 'p' at rec == 1, so path->depth == 2 with
path->member[0] being the unnamed union and path->member[1] being 'p'.
This would produce the path string ".p" since the first component is empty
but the separator "." is still emitted.

Would it make sense to skip empty components or substitute something like
"<anon>" to keep the path readable?

> +
>  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, &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.
>  		 */
> -		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);

---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32899532405

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH bpf-next v2 09/10] selftests/bpf: Test global functions returning arena pointers by value
  2026-08-25 20:54 ` [PATCH bpf-next v2 09/10] selftests/bpf: Test global functions returning arena pointers by value Yonghong Song
@ 2026-08-25 21:59   ` bot+bpf-ci
  2026-08-27  3:46     ` Yonghong Song
  0 siblings, 1 reply; 27+ messages in thread
From: bot+bpf-ci @ 2026-08-25 21:59 UTC (permalink / raw)
  To: yonghong.song, bpf
  Cc: ast, andrii, daniel, eddyz87, kernel-team, ast, andrii, daniel,
	martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 4605 bytes --]

> 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 <linux/bpf.h>
>  #include <bpf/bpf_helpers.h>
> +#include <bpf_arena_common.h>
>  #include "bpf_misc.h"

[ ... ]

> @@ -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__)

Should this use the same clang version check as the structurally identical
block at line 178? That block checks '#if defined(__clang_major__) &&
__clang_major__ >= 23' for __naked functions returning by-value structs:

aggregate_ret_func.c:178
#if defined(__clang_major__) && __clang_major__ >= 23
...
__naked struct with_ptr global_ret_struct_ptr(void)
{
	asm volatile (
	"r0 = 0;"
	"r2 = 0;"
	"exit;"
	);
}

SEC("tc")
__load_if_JITed()
__failure __msg("has unsupported return type")
__naked int aggregate_ret_global_struct_ptr_fail(void)
{
	asm volatile (
	"call %[global_ret_struct_ptr];"
	"r0 = 0;"
	"exit;"
	:
	: __imm(global_ret_struct_ptr)
	: __clobber_all);
}

The verifier_arena.c hunk added in this same commit also uses the '>= 23'
guard:

verifier_arena.c:737
#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)
{
	...
}

By-value struct returns in registers are an LLVM 23 BPF-ABI feature. With
an older clang, the return is lowered through a hidden pointer (sret), which
is the gcc behavior described in your new comment.

With clang < 23, the four new '__success __retval(0)' tests would be
compiled against a different signature than intended. The callers never
initialize r1, so they would be checked against a prototype that no longer
matches 'returns struct arena_pair' in the BTF.

Can the two adjacent blocks be consistent about which toolchains can build
the same construct?

> +
> +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";

[ ... ]


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32899532405

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH bpf-next v2 10/10] selftests/bpf: Test kfuncs returning arena pointers by value
  2026-08-25 20:55 ` [PATCH bpf-next v2 10/10] selftests/bpf: Test kfuncs " Yonghong Song
@ 2026-08-25 21:59   ` bot+bpf-ci
  2026-08-27  3:58     ` Yonghong Song
  0 siblings, 1 reply; 27+ messages in thread
From: bot+bpf-ci @ 2026-08-25 21:59 UTC (permalink / raw)
  To: yonghong.song, bpf
  Cc: ast, andrii, daniel, eddyz87, kernel-team, ast, andrii, daniel,
	martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 6829 bytes --]

> 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 <test_progs.h>
> +#include <bpf/btf.h>
>  #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();

A subsystem pattern flags this as potentially concerning:
testmod_has_arena_tagged_member() collapses four distinct outcomes
into the same 'false' return: environment broken (no vmlinux BTF),
testmod absent or BTF missing, stale bpf_testmod.ko, or the actual
capability probe (btf_type_tag attribute absent). When test__skip()
is called without a reason string, the run shows 'aggregate_ret:OK
(SKIP: 1/N)' with no indication of what was skipped or why.

Since PAHOLE_HAS_BTF_TAG depends on CC_IS_CLANG (lib/Kconfig.debug)
and GCC does not implement btf_type_tag, the skip branch is the normal
outcome on a GCC-built kernel, which makes a permanently-skipped test
easy to miss. The test appears to pass but the new coverage never runs.

The BPF selftests/bpf/prog_tests/btf_tag.c establishes a precedent:
it checks env.has_testmod, uses ASSERT_OK_PTR() so a BTF-load failure
is a real FAIL, and prints a reason string before test__skip():

    printf("%s:SKIP: btf_type_tag attribute not in %s", __func__, module_name)

Should testmod_has_arena_tagged_member() distinguish a broken environment
(ASSERT_OK_PTR on the vmlinux BTF) from a missing capability, and should
the test__skip() carry a reason string?

>  }

> 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 <vmlinux.h>
> +#include <bpf/bpf_helpers.h>
> +#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));
> +}

A subsystem pattern flags this as potentially concerning: a new BPF
prog file is added for two test cases, but progs/aggregate_ret_kfunc.c
in the same directory already covers by-value kfunc returns
(aggregate_ret_kfunc_precise, _fastcall_fail, _ptr_fail,
_nested_ptr_fail, _too_deep_fail, _small_no_r2, _too_big_fail). The
new file duplicates the header includes, the license string, the
SEC("tc")/__arch_x86_64/__arch_arm64/__load_if_JITed() preamble and
__kfunc_btf_root() itself, for two subtests.

RUN_TESTS() operates on a whole skeleton (test_progs.h:546 ->
test_loader__run_subtests(&tester, #skel, skel##__elf_bytes)), and
these two subtests must be gated on whether the running bpf_testmod's
BTF carries the 'arena' type tag - a per-toolchain condition that has
no per-subtest expression in bpf_misc.h (__arch_*/__load_if_JITed
cover architecture and JIT only). Putting them in
aggregate_ret_kfunc.c would force the whole existing matrix behind
the same gate.

Is the separate file justified by the per-skeleton gating requirement,
or could the new cases be added to aggregate_ret_kfunc.c?

> +
> +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);
> +}

The changelog claims this patch covers 'two arena pointers filling
R0:R2', but neither new program ever reads R2. Both bodies are
'call <kfunc>; r0 = 0; exit;' with __success __retval(0), which only
proves that check_kfunc_call() accepted the return type - i.e. that
btf_struct_member_walk(..., BTF_MEMBER_SCALAR | BTF_MEMBER_ARENA_PTR, ...)
returned true (kernel/bpf/verifier.c:14097). The number of return
registers is decided separately by mark_kfunc_ret_regs() -> ret_regs_cnt(size)
(kernel/bpf/verifier.c:11351, :413); if that path regressed to a
single register for a 16-byte STRUCT return, both new tests would
still pass, because R2 is never read.

The file this patch sits beside already establishes the idiom for
pinning this: aggregate_ret_kfunc_small_no_r2 asserts __failure
__msg("R2 !read_ok") to prove R2 is *not* a return register for an
8-byte struct, and aggregate_ret_kfunc_precise reads 'r6 = r2' after
the __int128 kfunc to prove R2 *is*.

Should at least one of the arena tests read R2 back to verify the
two-register return path?

> +
> +char _license[] SEC("license") = "GPL";

[ ... ]


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32899532405

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH bpf-next v2 07/10] bpf: Allow arena pointers in a by-value kfunc return
  2026-08-25 20:54 ` [PATCH bpf-next v2 07/10] bpf: Allow arena pointers in a by-value kfunc return Yonghong Song
@ 2026-08-25 22:13   ` bot+bpf-ci
  2026-08-26 18:57     ` Yonghong Song
  0 siblings, 1 reply; 27+ messages in thread
From: bot+bpf-ci @ 2026-08-25 22:13 UTC (permalink / raw)
  To: yonghong.song, bpf
  Cc: ast, andrii, daniel, eddyz87, kernel-team, ast, andrii, daniel,
	martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 8482 bytes --]

> 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)) {

The rewritten comment drops the mechanism that justified the rule and, as
written, gives a rationale that contradicts the new behaviour. The
previous comment was:

  /* 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 new text keeps the 'laundered into a scalar' conclusion but deletes
the 'comes back as raw register bits modeled as an unknown scalar' premise,
and then asserts arena pointers are fine. A reader is left with an
inconsistency, because an arena pointer member is laundered into a scalar
in exactly the same way -- mark_kfunc_ret_regs() marks both R0 and R2 as
unknown scalars regardless of member kinds.

The actual reason arena pointers are safe is not stated anywhere in the
change: a laundered arena address is only usable after
addr_space_cast/cast_kern, check_alu_op() already lets a program
cast_kern() any scalar, and cast_kern is lowered to a 32-bit ALU mov so
the result is confined to the arena window at r12. One clause noting that
arena pointers need no provenance because they are re-derived by
cast_kern would restore the invariant explanation.

Also, 'Otherwise, any other pointer' is redundant ('otherwise' plus
'any other').

> @@ -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);

In the bpf_diag_program_structure() reason text, 'structures composed of
scalar values and arena pointers' reads as a conjunction, i.e. that a
supported struct must contain both scalars and arena pointers. The
accepted set is a disjunction: BTF_MEMBER_SCALAR | BTF_MEMBER_ARENA_PTR
accepts any mix, including all-scalar and all-arena-pointer structs. The
other three strings in the same diagnostic use 'or' consistently, so this
line is the odd one out. Would 'structures composed of scalar values or
arena pointers' match?

> 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)

[ ... ]

This commit relaxes the by-value kfunc struct return rule from
BTF_MEMBER_SCALAR to BTF_MEMBER_SCALAR | BTF_MEMBER_ARENA_PTR and updates
all four user-visible verifier strings accordingly, but
Documentation/bpf/kfuncs.rst section 2.9 still documents the old contract
and is now factually wrong in three places:

  - 'A struct or union returned by value must be composed only of scalars
    (recursively), where a scalar is an integer or an enum'
  - 'A struct or union with a pointer member is therefore rejected at load
    time' -- a struct with a 'void __arena *' (btf_type_tag("arena"))
    member is now accepted
  - 'A kfunc may also return a value larger than 8 bytes and up to 16
    bytes -- a scalar-only struct or union, or an __int128'
  - 'A global subprogram is verified in isolation, so its by-value struct
    or union return is restricted to scalars just like a kfunc's' -- also
    invalidated by patch 4 of the same series (ba9c98e6fb9db, 'bpf: Allow
    a global function to return arena pointers by value')

This paragraph is not boilerplate: it was written specifically to explain
this restriction (it even paraphrases the code comment that this commit
deletes), so it is the authoritative reference for the rule being changed.
Looking at the range 37ffe60f491e..6ab11e51f5443, no commit in the series
updates Documentation/, so the documentation stays contradictory after the
whole series is applied.

Could the paragraph be extended to say members may be scalars or arena
pointers, and explain that an arena pointer member is likewise handed back
as a scalar which the program must cast_kern() before use?

The new acceptance path is reachable only through an annotation mechanism
the kfunc ABI does not currently offer. btf_type_is_arena_ptr() matches
only a kflag=0 BTF_KIND_TYPE_TAG named 'arena' and explicitly skips
kflag'd tags. The established way a kfunc declares arena pointers is
KF_ARENA_RET / KF_ARENA_ARG1/2 plus the '__arena' parameter-name suffix,
which tools/bpf/resolve_btfids emits as a kflag=1 type attribute
'address_space(1)' -- deliberately not matched here. resolve_btfids'
arena_tag_ptr() also only tags a func proto's return pointer and its
parameters, and errors out with 'arena type is not a pointer' for a struct
return, so KF_ARENA_RET cannot express 'this returned struct's members are
arena pointers'.

So a kfunc can only hit this new path if its declaration carries a raw
__attribute__((btf_type_tag("arena"))) on the member, and no kernel-side
macro exists for that. The follow-up selftest commit 6ab11e51f5443 has to
invent its own '#define __arena_tag
__attribute__((btf_type_tag("arena")))' in
tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h, guarded by
__has_attribute(btf_type_tag), plus a runtime BTF probe
(testmod_has_arena_tagged_member()) that test__skip()s when the build
toolchain did not emit the tag. That is evidence the code is live but also
that no supported kernel-side spelling exists.

Would an in-kernel __arena macro (or teaching resolve_btfids to tag struct
members under KF_ARENA_RET) make the feature usable by real kfuncs and
testable without a skip?


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32899532405

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH bpf-next v2 01/10] bpf: Record each half of a paired return value in verifier diagnostics
  2026-08-25 21:59   ` bot+bpf-ci
@ 2026-08-26 17:08     ` Yonghong Song
  0 siblings, 0 replies; 27+ messages in thread
From: Yonghong Song @ 2026-08-26 17:08 UTC (permalink / raw)
  To: bot+bpf-ci, bpf
  Cc: ast, andrii, daniel, eddyz87, kernel-team, martin.lau, clm,
	ihor.solodrai



On 8/25/26 2:59 PM, bot+bpf-ci@kernel.org wrote:
>> 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,
> This fix looks correct for prepare_func_exit(). Should check_func_call()
> and check_kfunc_call() get the same per-return-register diagnostic scope,
> or is R2 intentionally left out there?
>
> Commit 0630ad00d96d introduced the same pattern in three places, and this
> patch addresses only prepare_func_exit().
>
> In check_func_call(), the global-subprog path has:
>
>      bpf_diag_mod_begin(env, &caller->regs[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);
>      ...
>      nregs = bpf_ret_reg_pair(env, subprog) ? 2 : 1;
>      for (i = 0; i < nregs; i++)
>              mark_reg_unknown(env, caller->regs, ret_regs[i]);
>      bpf_diag_mod_end(env);
>
> When nregs is 2, mark_reg_unknown() writes into caller->regs[BPF_REG_2],
> but the scope target is caller->regs[BPF_REG_0], so the write emits no
> BPF_DIAG_HISTORY_MOD event.
>
> Similarly in check_kfunc_call():
>
>      bpf_diag_mod_begin(env, &regs[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);
>      ...
>      mark_kfunc_ret_regs(env, regs, t->size);   /* writes R0 and, for 8 < size <= 16, R2 */
>      ...
>      bpf_diag_mod_end(env);
>
> When t->size is between 8 and 16 bytes, mark_kfunc_ret_regs() writes the
> second half into R2, but again with no MOD event.
>
> In both cases R2 does get a BPF_DIAG_MOD_CALLER_SAVED scrub record, so
> the history shows R2 as clobbered by the call but never shows it being
> written with the second half of the return value.

Yes, I need to add these two cases for bpf_diag_mod.

>
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32899532405


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH bpf-next v2 03/10] bpf: Add btf_type_is_arena_ptr()
  2026-08-25 21:59   ` bot+bpf-ci
@ 2026-08-26 17:28     ` Yonghong Song
  0 siblings, 0 replies; 27+ messages in thread
From: Yonghong Song @ 2026-08-26 17:28 UTC (permalink / raw)
  To: bot+bpf-ci, bpf
  Cc: ast, andrii, daniel, eddyz87, kernel-team, martin.lau, clm,
	ihor.solodrai



On 8/25/26 2:59 PM, bot+bpf-ci@kernel.org wrote:
>> 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.
> This isn't a bug, but could the changelog use the imperative mood
> consistently (e.g., "Simplify ... and add ...")?  Also, could it explain
> why the helper needs external linkage, such as noting that a later patch
> calls it from verifier.c?
>
>> 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;
>> +}
> This isn't a bug, but since btf_type_tag_walk() sits right above this and
> is still used by btf_find_kptr() and friends, could btf_type_is_arena_ptr()
> be expressed in terms of it rather than repeating the modifier/type-tag
> walk?

Okay, will have more information in commit message.

>
> [ ... ]
>
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32899532405


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH bpf-next v2 04/10] bpf: Let the by-value struct walk take the kinds of member it accepts
  2026-08-25 21:59   ` bot+bpf-ci
@ 2026-08-26 17:39     ` Yonghong Song
  0 siblings, 0 replies; 27+ messages in thread
From: Yonghong Song @ 2026-08-26 17:39 UTC (permalink / raw)
  To: bot+bpf-ci, bpf
  Cc: ast, andrii, daniel, eddyz87, kernel-team, martin.lau, clm,
	ihor.solodrai



On 8/25/26 2:59 PM, bot+bpf-ci@kernel.org wrote:
>> 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 <yonghong.song@linux.dev>
> This isn't a bug, but the changelog only describes the one-line call swap
> in btf.c, while two other parts of the diff are what the "no functional
> change" claim rests on.
>
> The first is a new enumeration in a global header:
>
>> diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
>> +/* 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 */
>> +};
> The second is the array-member branch of the walk losing its trailing
> continue and falling through into the shared kind check:
>
>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>>   			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;
> Equivalence here holds because btf_member_kind_allowed() collapses to
> btf_type_is_scalar() when the mask is BTF_MEMBER_SCALAR alone, and because
> the shared check is the last statement of the for_each_member() body, so no
> member is skipped or checked twice.
>
> Would it help to mention the new enum btf_member_kind in bpf_verifier.h and
> this array-branch fall-through in the changelog, since those are what the
> "no functional change" claim rests on?

New enum btf_member_kind is self explanable in the code.
In the commit message, I will explain array-branch fall-through thing.

>
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32899532405


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH bpf-next v2 05/10] bpf: Report which member makes a kfunc return type unsupported
  2026-08-25 21:59   ` bot+bpf-ci
@ 2026-08-26 17:59     ` Yonghong Song
  0 siblings, 0 replies; 27+ messages in thread
From: Yonghong Song @ 2026-08-26 17:59 UTC (permalink / raw)
  To: bot+bpf-ci, bpf
  Cc: ast, andrii, daniel, eddyz87, kernel-team, martin.lau, clm,
	ihor.solodrai



On 8/25/26 2:59 PM, bot+bpf-ci@kernel.org wrote:
>> 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.
> Does the Reason text shown here match what the verifier actually emits?
>
> The code at verifier.c:14120-14128 uses this format string:
>
>    "%s() returns %s %s by value.%s "
>    "Only kfuncs returning scalar values, or "
>    "structures composed of scalar values are "
>    "supported."
>
> which would produce "... Only kfuncs returning scalar values, or
> structures composed of scalar values are supported." but the quoted
> output says "... Only scalar values, or structs composed of scalar
> values, are supported as by-value kernel function return types."
>
> The first two sentences of the Reason and the Suggestion match what the
> code emits, but the third sentence appears different. Looking at the
> forward range through 6ab11e51f544, commit 37ffe60f491e5 changes it again
> to "... or arena pointers, or structures composed of scalar values and
> arena pointers are supported." but still not the wording above.
>
> Could the changelog be updated with the actual verifier output?

Okay, will update the commit message.

>
>>    ...
>>    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. ...
>> 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
> [ ... ]
>
>> @@ -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)
> All external callers (btf_struct_is_composed_of() and check_kfunc_call())
> pass rec=0. Could the rec parameter be removed from the public API and
> initialized internally?
>
> This was raised by Eduard Zingerman in v1 and acknowledged with "Ack, will
> do" but the parameter remains in v2.

I would like to keep this one. btf_struct_is_composed_of() is a global func
without 'rec' parameger. The recursion is for btf_struct_member_walk(), a
static function.

>
>>   {
>>   	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;
>>   }
> [ ... ]
>
>> @@ -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));
>> +}
> Can this handle anonymous struct/union members correctly?
>
> BTF allows struct members to have name_off == 0 for anonymous members.
> When btf_name_by_offset() is called with name_off == 0, it returns the
> empty string (the start of the string table). The walk descends into
> anonymous struct/union members since btf_type_is_struct() at
> verifier.c:11664 returns true for both BTF_KIND_STRUCT and BTF_KIND_UNION.
>
> For a kfunc like:
>
>    struct ret_anon {
>            union { void *p; __u64 v; };  /* anonymous union */
>            __u64 tag;
>    };
>    __bpf_kfunc struct ret_anon bpf_kfunc_ret_anon(void);
>
> the walk would fail on 'p' at rec == 1, so path->depth == 2 with
> path->member[0] being the unnamed union and path->member[1] being 'p'.
> This would produce the path string ".p" since the first component is empty
> but the separator "." is still emitted.
>
> Would it make sense to skip empty components or substitute something like
> "<anon>" to keep the path readable?

Let us skip empty components. If anything wrong, user will look at
struct itself and can find the member sequence easily.

>
>> +
>>   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, &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.
>>   		 */
>> -		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);
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32899532405


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH bpf-next v2 06/10] bpf: Allow a global function to return arena pointers by value
  2026-08-25 21:12   ` sashiko-bot
@ 2026-08-26 18:40     ` Yonghong Song
  0 siblings, 0 replies; 27+ messages in thread
From: Yonghong Song @ 2026-08-26 18:40 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: bpf



On 8/25/26 2:12 PM, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [High] BPF_PROG_TYPE_EXT (freplace) programs are erroneously rejected when replacing a global function that returns an arena pointer or a struct containing an arena pointer.
> - [High] Callers of global functions returning arena pointers receive a `SCALAR_VALUE` instead of `PTR_TO_ARENA`, causing valid dereferences to be rejected.
> --
>
> commit 9797a7277b67bb03067fdf11db423996a277abbd
> Author: Yonghong Song <yonghong.song@linux.dev>
>
> bpf: Allow a global function to return arena pointers by value
>
> This commit relaxes BTF verification to allow global functions to return
> arena pointers or structures containing arena pointers. It updates the
> verifier's return type validation to recognize these types when processing
> subprograms.
>
>> 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;
> [Severity: High]
> Does this subprog condition inadvertently reject valid BPF_PROG_TYPE_EXT
> (freplace) programs?
>
> When replacing a global function that returns an arena pointer, the
> extension's main program must have an identical BTF signature to the target.
> During verification of the extension's main program (where subprog is 0),
> btf_prepare_func_args() is called. Since subprog evaluates to false,
> BTF_MEMBER_ARENA_PTR isn't added to member_kinds, causing the verifier to
> fall back and return -EOPNOTSUPP.

This analysis is correct. The above 'subprog' is not 0 (not for main prog),
so if it wants to return an arena point, the verifier will reject.

>
> [Severity: High]
> Even though this patch allows global functions to return arena pointers,
> won't callers still be unable to dereference them?
>
> In check_func_call() inside kernel/bpf/verifier.c, the return registers of
> global functions are unconditionally marked as unknown scalars:
>
> 	if (!returns_void) {
> 		nregs = bpf_ret_reg_pair(env, subprog) ? 2 : 1;
> 		for (i = 0; i < nregs; i++)
> 			mark_reg_unknown(env, caller->regs, ret_regs[i]);
> 		bpf_diag_mod_end(env);
> 	}
>
> Since the C type returned by the function (void __arena *) matches the type
> in the caller, the compiler doesn't emit an addr_space_cast instruction.
> Without this cast, the scalar is never promoted back to a PTR_TO_ARENA,
> causing check_mem_access() to reject subsequent dereferences with an "invalid
> mem access 'scalar'" error.

We should be okay here. For example, for returning type is a struct and
one of its member is an arena pointer (tagged with __arena). The compiler
will generate proper address_space conversion code due to __arena.

In kernel side, we will have something like below:

                         if (BPF_CLASS(insn->code) == BPF_ALU64) {
                                 if (insn->imm) {
                                         /* off == BPF_ADDR_SPACE_CAST */
                                         mark_reg_unknown(env, regs, insn->dst_reg);
                                         if (insn->imm == 1) /* cast from as(1) to as(0) */
                                                 dst_reg->type = PTR_TO_ARENA;
   	

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


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH bpf-next v2 07/10] bpf: Allow arena pointers in a by-value kfunc return
  2026-08-25 22:13   ` bot+bpf-ci
@ 2026-08-26 18:57     ` Yonghong Song
  0 siblings, 0 replies; 27+ messages in thread
From: Yonghong Song @ 2026-08-26 18:57 UTC (permalink / raw)
  To: bot+bpf-ci, bpf
  Cc: ast, andrii, daniel, eddyz87, kernel-team, martin.lau, clm,
	ihor.solodrai



On 8/25/26 3:13 PM, bot+bpf-ci@kernel.org wrote:
>> 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)) {
> The rewritten comment drops the mechanism that justified the rule and, as
> written, gives a rationale that contradicts the new behaviour. The
> previous comment was:
>
>    /* 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 new text keeps the 'laundered into a scalar' conclusion but deletes
> the 'comes back as raw register bits modeled as an unknown scalar' premise,
> and then asserts arena pointers are fine. A reader is left with an
> inconsistency, because an arena pointer member is laundered into a scalar
> in exactly the same way -- mark_kfunc_ret_regs() marks both R0 and R2 as
> unknown scalars regardless of member kinds.
>
> The actual reason arena pointers are safe is not stated anywhere in the
> change: a laundered arena address is only usable after
> addr_space_cast/cast_kern, check_alu_op() already lets a program
> cast_kern() any scalar, and cast_kern is lowered to a 32-bit ALU mov so
> the result is confined to the arena window at r12. One clause noting that
> arena pointers need no provenance because they are re-derived by
> cast_kern would restore the invariant explanation.
>
> Also, 'Otherwise, any other pointer' is redundant ('otherwise' plus
> 'any other').

Okay, I will adjust comments then.

>
>> @@ -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);
> In the bpf_diag_program_structure() reason text, 'structures composed of
> scalar values and arena pointers' reads as a conjunction, i.e. that a
> supported struct must contain both scalars and arena pointers. The
> accepted set is a disjunction: BTF_MEMBER_SCALAR | BTF_MEMBER_ARENA_PTR
> accepts any mix, including all-scalar and all-arena-pointer structs. The
> other three strings in the same diagnostic use 'or' consistently, so this
> line is the odd one out. Would 'structures composed of scalar values or
> arena pointers' match?

Sounds good.

>
>> 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)
> [ ... ]
>
> This commit relaxes the by-value kfunc struct return rule from
> BTF_MEMBER_SCALAR to BTF_MEMBER_SCALAR | BTF_MEMBER_ARENA_PTR and updates
> all four user-visible verifier strings accordingly, but
> Documentation/bpf/kfuncs.rst section 2.9 still documents the old contract
> and is now factually wrong in three places:
>
>    - 'A struct or union returned by value must be composed only of scalars
>      (recursively), where a scalar is an integer or an enum'
>    - 'A struct or union with a pointer member is therefore rejected at load
>      time' -- a struct with a 'void __arena *' (btf_type_tag("arena"))
>      member is now accepted
>    - 'A kfunc may also return a value larger than 8 bytes and up to 16
>      bytes -- a scalar-only struct or union, or an __int128'
>    - 'A global subprogram is verified in isolation, so its by-value struct
>      or union return is restricted to scalars just like a kfunc's' -- also
>      invalidated by patch 4 of the same series (ba9c98e6fb9db, 'bpf: Allow
>      a global function to return arena pointers by value')
>
> This paragraph is not boilerplate: it was written specifically to explain
> this restriction (it even paraphrases the code comment that this commit
> deletes), so it is the authoritative reference for the rule being changed.
> Looking at the range 37ffe60f491e..6ab11e51f5443, no commit in the series
> updates Documentation/, so the documentation stays contradictory after the
> whole series is applied.
>
> Could the paragraph be extended to say members may be scalars or arena
> pointers, and explain that an arena pointer member is likewise handed back
> as a scalar which the program must cast_kern() before use?

Okay, will update doc properly.

>
> The new acceptance path is reachable only through an annotation mechanism
> the kfunc ABI does not currently offer. btf_type_is_arena_ptr() matches
> only a kflag=0 BTF_KIND_TYPE_TAG named 'arena' and explicitly skips
> kflag'd tags. The established way a kfunc declares arena pointers is
> KF_ARENA_RET / KF_ARENA_ARG1/2 plus the '__arena' parameter-name suffix,
> which tools/bpf/resolve_btfids emits as a kflag=1 type attribute
> 'address_space(1)' -- deliberately not matched here. resolve_btfids'
> arena_tag_ptr() also only tags a func proto's return pointer and its
> parameters, and errors out with 'arena type is not a pointer' for a struct
> return, so KF_ARENA_RET cannot express 'this returned struct's members are
> arena pointers'.
>
> So a kfunc can only hit this new path if its declaration carries a raw
> __attribute__((btf_type_tag("arena"))) on the member, and no kernel-side
> macro exists for that. The follow-up selftest commit 6ab11e51f5443 has to
> invent its own '#define __arena_tag
> __attribute__((btf_type_tag("arena")))' in
> tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h, guarded by
> __has_attribute(btf_type_tag), plus a runtime BTF probe
> (testmod_has_arena_tagged_member()) that test__skip()s when the build
> toolchain did not emit the tag. That is evidence the code is live but also
> that no supported kernel-side spelling exists.
>
> Would an in-kernel __arena macro (or teaching resolve_btfids to tag struct
> members under KF_ARENA_RET) make the feature usable by real kfuncs and
> testable without a skip?

Good point, let me check this __arena macro is preferred compare to
   __attribute__((btf_type_tag("arena")))

>
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32899532405


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH bpf-next v2 09/10] selftests/bpf: Test global functions returning arena pointers by value
  2026-08-25 21:59   ` bot+bpf-ci
@ 2026-08-27  3:46     ` Yonghong Song
  0 siblings, 0 replies; 27+ messages in thread
From: Yonghong Song @ 2026-08-27  3:46 UTC (permalink / raw)
  To: bot+bpf-ci, bpf
  Cc: ast, andrii, daniel, eddyz87, kernel-team, martin.lau, clm,
	ihor.solodrai



On 8/25/26 2:59 PM, bot+bpf-ci@kernel.org wrote:
>> 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 <linux/bpf.h>
>>   #include <bpf/bpf_helpers.h>
>> +#include <bpf_arena_common.h>
>>   #include "bpf_misc.h"
> [ ... ]
>
>> @@ -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__)
> Should this use the same clang version check as the structurally identical
> block at line 178? That block checks '#if defined(__clang_major__) &&
> __clang_major__ >= 23' for __naked functions returning by-value structs:
>
> aggregate_ret_func.c:178
> #if defined(__clang_major__) && __clang_major__ >= 23
> ...
> __naked struct with_ptr global_ret_struct_ptr(void)
> {
> 	asm volatile (
> 	"r0 = 0;"
> 	"r2 = 0;"
> 	"exit;"
> 	);
> }
>
> SEC("tc")
> __load_if_JITed()
> __failure __msg("has unsupported return type")
> __naked int aggregate_ret_global_struct_ptr_fail(void)
> {
> 	asm volatile (
> 	"call %[global_ret_struct_ptr];"
> 	"r0 = 0;"
> 	"exit;"
> 	:
> 	: __imm(global_ret_struct_ptr)
> 	: __clobber_all);
> }
>
> The verifier_arena.c hunk added in this same commit also uses the '>= 23'
> guard:
>
> verifier_arena.c:737
> #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)
> {
> 	...
> }
>
> By-value struct returns in registers are an LLVM 23 BPF-ABI feature. With
> an older clang, the return is lowered through a hidden pointer (sret), which
> is the gcc behavior described in your new comment.
>
> With clang < 23, the four new '__success __retval(0)' tests would be
> compiled against a different signature than intended. The callers never
> initialize r1, so they would be checked against a prototype that no longer
> matches 'returns struct arena_pair' in the BTF.
>
> Can the two adjacent blocks be consistent about which toolchains can build
> the same construct?

The condition "defined(__clang_major__) && __clang_major__ >= 23"
is needed since returning two registers is only supported at >= llvm23.

Another case is for __clang__ guard. For naked function, clang compilation
will maintain naked asm code. But for gcc, even for naked function, it
still check the return type during lowering and at that point, gcc will
change e.g. 16-byte return type to a sret, which is not what we want.

I will make sure this is explained in commit message.

>
>> +
>> +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";
> [ ... ]
>
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32899532405


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH bpf-next v2 10/10] selftests/bpf: Test kfuncs returning arena pointers by value
  2026-08-25 21:59   ` bot+bpf-ci
@ 2026-08-27  3:58     ` Yonghong Song
  0 siblings, 0 replies; 27+ messages in thread
From: Yonghong Song @ 2026-08-27  3:58 UTC (permalink / raw)
  To: bot+bpf-ci, bpf
  Cc: ast, andrii, daniel, eddyz87, kernel-team, martin.lau, clm,
	ihor.solodrai



On 8/25/26 2:59 PM, bot+bpf-ci@kernel.org wrote:
>> 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 <test_progs.h>
>> +#include <bpf/btf.h>
>>   #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();
> A subsystem pattern flags this as potentially concerning:
> testmod_has_arena_tagged_member() collapses four distinct outcomes
> into the same 'false' return: environment broken (no vmlinux BTF),
> testmod absent or BTF missing, stale bpf_testmod.ko, or the actual
> capability probe (btf_type_tag attribute absent). When test__skip()
> is called without a reason string, the run shows 'aggregate_ret:OK
> (SKIP: 1/N)' with no indication of what was skipped or why.
>
> Since PAHOLE_HAS_BTF_TAG depends on CC_IS_CLANG (lib/Kconfig.debug)
> and GCC does not implement btf_type_tag, the skip branch is the normal
> outcome on a GCC-built kernel, which makes a permanently-skipped test
> easy to miss. The test appears to pass but the new coverage never runs.
>
> The BPF selftests/bpf/prog_tests/btf_tag.c establishes a precedent:
> it checks env.has_testmod, uses ASSERT_OK_PTR() so a BTF-load failure
> is a real FAIL, and prints a reason string before test__skip():
>
>      printf("%s:SKIP: btf_type_tag attribute not in %s", __func__, module_name)
>
> Should testmod_has_arena_tagged_member() distinguish a broken environment
> (ASSERT_OK_PTR on the vmlinux BTF) from a missing capability, and should
> the test__skip() carry a reason string?

It is already very clear for SKIP which is due to btf_type_tag attribute
is not available. The checking for e.g. vmlinux, load_module etc is
normal checking. It is to prevent potential environment issue but does not
mean the environment is broken.

>
>>   }
>> 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 <vmlinux.h>
>> +#include <bpf/bpf_helpers.h>
>> +#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));
>> +}
> A subsystem pattern flags this as potentially concerning: a new BPF
> prog file is added for two test cases, but progs/aggregate_ret_kfunc.c
> in the same directory already covers by-value kfunc returns
> (aggregate_ret_kfunc_precise, _fastcall_fail, _ptr_fail,
> _nested_ptr_fail, _too_deep_fail, _small_no_r2, _too_big_fail). The
> new file duplicates the header includes, the license string, the
> SEC("tc")/__arch_x86_64/__arch_arm64/__load_if_JITed() preamble and
> __kfunc_btf_root() itself, for two subtests.
>
> RUN_TESTS() operates on a whole skeleton (test_progs.h:546 ->
> test_loader__run_subtests(&tester, #skel, skel##__elf_bytes)), and
> these two subtests must be gated on whether the running bpf_testmod's
> BTF carries the 'arena' type tag - a per-toolchain condition that has
> no per-subtest expression in bpf_misc.h (__arch_*/__load_if_JITed
> cover architecture and JIT only). Putting them in
> aggregate_ret_kfunc.c would force the whole existing matrix behind
> the same gate.
>
> Is the separate file justified by the per-skeleton gating requirement,
> or could the new cases be added to aggregate_ret_kfunc.c?

Yes, I would like to be in aggregate_ret_kfunc_arena.c as
we want to test kfunc with arena's.

>
>> +
>> +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);
>> +}
> The changelog claims this patch covers 'two arena pointers filling
> R0:R2', but neither new program ever reads R2. Both bodies are
> 'call <kfunc>; r0 = 0; exit;' with __success __retval(0), which only
> proves that check_kfunc_call() accepted the return type - i.e. that
> btf_struct_member_walk(..., BTF_MEMBER_SCALAR | BTF_MEMBER_ARENA_PTR, ...)
> returned true (kernel/bpf/verifier.c:14097). The number of return
> registers is decided separately by mark_kfunc_ret_regs() -> ret_regs_cnt(size)
> (kernel/bpf/verifier.c:11351, :413); if that path regressed to a
> single register for a 16-byte STRUCT return, both new tests would
> still pass, because R2 is never read.
>
> The file this patch sits beside already establishes the idiom for
> pinning this: aggregate_ret_kfunc_small_no_r2 asserts __failure
> __msg("R2 !read_ok") to prove R2 is *not* a return register for an
> 8-byte struct, and aggregate_ret_kfunc_precise reads 'r6 = r2' after
> the __int128 kfunc to prove R2 *is*.
>
> Should at least one of the arena tests read R2 back to verify the
> two-register return path?

Yes, this is a problem. I will try to come up with better tests.

>
>> +
>> +char _license[] SEC("license") = "GPL";
> [ ... ]
>
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32899532405


^ permalink raw reply	[flat|nested] 27+ messages in thread

end of thread, other threads:[~2026-08-27  3:58 UTC | newest]

Thread overview: 27+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-25 20:54 [PATCH bpf-next v2 00/10] bpf: Allow arena pointers in by-value returns Yonghong Song
2026-08-25 20:54 ` [PATCH bpf-next v2 01/10] bpf: Record each half of a paired return value in verifier diagnostics Yonghong Song
2026-08-25 21:59   ` bot+bpf-ci
2026-08-26 17:08     ` Yonghong Song
2026-08-25 20:54 ` [PATCH bpf-next v2 02/10] bpf: Drop the recursion depth argument of btf_type_is_scalar_struct() Yonghong Song
2026-08-25 20:54 ` [PATCH bpf-next v2 03/10] bpf: Add btf_type_is_arena_ptr() Yonghong Song
2026-08-25 21:59   ` bot+bpf-ci
2026-08-26 17:28     ` Yonghong Song
2026-08-25 20:54 ` [PATCH bpf-next v2 04/10] bpf: Let the by-value struct walk take the kinds of member it accepts Yonghong Song
2026-08-25 21:59   ` bot+bpf-ci
2026-08-26 17:39     ` Yonghong Song
2026-08-25 20:54 ` [PATCH bpf-next v2 05/10] bpf: Report which member makes a kfunc return type unsupported Yonghong Song
2026-08-25 21:59   ` bot+bpf-ci
2026-08-26 17:59     ` Yonghong Song
2026-08-25 20:54 ` [PATCH bpf-next v2 06/10] bpf: Allow a global function to return arena pointers by value Yonghong Song
2026-08-25 21:12   ` sashiko-bot
2026-08-26 18:40     ` Yonghong Song
2026-08-25 20:54 ` [PATCH bpf-next v2 07/10] bpf: Allow arena pointers in a by-value kfunc return Yonghong Song
2026-08-25 22:13   ` bot+bpf-ci
2026-08-26 18:57     ` Yonghong Song
2026-08-25 20:54 ` [PATCH bpf-next v2 08/10] selftests/bpf: Check the member named for an unsupported kfunc return type Yonghong Song
2026-08-25 20:54 ` [PATCH bpf-next v2 09/10] selftests/bpf: Test global functions returning arena pointers by value Yonghong Song
2026-08-25 21:59   ` bot+bpf-ci
2026-08-27  3:46     ` Yonghong Song
2026-08-25 20:55 ` [PATCH bpf-next v2 10/10] selftests/bpf: Test kfuncs " Yonghong Song
2026-08-25 21:59   ` bot+bpf-ci
2026-08-27  3:58     ` Yonghong Song

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox