BPF List
 help / color / mirror / Atom feed
* [PATCH bpf-next v3 00/11] bpf: Allow arena pointers in by-value returns
@ 2026-08-27  6:11 Yonghong Song
  2026-08-27  6:11 ` [PATCH bpf-next v3 01/11] bpf: Record each half of a paired return value in verifier diagnostics Yonghong Song
                   ` (10 more replies)
  0 siblings, 11 replies; 26+ messages in thread
From: Yonghong Song @ 2026-08-27  6:11 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. Patch 11 updates the kfunc's doc.

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.
  v2 -> v3:
    - v2: https://lore.kernel.org/bpf/20260825205412.1320099-1-yonghong.song@linux.dev/
    - Fix two more verifier diagnostics.
    - Fix btf_member_path_str() by detecting anonymous union/struct.
    - Fix a few arena tests with non-zero arena pointers.
    - Update kfuncs doc for supporting return value with arena pointers.

Yonghong Song (11):
  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
  docs/bpf: Document arena pointers in a by-value return

 Documentation/bpf/kfuncs.rst                  |  39 ++--
 include/linux/bpf_verifier.h                  |  11 +-
 include/linux/btf.h                           |   1 +
 kernel/bpf/btf.c                              |  82 ++++----
 kernel/bpf/verifier.c                         | 195 ++++++++++++++----
 .../selftests/bpf/prog_tests/aggregate_ret.c  |  42 ++++
 .../selftests/bpf/progs/aggregate_ret_func.c  | 146 +++++++++++++
 .../selftests/bpf/progs/aggregate_ret_kfunc.c |  36 +++-
 .../bpf/progs/aggregate_ret_kfunc_arena.c     | 129 ++++++++++++
 .../selftests/bpf/progs/exceptions_fail.c     |   2 +-
 .../selftests/bpf/progs/verifier_arena.c      |  48 +++++
 .../selftests/bpf/test_kmods/bpf_testmod.c    |  48 +++++
 .../bpf/test_kmods/bpf_testmod_kfunc.h        |  51 +++++
 13 files changed, 729 insertions(+), 101 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/progs/aggregate_ret_kfunc_arena.c

-- 
2.53.0-Meta


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

* [PATCH bpf-next v3 01/11] bpf: Record each half of a paired return value in verifier diagnostics
  2026-08-27  6:11 [PATCH bpf-next v3 00/11] bpf: Allow arena pointers in by-value returns Yonghong Song
@ 2026-08-27  6:11 ` Yonghong Song
  2026-08-27  6:11 ` [PATCH bpf-next v3 02/11] bpf: Drop the recursion depth argument of btf_type_is_scalar_struct() Yonghong Song
                   ` (9 subsequent siblings)
  10 siblings, 0 replies; 26+ messages in thread
From: Yonghong Song @ 2026-08-27  6:11 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. There are three places, check_func_call(), prepare_func_exit() and
check_kfunc_call(), where only R0 is tracked by the diagnostic
modification scope and R2 is missed.

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")
Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
 kernel/bpf/verifier.c | 56 ++++++++++++++++++++++++++++++++-----------
 1 file changed, 42 insertions(+), 14 deletions(-)

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index e036ae20bf6b..5d8162e13c20 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -9995,9 +9995,14 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 		 */
 		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]);
+			mark_reg_unknown(env, caller->regs, ret_regs[0]);
 			bpf_diag_mod_end(env);
+			for (i = 1; i < nregs; i++) {
+				bpf_diag_mod_begin(env, &caller->regs[ret_regs[i]], NULL,
+						   BPF_DIAG_MOD_WRITE);
+				mark_reg_unknown(env, caller->regs, ret_regs[i]);
+				bpf_diag_mod_end(env);
+			}
 		}
 
 		if (env->subprog_info[subprog].might_throw) {
@@ -10403,10 +10408,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,
@@ -11342,15 +11351,33 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
 
 /*
  * Mark the register(s) holding a @size byte kfunc return value as unknown
- * scalars. Both halves of a register pair are treated the same way.
+ * scalars and return how many registers that took. Both halves of a register
+ * pair are treated the same way.
+ *
+ * R0 is written in the diagnostic modification scope the caller opened before
+ * the caller-saved scrub, since R0 gets no scrub record of its own and that
+ * scope is what carries its pre-call value into the history. A scope tracks a
+ * single register and does not nest, so close it before recording the rest of
+ * the return value. A value that fits in R0 leaves the caller's scope open for
+ * the caller to close, so a later write to R0 still lands in it.
  */
-static void mark_kfunc_ret_regs(struct bpf_verifier_env *env,
-				struct bpf_reg_state *regs, u32 size)
+static u32 mark_kfunc_ret_regs(struct bpf_verifier_env *env,
+			       struct bpf_reg_state *regs, u32 size)
 {
 	u32 i, nregs = ret_regs_cnt(size);
 
-	for (i = 0; i < nregs; i++)
+	mark_reg_unknown(env, regs, ret_regs[0]);
+	if (nregs == 1)
+		return nregs;
+
+	bpf_diag_mod_end(env);
+	for (i = 1; i < nregs; i++) {
+		bpf_diag_mod_begin(env, &regs[ret_regs[i]], NULL, BPF_DIAG_MOD_WRITE);
 		mark_reg_unknown(env, regs, ret_regs[i]);
+		bpf_diag_mod_end(env);
+	}
+
+	return nregs;
 }
 
 static bool is_kfunc_acquire(struct bpf_call_arg_meta *meta)
@@ -13767,7 +13794,7 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 	struct bpf_insn_aux_data *insn_aux;
 	const char *operation;
 	int err, insn_idx = *insn_idx_p;
-	u32 i, nargs, ptr_type_id;
+	u32 i, nargs, ptr_type_id, ret_nregs = 1;
 	struct bpf_kfunc_desc *desc;
 	struct btf *desc_btf;
 	int id;
@@ -14021,7 +14048,7 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 	}
 
 	if (btf_type_is_scalar(t)) {
-		mark_kfunc_ret_regs(env, regs, t->size);
+		ret_nregs = mark_kfunc_ret_regs(env, regs, t->size);
 		if (meta.btf == btf_vmlinux && (meta.func_id == special_kfunc_list[KF_bpf_res_spin_lock] ||
 		    meta.func_id == special_kfunc_list[KF_bpf_res_spin_lock_irqsave]))
 			__mark_reg_const_zero(env, &regs[BPF_REG_0]);
@@ -14039,7 +14066,7 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 				btf_name_by_offset(desc_btf, t->name_off));
 			return -EINVAL;
 		}
-		mark_kfunc_ret_regs(env, regs, t->size);
+		ret_nregs = mark_kfunc_ret_regs(env, regs, t->size);
 	} else if (btf_type_is_ptr(t)) {
 		ptr_type = btf_type_skip_modifiers(desc_btf, t->type, &ptr_type_id);
 		err = check_special_kfunc(env, &meta, regs, insn_aux, ptr_type, desc_btf);
@@ -14170,7 +14197,8 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 	 * Record R0 before process_iter_next_call() snapshots the alternate
 	 * iterator path's diagnostic position.
 	 */
-	bpf_diag_mod_end(env);
+	if (ret_nregs == 1)
+		bpf_diag_mod_end(env);
 
 	if (bpf_is_iter_next_kfunc(&meta)) {
 		err = process_iter_next_call(env, insn_idx, &meta);
-- 
2.53.0-Meta


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

* [PATCH bpf-next v3 02/11] bpf: Drop the recursion depth argument of btf_type_is_scalar_struct()
  2026-08-27  6:11 [PATCH bpf-next v3 00/11] bpf: Allow arena pointers in by-value returns Yonghong Song
  2026-08-27  6:11 ` [PATCH bpf-next v3 01/11] bpf: Record each half of a paired return value in verifier diagnostics Yonghong Song
@ 2026-08-27  6:11 ` Yonghong Song
  2026-08-27  7:04   ` bot+bpf-ci
  2026-08-27  6:11 ` [PATCH bpf-next v3 03/11] bpf: Add btf_type_is_arena_ptr() Yonghong Song
                   ` (8 subsequent siblings)
  10 siblings, 1 reply; 26+ messages in thread
From: Yonghong Song @ 2026-08-27  6:11 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 5d8162e13c20..bc3053e81500 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -11647,9 +11647,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;
@@ -11667,7 +11666,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;
 		}
@@ -11686,6 +11685,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,
@@ -12066,7 +12072,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;
@@ -12082,7 +12088,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;
@@ -13138,7 +13144,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;
 
@@ -13680,7 +13686,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;
 			}
@@ -14059,7 +14065,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] 26+ messages in thread

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

Add btf_type_is_arena_ptr() to test whether a BTF type is a pointer
carrying the "arena" type tag, and use it to simplify both
btf_scan_type_tags() and btf_validate_return_type(). The helper has
external linkage because a later patch calls it from verifier.c to
decide whether a struct member returned by value may be an arena
pointer.

btf_scan_type_tags() drove btf_type_tag_walk() with a one-entry match
table, so the "multiple type tags" error it checked for could never
fire: btf_type_tag_walk() only fails when the walk sets more than one
flag. Dropping that error path, and with it the int return, is
therefore 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] 26+ messages in thread

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

btf_scalar_struct_walk() hardcodes that every member of a struct returned
by value must be a scalar. A later patch needs the same walk to also
accept arena pointers, so give it a mask of the member kinds it allows.

There is no functional change: btf_member_kind_allowed() is exactly
btf_type_is_scalar() when the mask is BTF_MEMBER_SCALAR alone, and the
shared check is the last statement of the loop body, so dropping the array
branch's continue neither skips a member nor checks one twice.

Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
 include/linux/bpf_verifier.h | 11 ++++++++--
 kernel/bpf/btf.c             |  2 +-
 kernel/bpf/verifier.c        | 40 ++++++++++++++++++++++++++----------
 3 files changed, 39 insertions(+), 14 deletions(-)

diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 3eb61edc8c5e..ae9f606539f4 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 */
+	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 bc3053e81500..90139f1b78d1 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -11646,9 +11646,23 @@ 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. An array member counts as its
+ * element type.
+ */
+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;
@@ -11666,7 +11680,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;
 		}
@@ -11675,21 +11689,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] 26+ messages in thread

* [PATCH bpf-next v3 05/11] bpf: Report which member makes a kfunc return type unsupported
  2026-08-27  6:11 [PATCH bpf-next v3 00/11] bpf: Allow arena pointers in by-value returns Yonghong Song
                   ` (3 preceding siblings ...)
  2026-08-27  6:11 ` [PATCH bpf-next v3 04/11] bpf: Let the by-value struct walk take the kinds of member it accepts Yonghong Song
@ 2026-08-27  6:11 ` Yonghong Song
  2026-08-27  7:04   ` bot+bpf-ci
  2026-08-27  6:11 ` [PATCH bpf-next v3 06/11] bpf: Allow a global function to return arena pointers by value Yonghong Song
                   ` (5 subsequent siblings)
  10 siblings, 1 reply; 26+ messages in thread
From: Yonghong Song @ 2026-08-27  6:11 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 kfuncs returning
    scalar values, or structures composed of scalar values are
    supported.
  ...
  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, 76 insertions(+), 10 deletions(-)

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 90139f1b78d1..967ad010b322 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -11646,6 +11646,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)
 {
@@ -11658,11 +11667,12 @@ 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. An array member counts as its
- * element type.
+ * @member_kinds, BTF_MEMBER_MAX_DEPTH levels of nesting allowed. An array
+ * member counts as its element type.
  */
 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;
@@ -11676,31 +11686,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,
@@ -11710,6 +11731,22 @@ 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++) {
+		const char *name = btf_name_by_offset(btf, path->member[i]->name_off);
+
+		if (!name || !name[0])
+			continue;
+		len += scnprintf(buf + len, buf_sz - len, "%s%s", len ? "." : "", name);
+	}
+}
+
 enum kfunc_ptr_arg_type {
 	KF_ARG_CONST_MEM_SIZE,
 	KF_ARG_MEM_SIZE,
@@ -14077,17 +14114,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)) {
+		if (!btf_struct_member_walk(env, desc_btf, t, BTF_MEMBER_SCALAR, 0, &path)) {
 			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));
+			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;
+
+				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), member_note);
 			return -EINVAL;
 		}
 		ret_nregs = mark_kfunc_ret_regs(env, regs, t->size);
-- 
2.53.0-Meta


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

* [PATCH bpf-next v3 06/11] bpf: Allow a global function to return arena pointers by value
  2026-08-27  6:11 [PATCH bpf-next v3 00/11] bpf: Allow arena pointers in by-value returns Yonghong Song
                   ` (4 preceding siblings ...)
  2026-08-27  6:11 ` [PATCH bpf-next v3 05/11] bpf: Report which member makes a kfunc return type unsupported Yonghong Song
@ 2026-08-27  6:11 ` Yonghong Song
  2026-08-27  6:33   ` sashiko-bot
  2026-08-27  6:11 ` [PATCH bpf-next v3 07/11] bpf: Allow arena pointers in a by-value kfunc return Yonghong Song
                   ` (4 subsequent siblings)
  10 siblings, 1 reply; 26+ messages in thread
From: Yonghong Song @ 2026-08-27  6:11 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                              | 20 ++++++++++++++-----
 .../selftests/bpf/progs/exceptions_fail.c     |  2 +-
 2 files changed, 16 insertions(+), 6 deletions(-)

diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index b1f4ef614d4c..f6d82a8fd617 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -7973,13 +7973,22 @@ 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.
+		 * scalar pair, so a pointer member would be laundered into a
+		 * scalar and escape provenance and reference tracking. Only
+		 * scalars and arena pointers are allowed: an arena pointer has
+		 * no provenance to lose, since a program may already derive one
+		 * from any scalar with addr_space_cast(), which confines the
+		 * result to the arena. 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 +8084,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 "
+				"scalars and arena pointers 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..cde4982e9989 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 scalars")
 int reject_exception_cb_type_1(struct __sk_buff *ctx)
 {
 	bpf_throw(0);
-- 
2.53.0-Meta


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

* [PATCH bpf-next v3 07/11] bpf: Allow arena pointers in a by-value kfunc return
  2026-08-27  6:11 [PATCH bpf-next v3 00/11] bpf: Allow arena pointers in by-value returns Yonghong Song
                   ` (5 preceding siblings ...)
  2026-08-27  6:11 ` [PATCH bpf-next v3 06/11] bpf: Allow a global function to return arena pointers by value Yonghong Song
@ 2026-08-27  6:11 ` Yonghong Song
  2026-08-27  6:55   ` sashiko-bot
  2026-08-27  6:11 ` [PATCH bpf-next v3 08/11] selftests/bpf: Check the member named for an unsupported kfunc return type Yonghong Song
                   ` (3 subsequent siblings)
  10 siblings, 1 reply; 26+ messages in thread
From: Yonghong Song @ 2026-08-27  6:11 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.

Such a member carries the arena type tag but not the address space
qualifier, so the program has to cast it itself. Handing it back as a
scalar costs nothing: an arena address has no provenance to track, and the
cast is confined to the caller's arena.

Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
 kernel/bpf/verifier.c                         | 25 +++++++++++--------
 .../selftests/bpf/progs/aggregate_ret_kfunc.c |  2 +-
 2 files changed, 16 insertions(+), 11 deletions(-)

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 967ad010b322..9a36808136c9 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -14119,13 +14119,17 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 
 		/*
 		 * 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.
+		 * as an unknown scalar, so a pointer member would be laundered
+		 * into a scalar and escape provenance and reference tracking.
+		 * Only scalars and arena pointers are allowed: an arena pointer
+		 * has no provenance to lose, since a program may already derive
+		 * one from any scalar with addr_space_cast(), which confines the
+		 * result to the arena.
 		 */
-		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)) {
 			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));
 			if (path.too_deep) {
@@ -14142,15 +14146,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 or 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] 26+ messages in thread

* [PATCH bpf-next v3 08/11] selftests/bpf: Check the member named for an unsupported kfunc return type
  2026-08-27  6:11 [PATCH bpf-next v3 00/11] bpf: Allow arena pointers in by-value returns Yonghong Song
                   ` (6 preceding siblings ...)
  2026-08-27  6:11 ` [PATCH bpf-next v3 07/11] bpf: Allow arena pointers in a by-value kfunc return Yonghong Song
@ 2026-08-27  6:11 ` Yonghong Song
  2026-08-27  7:04   ` bot+bpf-ci
  2026-08-27  6:12 ` [PATCH bpf-next v3 09/11] selftests/bpf: Test global functions returning arena pointers by value Yonghong Song
                   ` (2 subsequent siblings)
  10 siblings, 1 reply; 26+ messages in thread
From: Yonghong Song @ 2026-08-27  6:11 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] 26+ messages in thread

* [PATCH bpf-next v3 09/11] selftests/bpf: Test global functions returning arena pointers by value
  2026-08-27  6:11 [PATCH bpf-next v3 00/11] bpf: Allow arena pointers in by-value returns Yonghong Song
                   ` (7 preceding siblings ...)
  2026-08-27  6:11 ` [PATCH bpf-next v3 08/11] selftests/bpf: Check the member named for an unsupported kfunc return type Yonghong Song
@ 2026-08-27  6:12 ` Yonghong Song
  2026-08-27  7:04   ` bot+bpf-ci
  2026-08-27  6:12 ` [PATCH bpf-next v3 10/11] selftests/bpf: Test kfuncs " Yonghong Song
  2026-08-27  6:12 ` [PATCH bpf-next v3 11/11] docs/bpf: Document arena pointers in a by-value return Yonghong Song
  10 siblings, 1 reply; 26+ messages in thread
From: Yonghong Song @ 2026-08-27  6:12 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.

check_arena_struct_ret() also stores through both halves of the returned
pair and reads them back, so the test covers the returned pointers still
being usable as arena pointers rather than only the program verifying.

A toolchain guard goes into each file, and they differ because the
constructs do.

aggregate_ret_func.c uses "#if defined(__clang__)". Those cases are
__naked, so the function body is the inline asm and nothing else: the
clang compiler never lowers the return, and the 16 byte return type
reaches the verifier only through BTF. The minimum checked is LLVM 21.
gcc is excluded because it 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.

verifier_arena.c uses "#if defined(__clang_major__) && __clang_major__ >=
23". split_arena_page() returns the struct from C, so the compiler lowers
the return itself, and a 16 byte return only lands in R0:R2 with the
LLVM 23 BPF ABI. An older clang does not fall back to anything here, it
rejects the function with "aggregate returns are not supported", so the
floor is what lets the file build at all.

Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
 .../selftests/bpf/progs/aggregate_ret_func.c  | 146 ++++++++++++++++++
 .../selftests/bpf/progs/verifier_arena.c      |  48 ++++++
 2 files changed, 194 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..f0077d951a25 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,149 @@ __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;
+};
+
+union arena_upair {
+	void __arena *p;
+	__u64 halves[2];
+};
+
+__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);
+}
+
+__naked union arena_upair global_ret_arena_union(void)
+{
+	asm volatile (
+	"r0 = 0;"
+	"r2 = 0;"
+	"exit;"
+	);
+}
+
+SEC("tc")
+__load_if_JITed()
+__success __retval(0)
+__naked int aggregate_ret_global_arena_union(void)
+{
+	asm volatile (
+	"call %[global_ret_arena_union];"
+	"r0 = 0;"
+	"exit;"
+	:
+	: __imm(global_ret_arena_union)
+	: __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..672a3aadd9cb 100644
--- a/tools/testing/selftests/bpf/progs/verifier_arena.c
+++ b/tools/testing/selftests/bpf/progs/verifier_arena.c
@@ -734,4 +734,52 @@ 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);
+	u32 volatile __arena *first, *second;
+	struct arena_page_pair pair;
+
+	if (!page)
+		return 1;
+
+	pair = split_arena_page(page);
+	if (!pair.first || !pair.second)
+		return 2;
+
+	/* Both halves of the pair must still be usable as arena pointers. */
+	first = pair.first;
+	second = pair.second;
+	*first = 1;
+	*second = 2;
+	if (*first != 1)
+		return 3;
+	if (*second != 2)
+		return 4;
+
+	return 0;
+}
+
+#endif
+
 char _license[] SEC("license") = "GPL";
-- 
2.53.0-Meta


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

* [PATCH bpf-next v3 10/11] selftests/bpf: Test kfuncs returning arena pointers by value
  2026-08-27  6:11 [PATCH bpf-next v3 00/11] bpf: Allow arena pointers in by-value returns Yonghong Song
                   ` (8 preceding siblings ...)
  2026-08-27  6:12 ` [PATCH bpf-next v3 09/11] selftests/bpf: Test global functions returning arena pointers by value Yonghong Song
@ 2026-08-27  6:12 ` Yonghong Song
  2026-08-27  7:17   ` bot+bpf-ci
  2026-08-27  6:12 ` [PATCH bpf-next v3 11/11] docs/bpf: Document arena pointers in a by-value return Yonghong Song
  10 siblings, 1 reply; 26+ messages in thread
From: Yonghong Song @ 2026-08-27  6:12 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. Two further
cases drop the tag from one member of a struct and one arm of a union,
and stay rejected naming that member, so what decides is the tag rather
than the member being a pointer. The existing cases for a struct and a
nested struct carrying a plain pointer stay rejected as well.

These cases call the kfuncs from C, so the compiler lowers the by-value
return itself, and a struct or union only lands in R0:R2 with the LLVM 23
BPF ABI. An older clang, and gcc, return it through a hidden pointer in
R1 instead, which shifts the arguments along and fails verification. The
file is therefore guarded on LLVM 23, falling back to a dummy test.

Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
 .../selftests/bpf/prog_tests/aggregate_ret.c  |  42 ++++++
 .../bpf/progs/aggregate_ret_kfunc_arena.c     | 129 ++++++++++++++++++
 .../selftests/bpf/test_kmods/bpf_testmod.c    |  32 +++++
 .../bpf/test_kmods/bpf_testmod_kfunc.h        |  30 ++++
 4 files changed, 233 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..94c35e1b547c
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/aggregate_ret_kfunc_arena.c
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf_arena_common.h>
+#include "bpf_misc.h"
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+#if defined(__clang_major__) && __clang_major__ >= 23
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARENA);
+	__uint(map_flags, BPF_F_MMAPABLE);
+	__uint(max_entries, 2);
+} arena SEC(".maps");
+
+/*
+ * A returned member carries the arena type tag but not the address space
+ * qualifier, so the program casts it into the arena address space itself
+ * rather than the compiler doing it.
+ */
+#define arena_ptr(p) ((u32 volatile __arena *)(p))
+
+SEC("syscall")
+__arch_x86_64 __arch_arm64
+__load_if_JITed()
+__success __retval(0)
+int aggregate_ret_kfunc_arena(void *ctx)
+{
+	u32 volatile __arena *page = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+	u32 volatile __arena *a, *b;
+	struct prog_test_ret_arena r;
+
+	if (!page)
+		return 1;
+
+	/* Both halves come back in R0:R2, pointing at page and page + 4. */
+	r = bpf_kfunc_call_test_ret_arena((u64)page);
+	if (!r.a || !r.b)
+		return 2;
+
+	a = arena_ptr(r.a);
+	b = arena_ptr(r.b);
+	*a = 1;
+	*b = 2;
+	if (*a != 1)
+		return 3;
+	if (*b != 2)
+		return 4;
+
+	/* The halves are the first two slots of the page the program allocated. */
+	page[0] = 7;
+	if (*a != 7)
+		return 5;
+	page[1] = 9;
+	if (*b != 9)
+		return 6;
+
+	return 0;
+}
+
+SEC("syscall")
+__arch_x86_64 __arch_arm64
+__load_if_JITed()
+__success __retval(0)
+int aggregate_ret_kfunc_arena_mixed(void *ctx)
+{
+	u32 __arena *page = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+	struct prog_test_ret_arena_mixed r;
+	u32 volatile __arena *p;
+
+	if (!page)
+		return 1;
+
+	/* An arena pointer in R0 beside a scalar in R2. */
+	r = bpf_kfunc_call_test_ret_arena_mixed((u64)page);
+	if (!r.p)
+		return 2;
+	if (r.tag != 0xbeef)
+		return 3;
+
+	p = arena_ptr(r.p);
+	*p = 3;
+	if (*p != 3)
+		return 4;
+
+	return 0;
+}
+
+SEC("syscall")
+__arch_x86_64 __arch_arm64
+__failure __msg("is not composed of scalars or arena pointers")
+__msg("member 'b' has type PTR")
+int aggregate_ret_kfunc_arena_untagged_fail(void *ctx)
+{
+	struct prog_test_ret_arena_untagged r;
+
+	r = bpf_kfunc_call_test_ret_arena_untagged(0);
+
+	return r.a == r.b;
+}
+
+SEC("syscall")
+__arch_x86_64 __arch_arm64
+__failure __msg("is not composed of scalars or arena pointers")
+__msg("member 'b' has type PTR")
+int aggregate_ret_kfunc_arena_union_fail(void *ctx)
+{
+	union prog_test_ret_arena_union r;
+
+	r = bpf_kfunc_call_test_ret_arena_union(0);
+
+	return r.a == r.b;
+}
+
+#else
+
+SEC("socket")
+__description("aggregate_ret_kfunc_arena: needs LLVM 23, dummy test")
+__skip("needs LLVM 23")
+__success
+int dummy_test(void)
+{
+	return 0;
+}
+
+#endif
+
+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..0ef2ce875d71 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -981,6 +981,34 @@ __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(u64 addr)
+{
+	struct prog_test_ret_arena r = { .a = (void *)addr, .b = (void *)(addr + 4) };
+
+	return r;
+}
+
+__bpf_kfunc struct prog_test_ret_arena_mixed bpf_kfunc_call_test_ret_arena_mixed(u64 addr)
+{
+	struct prog_test_ret_arena_mixed r = { .p = (void *)addr, .tag = 0xbeef };
+
+	return r;
+}
+
+__bpf_kfunc struct prog_test_ret_arena_untagged bpf_kfunc_call_test_ret_arena_untagged(u64 addr)
+{
+	struct prog_test_ret_arena_untagged r = { .a = (void *)addr, .b = NULL };
+
+	return r;
+}
+
+__bpf_kfunc union prog_test_ret_arena_union bpf_kfunc_call_test_ret_arena_union(u64 addr)
+{
+	union prog_test_ret_arena_union r = { .a = (void *)addr };
+
+	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 +1581,10 @@ 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_arena_untagged)
+BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_arena_union)
 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..a2e9e9f3184e 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,26 @@ 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_arena_untagged {	/* 16 bytes: 'b' lacks the arena tag */
+	void __arena_tag *a;
+	void *b;
+};
+
+union prog_test_ret_arena_union {	/* 8 bytes: 'b' lacks the arena tag */
+	void __arena_tag *a;
+	void *b;
+};
+
 struct prog_test_ret_nested {	/* 16 bytes: the pointer hides one level down */
 	struct {
 		void *p;
@@ -179,6 +205,10 @@ 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(__u64 addr) __ksym;
+struct prog_test_ret_arena_mixed bpf_kfunc_call_test_ret_arena_mixed(__u64 addr) __ksym;
+struct prog_test_ret_arena_untagged bpf_kfunc_call_test_ret_arena_untagged(__u64 addr) __ksym;
+union prog_test_ret_arena_union bpf_kfunc_call_test_ret_arena_union(__u64 addr) __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] 26+ messages in thread

* [PATCH bpf-next v3 11/11] docs/bpf: Document arena pointers in a by-value return
  2026-08-27  6:11 [PATCH bpf-next v3 00/11] bpf: Allow arena pointers in by-value returns Yonghong Song
                   ` (9 preceding siblings ...)
  2026-08-27  6:12 ` [PATCH bpf-next v3 10/11] selftests/bpf: Test kfuncs " Yonghong Song
@ 2026-08-27  6:12 ` Yonghong Song
  10 siblings, 0 replies; 26+ messages in thread
From: Yonghong Song @ 2026-08-27  6:12 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, kernel-team

Section 2.9 describes the by-value return contract as scalars only, which
no longer holds: a kfunc and a global subprogram may now return a struct
or union whose members are scalars or arena pointers. Update it.

Note that an arena pointer member is handed back as a scalar like every
other member. That costs nothing: the program has to cast_kern() the value
before it can be used, and the verifier allows that cast on any scalar, so
the member gives the program no reach it did not already have.

Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
 Documentation/bpf/kfuncs.rst | 39 +++++++++++++++++++++---------------
 1 file changed, 23 insertions(+), 16 deletions(-)

diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst
index 89dea6b0b024..96bf63d34432 100644
--- a/Documentation/bpf/kfuncs.rst
+++ b/Documentation/bpf/kfuncs.rst
@@ -581,20 +581,27 @@ against the arena. Larger accesses must verify the range explicitly.
 A kfunc may return a scalar, a pointer, or a small struct or union by
 value. A scalar or pointer of up to 8 bytes is returned in R0, as usual.
 
-A struct or union returned by value must be composed only of scalars
-(recursively), where a scalar is an integer or an enum; arrays of scalars are
-allowed as members. Its bytes are handed back to the program as the raw
-contents of R0 (and R2), so a pointer field would be laundered into a scalar
-and escape the verifier's pointer provenance and reference tracking. A struct
-or union with a pointer member is therefore rejected at load time, and so is
-one with a floating-point member, which the ABI may not return in R0:R2 at
-all.
+A struct or union returned by value must be composed only of scalars and arena
+pointers (recursively), where a scalar is an integer or an enum and an arena
+pointer is one carrying the ``btf_type_tag("arena")`` attribute; arrays of
+either are allowed as members. Its bytes are handed back to the program as the
+raw contents of R0 (and R2), so a member of any other pointer type would be
+laundered into a scalar and escape the verifier's pointer provenance and
+reference tracking. A struct or union with such a member is therefore rejected
+at load time, and so is one with a floating-point member, which the ABI may not
+return in R0:R2 at all.
+
+An arena pointer member is handed back as a scalar too, but nothing is lost by
+that. The program must ``cast_kern()`` the value before it can be used, and the
+verifier allows that cast on any scalar, so a laundered arena address gives the
+program no reach it did not already have. The result is confined to the
+program's arena in either case.
 
 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``. Such a value is returned
-in the register pair R0:R2, matching the convention LLVM uses for the BPF
-target: the first 8 bytes in R0 and the second 8 bytes in R2. A struct or
-union of 8 bytes or less is returned in R0 alone.
+struct or union of scalars and arena pointers, or an ``__int128``. Such a
+value is returned in the register pair R0:R2, matching the convention LLVM
+uses for the BPF target: the first 8 bytes in R0 and the second 8 bytes in R2.
+A struct or union of 8 bytes or less is returned in R0 alone.
 
 ::
 
@@ -620,10 +627,10 @@ used when the program is JITed, since the interpreter propagates only R0 out of
 a subprogram: without a JIT the return value stays in R0 alone, and a caller
 reading R2 is rejected for reading an uninitialized register. A global
 subprogram is verified in isolation, so its by-value struct or union return is
-restricted to scalars just like a kfunc's; a static subprogram is verified
-inline and has no such restriction. The main program is not covered: its return
-value is the program's exit code, read out of R0 alone, so a declared upper
-half is never looked at.
+restricted to scalars and arena pointers just like a kfunc's; a static
+subprogram is verified inline and has no such restriction. The main program is
+not covered: its return value is the program's exit code, read out of R0
+alone, so a declared upper half is never looked at.
 
 A global subprogram must leave a scalar in *every* register of the pair, so
 both halves of the returned value have to be assigned. Leaving the upper half
-- 
2.53.0-Meta


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

* Re: [PATCH bpf-next v3 06/11] bpf: Allow a global function to return arena pointers by value
  2026-08-27  6:11 ` [PATCH bpf-next v3 06/11] bpf: Allow a global function to return arena pointers by value Yonghong Song
@ 2026-08-27  6:33   ` sashiko-bot
  2026-08-28 18:00     ` Yonghong Song
  0 siblings, 1 reply; 26+ messages in thread
From: sashiko-bot @ 2026-08-27  6:33 UTC (permalink / raw)
  To: Yonghong Song; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The verifier error message for unsupported return types incorrectly claims that arena pointers are supported for the main BPF program, when they are actually rejected.
--

commit 518fd93d71684c317540966a4f0aa467d524c25b
Author: Yonghong Song <yonghong.song@linux.dev>

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

This patch allows global functions to return a struct by value that contains
arena pointers. It updates btf_validate_return_type() to allow arena pointers
and adjusts the corresponding error message in btf_prepare_func_args().

> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index b1f4ef614d4c4..f6d82a8fd617b 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c
> @@ -7973,13 +7973,22 @@ 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
[ ... ]
> +		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 +8084,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 "
> +				"scalars and arena pointers up to 16 bytes is supported.\n",
>  				tname);
>  		}
>  		return err;

[Severity: Low]
Does this new error message in btf_prepare_func_args() incorrectly claim that
arena pointers are supported for the main BPF program?

In btf_validate_return_type(), arena pointers are only added to member_kinds
if subprog is non-zero:

    if (subprog)
        member_kinds |= BTF_MEMBER_ARENA_PTR;

However, if a user attempts to load a main BPF program (subprog == 0) with a
global linkage (like BPF_PROG_TYPE_EXT) that returns a struct containing an
arena pointer, it will be rejected by btf_validate_return_type() but then print
this error message incorrectly implying that arena pointers are supported.

Should the error message be adjusted, or should the subprog check be changed so
they match?

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

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

* Re: [PATCH bpf-next v3 07/11] bpf: Allow arena pointers in a by-value kfunc return
  2026-08-27  6:11 ` [PATCH bpf-next v3 07/11] bpf: Allow arena pointers in a by-value kfunc return Yonghong Song
@ 2026-08-27  6:55   ` sashiko-bot
  2026-08-28 18:10     ` Yonghong Song
  0 siblings, 1 reply; 26+ messages in thread
From: sashiko-bot @ 2026-08-27  6:55 UTC (permalink / raw)
  To: Yonghong Song; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] Kfuncs returning structs by value can leak uninitialized kernel stack memory via struct padding.
--

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

bpf: Allow arena pointers in a by-value kfunc return

This commit allows by-value struct returns from kfuncs to contain arena
pointers, in addition to scalars. An arena pointer has no provenance to lose
and can be cast by the program, so returning it as a scalar is safe.

> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 967ad010b3222..9a36808136c9a 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -14119,13 +14119,17 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
>  
>  		/*
>  		 * 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.
> +		 * as an unknown scalar, so a pointer member would be laundered
> +		 * into a scalar and escape provenance and reference tracking.
> +		 * Only scalars and arena pointers are allowed: an arena pointer
> +		 * has no provenance to lose, since a program may already derive
> +		 * one from any scalar with addr_space_cast(), which confines the
> +		 * result to the arena.
>  		 */
> -		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)) {

[Severity: High]
This is a pre-existing issue, but does returning structs by value from kfuncs
leak uninitialized kernel stack memory via struct padding?

When a BPF program calls a kfunc that returns a struct by value (up to 16
bytes) containing padding bytes, the C compiler leaves the padding bytes
uninitialized in the return registers (e.g., RAX/RDX on x86-64).

The verifier's check_kfunc_call() processes this using btf_struct_member_walk()
which ignores padding. The code then calls mark_kfunc_ret_regs() which
unconditionally marks the entire return registers as fully initialized
scalar values:

kernel/bpf/verifier.c:mark_kfunc_ret_regs() {
    ...
    mark_reg_unknown(env, regs, ret_regs[0]);
    if (nregs == 1)
        return nregs;

    for (i = 1; i < nregs; i++) {
        mark_reg_unknown(env, regs, ret_regs[i]);
    }
    ...
}

Can a BPF program then read these registers and exfiltrate the uninitialized
padding bytes (e.g., via BPF maps)?

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

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

* Re: [PATCH bpf-next v3 02/11] bpf: Drop the recursion depth argument of btf_type_is_scalar_struct()
  2026-08-27  6:11 ` [PATCH bpf-next v3 02/11] bpf: Drop the recursion depth argument of btf_type_is_scalar_struct() Yonghong Song
@ 2026-08-27  7:04   ` bot+bpf-ci
  2026-08-28 17:39     ` Yonghong Song
  0 siblings, 1 reply; 26+ messages in thread
From: bot+bpf-ci @ 2026-08-27  7:04 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: 4132 bytes --]

> commit 05b126fd8e09fdf16e522924b7a453dc9050c44b
> Author: Yonghong Song <yonghong.song@linux.dev>
> Date:   Wed Aug 26 23:11:25 2026 -0700
>
>     bpf: Drop the recursion depth argument of btf_type_is_scalar_struct()
>
>     btf_type_is_scalar_struct() recurses into nested struct members and
>     carries the nesting depth in a @rec argument, so every caller has to
>     spell out the 0 that starts the walk.
>
>     Move the recursion into a static helper that keeps @rec and leave
>     btf_type_is_scalar_struct() as a thin wrapper over it, so callers only
>     name the type they are asking about.
>
>     No functional change.

This isn't a bug, but would it help to say the split is in preparation
for giving the walk extra state, rather than presenting it as call-site
tidying?

Looking at the next patch in the series, 'bpf: Let the by-value struct
walk take the kinds of member it accepts', a @member_kinds parameter is
added to exactly this walk helper, which looks like the load-bearing
reason for introducing the wrapper now. As written, a reviewer reading
this patch alone sees only a cosmetic change and has no way to tell it
is groundwork.

> diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
> index 004b06785521c..3eb61edc8c5e1 100644
> --- a/include/linux/bpf_verifier.h
> +++ b/include/linux/bpf_verifier.h
> @@ -1489,7 +1489,7 @@ struct bpf_iarray *bpf_insn_successors(struct bpf_verifier_env *env, u32 idx);
>  void bpf_fmt_stack_mask(char *buf, ssize_t buf_sz, u64 stack_mask);
>  bool bpf_subprog_is_global(const struct bpf_verifier_env *env, int subprog);
>  bool btf_type_is_scalar_struct(struct bpf_verifier_env *env, const struct btf *btf,
> -			       const struct btf_type *t, int rec);
> +			       const struct btf_type *t);
>
>  int bpf_find_subprog(struct bpf_verifier_env *env, int off);
>  bool bpf_is_throw_kfunc(struct bpf_insn *insn);

[ ... ]

> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 5d8162e13c208..bc3053e81500f 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -11647,9 +11647,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;
> @@ -11667,7 +11666,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;
>  		}
> @@ -11686,6 +11685,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);
> +}

This isn't a bug, but since the following patch renames both of these,
could this patch introduce the final names directly so the call sites
and the header prototype are only touched once?

Both names introduced here are renamed one commit later in the same
series: btf_scalar_struct_walk() becomes btf_struct_member_walk() and
btf_type_is_scalar_struct() becomes btf_struct_is_composed_of() in
'bpf: Let the by-value struct walk take the kinds of member it accepts',
which also re-touches the include/linux/bpf_verifier.h prototype and
all six call sites a second time.

[ ... ]


---
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/33045482525

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

* Re: [PATCH bpf-next v3 05/11] bpf: Report which member makes a kfunc return type unsupported
  2026-08-27  6:11 ` [PATCH bpf-next v3 05/11] bpf: Report which member makes a kfunc return type unsupported Yonghong Song
@ 2026-08-27  7:04   ` bot+bpf-ci
  2026-08-28 17:45     ` Yonghong Song
  0 siblings, 1 reply; 26+ messages in thread
From: bot+bpf-ci @ 2026-08-27  7:04 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: 4987 bytes --]

> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 90139f1b78d10..967ad010b3222 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -11646,6 +11646,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;
> +};
> +

[ ... ]

> @@ -11676,31 +11686,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;
>  }

[ ... ]

> @@ -14077,17 +14114,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)) {
> +		if (!btf_struct_member_walk(env, desc_btf, t, BTF_MEMBER_SCALAR, 0, &path)) {
>  			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));
> +			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;
> +
> +				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));

For an array member, does this report the type that the walk actually
rejected?

Looking at btf_struct_member_walk(), when the member is an array it
resolves the element type before testing it:

    if (btf_type_is_array(member_type)) {
        array = btf_array(member_type);
        if (!array->nelems)
            goto bad_member;
        member_type = btf_type_skip_modifiers(btf, array->type, NULL);
    }
    if (!btf_member_kind_allowed(btf, member_type, member_kinds))
        goto bad_member;

The walk stores only the struct member itself in the path, discarding
the resolved element type. So for a kfunc returning:

    struct s { void *p[2]; };

the walk rejects the element type PTR, but this diagnostic derives
bad_type from bad->type (the array type), printing:

    member 'p' has type ARRAY
    ... Its member 'p' is ARRAY, not a scalar.

That contradicts the rule documented earlier ("An array member counts
as its element type") and the accepted behaviour of __u64 a[2], which
passes. The same issue affects struct inner arr[2] (reported as ARRAY,
real reason is the element is a non-scalar struct) and zero-length
arrays, whose rejection has nothing to do with type at all.

This is confined to verifier log text - the accept/reject decision is
unchanged - but the message the patch exists to add is inaccurate for
array members.

> +				member_note = bpf_diag_fmt(
> +					env, " Its member '%s' is %s, not a scalar.", bad_name,
> +					btf_type_str(bad_type));
> +			}

[ ... ]


---
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/33045482525

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

* Re: [PATCH bpf-next v3 08/11] selftests/bpf: Check the member named for an unsupported kfunc return type
  2026-08-27  6:11 ` [PATCH bpf-next v3 08/11] selftests/bpf: Check the member named for an unsupported kfunc return type Yonghong Song
@ 2026-08-27  7:04   ` bot+bpf-ci
  2026-08-28 18:20     ` Yonghong Song
  0 siblings, 1 reply; 26+ messages in thread
From: bot+bpf-ci @ 2026-08-27  7:04 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: 3030 bytes --]

> diff --git a/tools/testing/selftests/bpf/progs/aggregate_ret_kfunc.c b/tools/testing/selftests/bpf/progs/aggregate_ret_kfunc.c
> index f10e5cf6fd890..e9c82df8efb26 100644
> --- a/tools/testing/selftests/bpf/progs/aggregate_ret_kfunc.c
> +++ b/tools/testing/selftests/bpf/progs/aggregate_ret_kfunc.c

[ ... ]

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

[ ... ]

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

[ ... ]

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

A subsystem pattern flags this as potentially concerning: the commit
message states the third case covers "a type nested deeper than the
walk descends, which has no single member to blame and reports the
depth instead." But the test's only assertion, __msg("max struct
nesting depth exceeded"), pins a string that predates the series.

Looking at the verifier code, the depth report text ' It nests structs
more than %d levels deep.' was added by commit b99471ca9c385, and
unlike the member case there is no verbose() counterpart for it - it
is only emitted through bpf_diag_program_structure(). This diagnostic
output is reachable to __msg since libbpf retries a failed load at
log_level 1.

If the new path->too_deep assignment and the entire 'if (path.too_deep)'
branch were deleted, path.depth would still be 0 (the too-deep return
bypasses the bad_member/bad_path labels), member_note would stay "",
and nothing else would change in the log. The test would still pass even
though the new behavior was not present.

The two sibling tests don't have this problem - 'member 'p' has type
PTR' and 'member 'in.p' has type PTR' are both new strings from
b99471ca9c385 and directly verify that the kernel names the member
correctly. The too_deep test also omits the __msg("is not composed of
scalars or arena pointers") anchor that both siblings carry, so it does
not even pin that the failure came from the kfunc-return path.

Would adding an assertion on the depth diagnostic text plus the anchor
message make the case more discriminating?

[ ... ]


---
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/33045482525

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

* Re: [PATCH bpf-next v3 09/11] selftests/bpf: Test global functions returning arena pointers by value
  2026-08-27  6:12 ` [PATCH bpf-next v3 09/11] selftests/bpf: Test global functions returning arena pointers by value Yonghong Song
@ 2026-08-27  7:04   ` bot+bpf-ci
  2026-08-28 18:26     ` Yonghong Song
  0 siblings, 1 reply; 26+ messages in thread
From: bot+bpf-ci @ 2026-08-27  7:04 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: 2206 bytes --]

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

This isn't a bug, but would it be worth adding the new union-of-arena-
pointer case to the enumeration? The patch adds union arena_upair /
global_ret_arena_union() / aggregate_ret_global_arena_union() as a
sixteen-byte union of an arena pointer and a __u64[2]. Since the next
sentence says "cases for a struct and a union carrying a plain pointer
stay rejected", a reader might think the only union touched is a rejected
one, when the patch actually adds a newly-accepted union case as well.

> diff --git a/tools/testing/selftests/bpf/progs/verifier_arena.c b/tools/testing/selftests/bpf/progs/verifier_arena.c
> index 815f342eb4b05..672a3aadd9cb2 100644
> --- a/tools/testing/selftests/bpf/progs/verifier_arena.c
> +++ b/tools/testing/selftests/bpf/progs/verifier_arena.c
> @@ -734,4 +734,52 @@ 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;
> +}

This isn't a bug, but since the returned pointers are the first two u32
slots of a single page rather than two halves of it (page and page + 1
are offsets 0 and 4 within a single 4096-byte allocation), would a name
like arena_word_pair() / struct arena_word_pair describe the helper more
directly? The neighbouring arena return tests in the same file name
themselves after what they exercise rather than after an imagined data
layout (check_arena_arg_ret, check_arena_arg_quals1/2).


---
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/33045482525

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

* Re: [PATCH bpf-next v3 10/11] selftests/bpf: Test kfuncs returning arena pointers by value
  2026-08-27  6:12 ` [PATCH bpf-next v3 10/11] selftests/bpf: Test kfuncs " Yonghong Song
@ 2026-08-27  7:17   ` bot+bpf-ci
  2026-08-28 18:28     ` Yonghong Song
  0 siblings, 1 reply; 26+ messages in thread
From: bot+bpf-ci @ 2026-08-27  7:17 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: 2586 bytes --]

> 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..94c35e1b547cd
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/aggregate_ret_kfunc_arena.c
> @@ -0,0 +1,129 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
> +#include <vmlinux.h>
> +#include <bpf/bpf_helpers.h>
> +#include <bpf_arena_common.h>
> +#include "bpf_misc.h"
> +#include "../test_kmods/bpf_testmod_kfunc.h"
> +
> +#if defined(__clang_major__) && __clang_major__ >= 23
> +
> +struct {
> +	__uint(type, BPF_MAP_TYPE_ARENA);
> +	__uint(map_flags, BPF_F_MMAPABLE);
> +	__uint(max_entries, 2);
> +} arena SEC(".maps");
> +
> +/*
> + * A returned member carries the arena type tag but not the address space
> + * qualifier, so the program casts it into the arena address space itself
> + * rather than the compiler doing it.
> + */
> +#define arena_ptr(p) ((u32 volatile __arena *)(p))

This isn't a bug, but could the cast be spelled out at each use site, or
the macro named after the type it produces (e.g. arena_u32p())? The name
arena_ptr() reads as a generic address-space cast, but it hardcodes 'u32
volatile', which matters for the load/store widths the test checks. Other
arena selftest programs write the cast inline with the type visible at the
use site (for example '(char __arena *)ptr' in progs/arena_mem_usage.c).

> +
> +SEC("syscall")
> +__arch_x86_64 __arch_arm64
> +__load_if_JITed()
> +__success __retval(0)
> +int aggregate_ret_kfunc_arena(void *ctx)
> +{
> +	u32 volatile __arena *page = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
> +	u32 volatile __arena *a, *b;
> +	struct prog_test_ret_arena r;
> +
> +	if (!page)
> +		return 1;
> +
> +	/* Both halves come back in R0:R2, pointing at page and page + 4. */
> +	r = bpf_kfunc_call_test_ret_arena((u64)page);
> +	if (!r.a || !r.b)
> +		return 2;
> +
> +	a = arena_ptr(r.a);
> +	b = arena_ptr(r.b);
> +	*a = 1;
> +	*b = 2;
> +	if (*a != 1)
> +		return 3;
> +	if (*b != 2)
> +		return 4;
> +
> +	/* The halves are the first two slots of the page the program allocated. */
> +	page[0] = 7;
> +	if (*a != 7)
> +		return 5;
> +	page[1] = 9;
> +	if (*b != 9)
> +		return 6;
> +
> +	return 0;
> +}

[ ... ]


---
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/33045482525

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

* Re: [PATCH bpf-next v3 02/11] bpf: Drop the recursion depth argument of btf_type_is_scalar_struct()
  2026-08-27  7:04   ` bot+bpf-ci
@ 2026-08-28 17:39     ` Yonghong Song
  0 siblings, 0 replies; 26+ messages in thread
From: Yonghong Song @ 2026-08-28 17:39 UTC (permalink / raw)
  To: bot+bpf-ci, bpf
  Cc: ast, andrii, daniel, eddyz87, kernel-team, martin.lau, clm,
	ihor.solodrai



On 8/27/26 12:04 AM, bot+bpf-ci@kernel.org wrote:
>> commit 05b126fd8e09fdf16e522924b7a453dc9050c44b
>> Author: Yonghong Song <yonghong.song@linux.dev>
>> Date:   Wed Aug 26 23:11:25 2026 -0700
>>
>>      bpf: Drop the recursion depth argument of btf_type_is_scalar_struct()
>>
>>      btf_type_is_scalar_struct() recurses into nested struct members and
>>      carries the nesting depth in a @rec argument, so every caller has to
>>      spell out the 0 that starts the walk.
>>
>>      Move the recursion into a static helper that keeps @rec and leave
>>      btf_type_is_scalar_struct() as a thin wrapper over it, so callers only
>>      name the type they are asking about.
>>
>>      No functional change.
> This isn't a bug, but would it help to say the split is in preparation
> for giving the walk extra state, rather than presenting it as call-site
> tidying?
>
> Looking at the next patch in the series, 'bpf: Let the by-value struct
> walk take the kinds of member it accepts', a @member_kinds parameter is
> added to exactly this walk helper, which looks like the load-bearing
> reason for introducing the wrapper now. As written, a reviewer reading
> this patch alone sees only a cosmetic change and has no way to tell it
> is groundwork.

I think we are fine. This is a pure refactoring to remove a parameter
for a global function.

>
>> diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
>> index 004b06785521c..3eb61edc8c5e1 100644
>> --- a/include/linux/bpf_verifier.h
>> +++ b/include/linux/bpf_verifier.h
>> @@ -1489,7 +1489,7 @@ struct bpf_iarray *bpf_insn_successors(struct bpf_verifier_env *env, u32 idx);
>>   void bpf_fmt_stack_mask(char *buf, ssize_t buf_sz, u64 stack_mask);
>>   bool bpf_subprog_is_global(const struct bpf_verifier_env *env, int subprog);
>>   bool btf_type_is_scalar_struct(struct bpf_verifier_env *env, const struct btf *btf,
>> -			       const struct btf_type *t, int rec);
>> +			       const struct btf_type *t);
>>
>>   int bpf_find_subprog(struct bpf_verifier_env *env, int off);
>>   bool bpf_is_throw_kfunc(struct bpf_insn *insn);
> [ ... ]
>
>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>> index 5d8162e13c208..bc3053e81500f 100644
>> --- a/kernel/bpf/verifier.c
>> +++ b/kernel/bpf/verifier.c
>> @@ -11647,9 +11647,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;
>> @@ -11667,7 +11666,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;
>>   		}
>> @@ -11686,6 +11685,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);
>> +}
> This isn't a bug, but since the following patch renames both of these,
> could this patch introduce the final names directly so the call sites
> and the header prototype are only touched once?
>
> Both names introduced here are renamed one commit later in the same
> series: btf_scalar_struct_walk() becomes btf_struct_member_walk() and
> btf_type_is_scalar_struct() becomes btf_struct_is_composed_of() in
> 'bpf: Let the by-value struct walk take the kinds of member it accepts',
> which also re-touches the include/linux/bpf_verifier.h prototype and
> all six call sites a second time.

Not sure what is going here. The following is actual code:

+bool btf_type_is_scalar_struct(struct bpf_verifier_env *env,
+                              const struct btf *btf,
+                              const struct btf_type *t)
+{
+       return btf_struct_member_walk(env, btf, t, 0);
+}

which does not have btf_scalar_struct_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/33045482525


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

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



On 8/27/26 12:04 AM, bot+bpf-ci@kernel.org wrote:
>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>> index 90139f1b78d10..967ad010b3222 100644
>> --- a/kernel/bpf/verifier.c
>> +++ b/kernel/bpf/verifier.c
>> @@ -11646,6 +11646,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;
>> +};
>> +
> [ ... ]
>
>> @@ -11676,31 +11686,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;
>>   }
> [ ... ]
>
>> @@ -14077,17 +14114,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)) {
>> +		if (!btf_struct_member_walk(env, desc_btf, t, BTF_MEMBER_SCALAR, 0, &path)) {
>>   			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));
>> +			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;
>> +
>> +				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));
> For an array member, does this report the type that the walk actually
> rejected?
>
> Looking at btf_struct_member_walk(), when the member is an array it
> resolves the element type before testing it:
>
>      if (btf_type_is_array(member_type)) {
>          array = btf_array(member_type);
>          if (!array->nelems)
>              goto bad_member;
>          member_type = btf_type_skip_modifiers(btf, array->type, NULL);
>      }
>      if (!btf_member_kind_allowed(btf, member_type, member_kinds))
>          goto bad_member;
>
> The walk stores only the struct member itself in the path, discarding
> the resolved element type. So for a kfunc returning:
>
>      struct s { void *p[2]; };
>
> the walk rejects the element type PTR, but this diagnostic derives
> bad_type from bad->type (the array type), printing:
>
>      member 'p' has type ARRAY
>      ... Its member 'p' is ARRAY, not a scalar.
>
> That contradicts the rule documented earlier ("An array member counts
> as its element type") and the accepted behaviour of __u64 a[2], which
> passes. The same issue affects struct inner arr[2] (reported as ARRAY,
> real reason is the element is a non-scalar struct) and zero-length
> arrays, whose rejection has nothing to do with type at all.

This is indeed a problem. Will fix.

>
> This is confined to verifier log text - the accept/reject decision is
> unchanged - but the message the patch exists to add is inaccurate for
> array members.
>
>> +				member_note = bpf_diag_fmt(
>> +					env, " Its member '%s' is %s, not a scalar.", bad_name,
>> +					btf_type_str(bad_type));
>> +			}
> [ ... ]
>
>
> ---
> 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/33045482525


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

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



On 8/26/26 11:33 PM, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Low] The verifier error message for unsupported return types incorrectly claims that arena pointers are supported for the main BPF program, when they are actually rejected.
> --
>
> commit 518fd93d71684c317540966a4f0aa467d524c25b
> Author: Yonghong Song <yonghong.song@linux.dev>
>
> bpf: Allow a global function to return arena pointers by value
>
> This patch allows global functions to return a struct by value that contains
> arena pointers. It updates btf_validate_return_type() to allow arena pointers
> and adjusts the corresponding error message in btf_prepare_func_args().
>
>> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
>> index b1f4ef614d4c4..f6d82a8fd617b 100644
>> --- a/kernel/bpf/btf.c
>> +++ b/kernel/bpf/btf.c
>> @@ -7973,13 +7973,22 @@ 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
> [ ... ]
>> +		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 +8084,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 "
>> +				"scalars and arena pointers up to 16 bytes is supported.\n",
>>   				tname);
>>   		}
>>   		return err;
> [Severity: Low]
> Does this new error message in btf_prepare_func_args() incorrectly claim that
> arena pointers are supported for the main BPF program?
>
> In btf_validate_return_type(), arena pointers are only added to member_kinds
> if subprog is non-zero:
>
>      if (subprog)
>          member_kinds |= BTF_MEMBER_ARENA_PTR;
>
> However, if a user attempts to load a main BPF program (subprog == 0) with a
> global linkage (like BPF_PROG_TYPE_EXT) that returns a struct containing an
> arena pointer, it will be rejected by btf_validate_return_type() but then print
> this error message incorrectly implying that arena pointers are supported.
>
> Should the error message be adjusted, or should the subprog check be changed so
> they match?

Okay, I will have a separate message for main prog (subprog == 0) vs. none main prog.



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

* Re: [PATCH bpf-next v3 07/11] bpf: Allow arena pointers in a by-value kfunc return
  2026-08-27  6:55   ` sashiko-bot
@ 2026-08-28 18:10     ` Yonghong Song
  0 siblings, 0 replies; 26+ messages in thread
From: Yonghong Song @ 2026-08-28 18:10 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: bpf



On 8/26/26 11:55 PM, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
>
> Pre-existing issues:
> - [High] Kfuncs returning structs by value can leak uninitialized kernel stack memory via struct padding.
> --
>
> commit a43266e4517bac02a4bb85149655178eefda72ad
> Author: Yonghong Song <yonghong.song@linux.dev>
>
> bpf: Allow arena pointers in a by-value kfunc return
>
> This commit allows by-value struct returns from kfuncs to contain arena
> pointers, in addition to scalars. An arena pointer has no provenance to lose
> and can be cast by the program, so returning it as a scalar is safe.
>
>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>> index 967ad010b3222..9a36808136c9a 100644
>> --- a/kernel/bpf/verifier.c
>> +++ b/kernel/bpf/verifier.c
>> @@ -14119,13 +14119,17 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
>>   
>>   		/*
>>   		 * 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.
>> +		 * as an unknown scalar, so a pointer member would be laundered
>> +		 * into a scalar and escape provenance and reference tracking.
>> +		 * Only scalars and arena pointers are allowed: an arena pointer
>> +		 * has no provenance to lose, since a program may already derive
>> +		 * one from any scalar with addr_space_cast(), which confines the
>> +		 * result to the arena.
>>   		 */
>> -		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)) {
> [Severity: High]
> This is a pre-existing issue, but does returning structs by value from kfuncs
> leak uninitialized kernel stack memory via struct padding?
>
> When a BPF program calls a kfunc that returns a struct by value (up to 16
> bytes) containing padding bytes, the C compiler leaves the padding bytes
> uninitialized in the return registers (e.g., RAX/RDX on x86-64).
>
> The verifier's check_kfunc_call() processes this using btf_struct_member_walk()
> which ignores padding. The code then calls mark_kfunc_ret_regs() which
> unconditionally marks the entire return registers as fully initialized
> scalar values:
>
> kernel/bpf/verifier.c:mark_kfunc_ret_regs() {
>      ...
>      mark_reg_unknown(env, regs, ret_regs[0]);
>      if (nregs == 1)
>          return nregs;
>
>      for (i = 1; i < nregs; i++) {
>          mark_reg_unknown(env, regs, ret_regs[i]);
>      }
>      ...
> }
>
> Can a BPF program then read these registers and exfiltrate the uninitialized
> padding bytes (e.g., via BPF maps)?

We should be okay. Based on type, the return register will do proper narrowing
e.g., zeroing out the upper 32bit value, etc.

>


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

* Re: [PATCH bpf-next v3 08/11] selftests/bpf: Check the member named for an unsupported kfunc return type
  2026-08-27  7:04   ` bot+bpf-ci
@ 2026-08-28 18:20     ` Yonghong Song
  0 siblings, 0 replies; 26+ messages in thread
From: Yonghong Song @ 2026-08-28 18:20 UTC (permalink / raw)
  To: bot+bpf-ci, bpf
  Cc: ast, andrii, daniel, eddyz87, kernel-team, martin.lau, clm,
	ihor.solodrai



On 8/27/26 12:04 AM, bot+bpf-ci@kernel.org wrote:
>> diff --git a/tools/testing/selftests/bpf/progs/aggregate_ret_kfunc.c b/tools/testing/selftests/bpf/progs/aggregate_ret_kfunc.c
>> index f10e5cf6fd890..e9c82df8efb26 100644
>> --- a/tools/testing/selftests/bpf/progs/aggregate_ret_kfunc.c
>> +++ b/tools/testing/selftests/bpf/progs/aggregate_ret_kfunc.c
> [ ... ]
>
>> @@ -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)
> [ ... ]
>
>> @@ -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)
> [ ... ]
>
>> +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);
>> +}
> A subsystem pattern flags this as potentially concerning: the commit
> message states the third case covers "a type nested deeper than the
> walk descends, which has no single member to blame and reports the
> depth instead." But the test's only assertion, __msg("max struct
> nesting depth exceeded"), pins a string that predates the series.
>
> Looking at the verifier code, the depth report text ' It nests structs
> more than %d levels deep.' was added by commit b99471ca9c385, and
> unlike the member case there is no verbose() counterpart for it - it
> is only emitted through bpf_diag_program_structure(). This diagnostic
> output is reachable to __msg since libbpf retries a failed load at
> log_level 1.
>
> If the new path->too_deep assignment and the entire 'if (path.too_deep)'
> branch were deleted, path.depth would still be 0 (the too-deep return
> bypasses the bad_member/bad_path labels), member_note would stay "",
> and nothing else would change in the log. The test would still pass even
> though the new behavior was not present.
>
> The two sibling tests don't have this problem - 'member 'p' has type
> PTR' and 'member 'in.p' has type PTR' are both new strings from
> b99471ca9c385 and directly verify that the kernel names the member
> correctly. The too_deep test also omits the __msg("is not composed of
> scalars or arena pointers") anchor that both siblings carry, so it does
> not even pin that the failure came from the kfunc-return path.
>
> Would adding an assertion on the depth diagnostic text plus the anchor
> message make the case more discriminating?

I would like to keep it as is. For path.too_deep, we do not need to
have member names. The user only needs to take a look at the top
struct which is already available.

>
> [ ... ]
>
>
> ---
> 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/33045482525


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

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



On 8/27/26 12:04 AM, bot+bpf-ci@kernel.org wrote:
>> 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.
> This isn't a bug, but would it be worth adding the new union-of-arena-
> pointer case to the enumeration? The patch adds union arena_upair /
> global_ret_arena_union() / aggregate_ret_global_arena_union() as a
> sixteen-byte union of an arena pointer and a __u64[2]. Since the next
> sentence says "cases for a struct and a union carrying a plain pointer
> stay rejected", a reader might think the only union touched is a rejected
> one, when the patch actually adds a newly-accepted union case as well.

There is no need to have too many union cases. The existing one
should be enough.

>
>> diff --git a/tools/testing/selftests/bpf/progs/verifier_arena.c b/tools/testing/selftests/bpf/progs/verifier_arena.c
>> index 815f342eb4b05..672a3aadd9cb2 100644
>> --- a/tools/testing/selftests/bpf/progs/verifier_arena.c
>> +++ b/tools/testing/selftests/bpf/progs/verifier_arena.c
>> @@ -734,4 +734,52 @@ 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;
>> +}
> This isn't a bug, but since the returned pointers are the first two u32
> slots of a single page rather than two halves of it (page and page + 1
> are offsets 0 and 4 within a single 4096-byte allocation), would a name
> like arena_word_pair() / struct arena_word_pair describe the helper more
> directly? The neighbouring arena return tests in the same file name
> themselves after what they exercise rather than after an imagined data
> layout (check_arena_arg_ret, check_arena_arg_quals1/2).

Okay, I will use arena_work_pair() then.

>
>
> ---
> 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/33045482525


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

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



On 8/27/26 12:17 AM, bot+bpf-ci@kernel.org wrote:
>> 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..94c35e1b547cd
>> --- /dev/null
>> +++ b/tools/testing/selftests/bpf/progs/aggregate_ret_kfunc_arena.c
>> @@ -0,0 +1,129 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
>> +#include <vmlinux.h>
>> +#include <bpf/bpf_helpers.h>
>> +#include <bpf_arena_common.h>
>> +#include "bpf_misc.h"
>> +#include "../test_kmods/bpf_testmod_kfunc.h"
>> +
>> +#if defined(__clang_major__) && __clang_major__ >= 23
>> +
>> +struct {
>> +	__uint(type, BPF_MAP_TYPE_ARENA);
>> +	__uint(map_flags, BPF_F_MMAPABLE);
>> +	__uint(max_entries, 2);
>> +} arena SEC(".maps");
>> +
>> +/*
>> + * A returned member carries the arena type tag but not the address space
>> + * qualifier, so the program casts it into the arena address space itself
>> + * rather than the compiler doing it.
>> + */
>> +#define arena_ptr(p) ((u32 volatile __arena *)(p))
> This isn't a bug, but could the cast be spelled out at each use site, or
> the macro named after the type it produces (e.g. arena_u32p())? The name
> arena_ptr() reads as a generic address-space cast, but it hardcodes 'u32
> volatile', which matters for the load/store widths the test checks. Other
> arena selftest programs write the cast inline with the type visible at the
> use site (for example '(char __arena *)ptr' in progs/arena_mem_usage.c).

Okay, let us inline arena_ptr(p) directly in the code.

>
>> +
>> +SEC("syscall")
>> +__arch_x86_64 __arch_arm64
>> +__load_if_JITed()
>> +__success __retval(0)
>> +int aggregate_ret_kfunc_arena(void *ctx)
>> +{
>> +	u32 volatile __arena *page = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
>> +	u32 volatile __arena *a, *b;
>> +	struct prog_test_ret_arena r;
>> +
>> +	if (!page)
>> +		return 1;
>> +
>> +	/* Both halves come back in R0:R2, pointing at page and page + 4. */
>> +	r = bpf_kfunc_call_test_ret_arena((u64)page);
>> +	if (!r.a || !r.b)
>> +		return 2;
>> +
>> +	a = arena_ptr(r.a);
>> +	b = arena_ptr(r.b);
>> +	*a = 1;
>> +	*b = 2;
>> +	if (*a != 1)
>> +		return 3;
>> +	if (*b != 2)
>> +		return 4;
>> +
>> +	/* The halves are the first two slots of the page the program allocated. */
>> +	page[0] = 7;
>> +	if (*a != 7)
>> +		return 5;
>> +	page[1] = 9;
>> +	if (*b != 9)
>> +		return 6;
>> +
>> +	return 0;
>> +}
> [ ... ]
>
>
> ---
> 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/33045482525


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

end of thread, other threads:[~2026-08-28 18:28 UTC | newest]

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

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