All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH bpf-next v2 0/5] bpf: Minimize annotations for arena programs
@ 2026-05-30  0:22 Emil Tsalapatis
  2026-05-30  0:22 ` [PATCH bpf-next v2 1/5] selftests/bpf: libarena: Add "arena" BTF type tag to __arena qualifier Emil Tsalapatis
                   ` (4 more replies)
  0 siblings, 5 replies; 20+ messages in thread
From: Emil Tsalapatis @ 2026-05-30  0:22 UTC (permalink / raw)
  To: bpf
  Cc: ast, andrii, memxor, daniel, eddyz87, song, mattbobrowski,
	Emil Tsalapatis

BPF programs must currently include code to address two limitations
of function signatures that include arena types. First, arena arguments
must be annotated with __arg_arena in the function signature in addition
to __arena. Second, it is currently not allowed to return an arena pointer
from a subprog, even though it is safe to do so. These limitations require
extra annotations and typecasts respectively, and have proven sources of
confusion to programmers.

The patchset improves arena-related function signatures in two ways.
First, it removes the need for __arg_arena in function signatures.
Second, it allows subprogs to directly return arena pointers to their
caller.

To do this we add a new type tag to the existing __arena annotation.
The annotation is currently an alias for __attribute__((address_space(1))),
which is not discoverable from BTF alone and so cannot be used to
determine whether a pointer variable is an arena pointer during
verification. With the new type tag, we can determine whether
either the arguments and or the return value of a function belong
in an arena.

We test the new code by modifying libarena to take advantage of these
relaxed limitations.

CHANGELOG
=========

v1 -> v2 (lore.kernel.org/bpf/20260527071457.4598-1-emil@etsalapatis.com/)

- Rebased to fix conflict
- Removed the typedef foo * foo_t typedefs. Those were necessary to avoid
annotating each instance of the type with __arena. The new version of the
patch instead removes typedefs and uses __arena everywhere directly (see
patch 4/5 for more details).
- Reorganized the patchset to frontload all kernel-side changes and place
the libarena changes at the end.


Emil Tsalapatis (5):
  selftests/bpf: libarena: Add "arena" BTF type tag to __arena qualifier
  verifier: parse BTF type tags for function arguments
  bpf: Allow subprogs to return arena pointers
  selftests/bpf: Remove __arg_arena from the codebase
  selftests/bpf: libarena: Directly return arena pointers from functions

 kernel/bpf/btf.c                              | 170 +++++++++++++-----
 kernel/bpf/verifier.c                         |   6 +-
 tools/testing/selftests/bpf/bpf_arena_htab.h  |  11 +-
 .../selftests/bpf/bpf_arena_strsearch.h       |   4 +-
 .../bpf/libarena/include/bpf_arena_common.h   |   4 +-
 .../libarena/include/bpf_arena_spin_lock.h    |   6 +-
 .../bpf/libarena/include/libarena/asan.h      |   6 +-
 .../bpf/libarena/include/libarena/buddy.h     |  23 +--
 .../bpf/libarena/include/libarena/common.h    |   3 +-
 .../libarena/selftests/st_asan_buddy.bpf.c    |   4 +-
 .../bpf/libarena/selftests/st_asan_common.h   |   2 +-
 .../bpf/libarena/selftests/st_buddy.bpf.c     |   2 +-
 .../selftests/bpf/libarena/src/asan.bpf.c     |  38 ++--
 .../selftests/bpf/libarena/src/buddy.bpf.c    |  96 +++++-----
 .../selftests/bpf/libarena/src/common.bpf.c   |  10 +-
 .../selftests/bpf/progs/arena_spin_lock.c     |   1 +
 16 files changed, 225 insertions(+), 161 deletions(-)

-- 
2.54.0


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

* [PATCH bpf-next v2 1/5] selftests/bpf: libarena: Add "arena" BTF type tag to __arena qualifier
  2026-05-30  0:22 [PATCH bpf-next v2 0/5] bpf: Minimize annotations for arena programs Emil Tsalapatis
@ 2026-05-30  0:22 ` Emil Tsalapatis
  2026-05-30  0:22 ` [PATCH bpf-next v2 2/5] verifier: parse BTF type tags for function arguments Emil Tsalapatis
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 20+ messages in thread
From: Emil Tsalapatis @ 2026-05-30  0:22 UTC (permalink / raw)
  To: bpf
  Cc: ast, andrii, memxor, daniel, eddyz87, song, mattbobrowski,
	Emil Tsalapatis

The arena qualifier currently designates its associated type
as belonging to address space 1. This property affects code
generation, but is not reflected in the BTF information of
the function.

This lack of information at the BTF level prevents us from
returning arena pointers from global subprograms. Subprogs
cannot return any data structure more complex than a scalar,
so pointers to structs are rejected as a return type. We
have no way of marking the return type as a pointer to an
arena, which is safe provided the two subprogs have the same
arena.

Expand the __arena qualifier to also attach a BTF type tag
to the type. This lets us determine whether a variable belongs
to an arena from its type alone through BTF parsing.

Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com>
---
 tools/testing/selftests/bpf/libarena/include/bpf_arena_common.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tools/testing/selftests/bpf/libarena/include/bpf_arena_common.h b/tools/testing/selftests/bpf/libarena/include/bpf_arena_common.h
index 16f8ce832004..445be3c4edec 100644
--- a/tools/testing/selftests/bpf/libarena/include/bpf_arena_common.h
+++ b/tools/testing/selftests/bpf/libarena/include/bpf_arena_common.h
@@ -33,7 +33,7 @@
 #endif
 
 #if defined(__BPF_FEATURE_ADDR_SPACE_CAST) && !defined(BPF_ARENA_FORCE_ASM)
-#define __arena __attribute__((address_space(1)))
+#define __arena __attribute__((address_space(1))) __attribute__((btf_type_tag("arena")))
 #define __arena_global __attribute__((address_space(1)))
 #define cast_kern(ptr) /* nop for bpf prog. emitted by LLVM */
 #define cast_user(ptr) /* nop for bpf prog. emitted by LLVM */
-- 
2.54.0


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

* [PATCH bpf-next v2 2/5] verifier: parse BTF type tags for function arguments
  2026-05-30  0:22 [PATCH bpf-next v2 0/5] bpf: Minimize annotations for arena programs Emil Tsalapatis
  2026-05-30  0:22 ` [PATCH bpf-next v2 1/5] selftests/bpf: libarena: Add "arena" BTF type tag to __arena qualifier Emil Tsalapatis
@ 2026-05-30  0:22 ` Emil Tsalapatis
  2026-05-30  0:59   ` sashiko-bot
  2026-06-01 18:37   ` Eduard Zingerman
  2026-05-30  0:22 ` [PATCH bpf-next v2 3/5] bpf: Allow subprogs to return arena pointers Emil Tsalapatis
                   ` (2 subsequent siblings)
  4 siblings, 2 replies; 20+ messages in thread
From: Emil Tsalapatis @ 2026-05-30  0:22 UTC (permalink / raw)
  To: bpf
  Cc: ast, andrii, memxor, daniel, eddyz87, song, mattbobrowski,
	Emil Tsalapatis

The BTF parsing logic for function arguments goes through
the arguments' decl tags, but does not go into their type
tags. Add type tag parsing for function arguments.

Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com>
---
 kernel/bpf/btf.c | 120 +++++++++++++++++++++++++++++++++--------------
 1 file changed, 85 insertions(+), 35 deletions(-)

diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 17d4ab0a8206..c6a930aca67e 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -7802,6 +7802,84 @@ enum btf_arg_tag {
 	ARG_TAG_ARENA	  = BIT_ULL(5),
 };
 
+static int btf_scan_decl_tags(struct bpf_verifier_env *env,
+			      const struct btf *btf,
+			      const struct btf_type *fn_t,
+			      u32 arg_idx, bool is_global, u32 *tags)
+{
+	int id = btf_named_start_id(btf, false) - 1;
+
+	/*
+	 * The 'arg:<tag>' decl_tag takes precedence over the derivation
+	 * of the register type from the BTF type itself.
+	 */
+	while ((id = btf_find_next_decl_tag(btf, fn_t, arg_idx, "arg:", id)) > 0) {
+		const struct btf_type *tag_t = btf_type_by_id(btf, id);
+		const char *tag = __btf_name_by_offset(btf, tag_t->name_off) + 4;
+
+		/* disallow arg tags in static subprogs */
+		if (!is_global) {
+			bpf_log(&env->log,
+				"arg#%d type tag is not supported in static functions\n",
+				arg_idx);
+			return -EOPNOTSUPP;
+		}
+
+		if (strcmp(tag, "ctx") == 0) {
+			*tags |= ARG_TAG_CTX;
+		} else if (strcmp(tag, "trusted") == 0) {
+			*tags |= ARG_TAG_TRUSTED;
+		} else if (strcmp(tag, "untrusted") == 0) {
+			*tags |= ARG_TAG_UNTRUSTED;
+		} else if (strcmp(tag, "nonnull") == 0) {
+			*tags |= ARG_TAG_NONNULL;
+		} else if (strcmp(tag, "nullable") == 0) {
+			*tags |= ARG_TAG_NULLABLE;
+		} else if (strcmp(tag, "arena") == 0) {
+			*tags |= ARG_TAG_ARENA;
+		} else {
+			bpf_log(&env->log, "arg#%d has unsupported set of tags\n", arg_idx);
+			return -EOPNOTSUPP;
+		}
+	}
+	if (id != -ENOENT) {
+		bpf_log(&env->log, "arg#%d type tag fetching failure: %d\n", arg_idx, id);
+		return id;
+	}
+
+	return 0;
+}
+
+static int btf_scan_type_tags(struct bpf_verifier_env *env,
+			      const struct btf *btf, u32 type_id,
+			      u32 *tags)
+{
+	const struct btf_type *t;
+
+	/* Find the first pointer type in the chain. */
+	t = btf_type_skip_modifiers(btf, type_id, NULL);
+	if (!t || !btf_type_is_ptr(t))
+		return 0;
+
+	/* We got a pointer, get all associated type tags. */
+	t = btf_type_by_id(btf, t->type);
+	while (t && btf_type_is_type_tag(t)) {
+		const char *tag = __btf_name_by_offset(btf, t->name_off);
+
+		if (strcmp(tag, "arena") == 0) {
+			*tags |= ARG_TAG_ARENA;
+		} else {
+			bpf_log(&env->log, "function signature member has unsupported type tag '%s'\n",
+				tag);
+			return -EOPNOTSUPP;
+		}
+
+		t = btf_type_by_id(btf, t->type);
+	}
+
+	return 0;
+}
+
 /* Process BTF of a function to produce high-level expectation of function
  * arguments (like ARG_PTR_TO_CTX, or ARG_PTR_TO_MEM, etc). This information
  * is cached in subprog info for reuse.
@@ -7820,6 +7898,7 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
 	struct btf *btf = prog->aux->btf;
 	const struct btf_param *args;
 	const struct btf_type *t, *ref_t, *fn_t;
+	int err;
 	u32 i, nargs, btf_id;
 	const char *tname;
 
@@ -7903,42 +7982,13 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
 	 */
 	for (i = 0; i < nargs; i++) {
 		u32 tags = 0;
-		int id = btf_named_start_id(btf, false) - 1;
-
-		/* 'arg:<tag>' decl_tag takes precedence over derivation of
-		 * register type from BTF type itself
-		 */
-		while ((id = btf_find_next_decl_tag(btf, fn_t, i, "arg:", id)) > 0) {
-			const struct btf_type *tag_t = btf_type_by_id(btf, id);
-			const char *tag = __btf_name_by_offset(btf, tag_t->name_off) + 4;
-
-			/* disallow arg tags in static subprogs */
-			if (!is_global) {
-				bpf_log(log, "arg#%d type tag is not supported in static functions\n", i);
-				return -EOPNOTSUPP;
-			}
+		err = btf_scan_decl_tags(env, btf, fn_t, i, is_global, &tags);
+		if (err)
+			return err;
 
-			if (strcmp(tag, "ctx") == 0) {
-				tags |= ARG_TAG_CTX;
-			} else if (strcmp(tag, "trusted") == 0) {
-				tags |= ARG_TAG_TRUSTED;
-			} else if (strcmp(tag, "untrusted") == 0) {
-				tags |= ARG_TAG_UNTRUSTED;
-			} else if (strcmp(tag, "nonnull") == 0) {
-				tags |= ARG_TAG_NONNULL;
-			} else if (strcmp(tag, "nullable") == 0) {
-				tags |= ARG_TAG_NULLABLE;
-			} else if (strcmp(tag, "arena") == 0) {
-				tags |= ARG_TAG_ARENA;
-			} else {
-				bpf_log(log, "arg#%d has unsupported set of tags\n", i);
-				return -EOPNOTSUPP;
-			}
-		}
-		if (id != -ENOENT) {
-			bpf_log(log, "arg#%d type tag fetching failure: %d\n", i, id);
-			return id;
-		}
+		err = btf_scan_type_tags(env, btf, args[i].type, &tags);
+		if (err)
+			return err;
 
 		t = btf_type_by_id(btf, args[i].type);
 		while (btf_type_is_modifier(t))
-- 
2.54.0


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

* [PATCH bpf-next v2 3/5] bpf: Allow subprogs to return arena pointers
  2026-05-30  0:22 [PATCH bpf-next v2 0/5] bpf: Minimize annotations for arena programs Emil Tsalapatis
  2026-05-30  0:22 ` [PATCH bpf-next v2 1/5] selftests/bpf: libarena: Add "arena" BTF type tag to __arena qualifier Emil Tsalapatis
  2026-05-30  0:22 ` [PATCH bpf-next v2 2/5] verifier: parse BTF type tags for function arguments Emil Tsalapatis
@ 2026-05-30  0:22 ` Emil Tsalapatis
  2026-05-30  4:54   ` sashiko-bot
  2026-06-01 19:01   ` Eduard Zingerman
  2026-05-30  0:22 ` [PATCH bpf-next v2 4/5] selftests/bpf: Remove __arg_arena from the codebase Emil Tsalapatis
  2026-05-30  0:22 ` [PATCH bpf-next v2 5/5] selftests/bpf: libarena: Directly return arena pointers from functions Emil Tsalapatis
  4 siblings, 2 replies; 20+ messages in thread
From: Emil Tsalapatis @ 2026-05-30  0:22 UTC (permalink / raw)
  To: bpf
  Cc: ast, andrii, memxor, daniel, eddyz87, song, mattbobrowski,
	Emil Tsalapatis

BPF subprogs currently only return void or scalar values. However,
it is also safe to return arena pointers between subprogs in the same
BPF program: Arena pointers are guaranteed to be safe for both programs
at any point. Expand the verifier to permit returning an arena pointer
to the caller.

The main subprog is still not allowed to return an arena pointer because
arena pointers are internal to the BPF program, and the return values
permitted for each main subprog depend on the program type anyway.

Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com>
---
 kernel/bpf/btf.c      | 50 +++++++++++++++++++++++++++++++++----------
 kernel/bpf/verifier.c |  6 +++++-
 2 files changed, 44 insertions(+), 12 deletions(-)

diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index c6a930aca67e..7a8101879f84 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -7880,6 +7880,36 @@ static int btf_scan_type_tags(struct bpf_verifier_env *env,
 	return 0;
 }
 
+/* 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)
+{
+	u32 tags = 0;
+	int err;
+
+	err = btf_scan_type_tags(env, btf, t->type, &tags);
+	if (err)
+		return err;
+	t = btf_type_by_id(btf, t->type);
+
+	while (btf_type_is_modifier(t))
+		t = btf_type_by_id(btf, t->type);
+
+	/*
+	 * We allow all subprogs except for the main one to return any kind of arena pointer.
+	 * General arena variables are not allowed, since it makes no sense to return by value
+	 * a variable that's on the heap in the first place.
+	 */
+	if (subprog && (tags & ARG_TAG_ARENA) && btf_type_is_ptr(t))
+		return 0;
+
+	/* We always accept void or scalars. */
+	if (btf_type_is_void(t) || btf_type_is_int(t) || btf_is_any_enum(t))
+		return 0;
+
+	return -EOPNOTSUPP;
+}
+
 /* Process BTF of a function to produce high-level expectation of function
  * arguments (like ARG_PTR_TO_CTX, or ARG_PTR_TO_MEM, etc). This information
  * is cached in subprog info for reuse.
@@ -7963,17 +7993,15 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
 			tname, nargs, MAX_BPF_FUNC_REG_ARGS);
 		return -EINVAL;
 	}
-	/* check that function is void or returns int, exception cb also requires this */
-	t = btf_type_by_id(btf, t->type);
-	while (btf_type_is_modifier(t))
-		t = btf_type_by_id(btf, t->type);
-	if (!btf_type_is_void(t) && !btf_type_is_int(t) && !btf_is_any_enum(t)) {
-		if (!is_global)
-			return -EINVAL;
-		bpf_log(log,
-			"Global function %s() return value not void or scalar. "
-			"Only those are supported.\n",
-			tname);
+
+	err = btf_validate_return_type(env, btf, t, subprog);
+	if (err) {
+		if (is_global) {
+			bpf_log(log,
+				"Global function %s() return value not void or scalar. "
+				"Only those are supported.\n",
+				tname);
+		}
 		return -EINVAL;
 	}
 
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index c8d980fdd709..64d7954e93ea 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -9375,7 +9375,7 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 		clear_caller_saved_regs(env, caller->regs);
 		invalidate_outgoing_stack_args(env, cur_func(env));
 
-		/* All non-void global functions return a 64-bit SCALAR_VALUE. */
+		/* All non-void global functions return a 64-bit SCALAR_VALUE or PTR_TO_ARENA. */
 		if (!subprog_returns_void(env, subprog)) {
 			mark_reg_unknown(env, caller->regs, BPF_REG_0);
 			caller->regs[BPF_REG_0].subreg_def = DEF_NOT_SUBREG;
@@ -16645,6 +16645,10 @@ static int check_global_subprog_return_code(struct bpf_verifier_env *env)
 	if (err)
 		return err;
 
+	/* Pointers to arena are safe to pass between subprograms. */
+	if (is_arena_reg(env, BPF_REG_0))
+		return 0;
+
 	if (is_pointer_value(env, BPF_REG_0)) {
 		verbose(env, "R%d leaks addr as return value\n", BPF_REG_0);
 		return -EACCES;
-- 
2.54.0


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

* [PATCH bpf-next v2 4/5] selftests/bpf: Remove __arg_arena from the codebase
  2026-05-30  0:22 [PATCH bpf-next v2 0/5] bpf: Minimize annotations for arena programs Emil Tsalapatis
                   ` (2 preceding siblings ...)
  2026-05-30  0:22 ` [PATCH bpf-next v2 3/5] bpf: Allow subprogs to return arena pointers Emil Tsalapatis
@ 2026-05-30  0:22 ` Emil Tsalapatis
  2026-05-30  5:03   ` sashiko-bot
  2026-06-01 19:06   ` Eduard Zingerman
  2026-05-30  0:22 ` [PATCH bpf-next v2 5/5] selftests/bpf: libarena: Directly return arena pointers from functions Emil Tsalapatis
  4 siblings, 2 replies; 20+ messages in thread
From: Emil Tsalapatis @ 2026-05-30  0:22 UTC (permalink / raw)
  To: bpf
  Cc: ast, andrii, memxor, daniel, eddyz87, song, mattbobrowski,
	Emil Tsalapatis

Now that BPF __arg_arena has been subsumed by __arena, remove
__arg_arena from the codebase. This way the user has one fewer
annotation to worry about.

To remove __arg_arena we remove the typedefs we were previously
using to minimize __arena annotations. This is because __arena
now also includes a BTF type tag, which is ignored for non-pointer
types. As a result, we cannot capture the whole __arena annotation
inside a typedef and need to directly annotate the pointer type when
declaring the variable.

The extra verbosity is worth it because the use of the __arena tag
is intuitive to the programmer and removes the __arg_arena tag that
has been a consistent source of confusion for users. The typedefs
can be reintroduced later (without __arg_arena) once compilers start
supporting BTF type tags for non-pointer types.

Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com>
---
 tools/testing/selftests/bpf/bpf_arena_htab.h  | 11 ++-
 .../selftests/bpf/bpf_arena_strsearch.h       |  4 +-
 .../bpf/libarena/include/bpf_arena_common.h   |  2 +-
 .../libarena/include/bpf_arena_spin_lock.h    |  6 +-
 .../bpf/libarena/include/libarena/asan.h      |  6 +-
 .../bpf/libarena/include/libarena/buddy.h     | 22 ++---
 .../libarena/selftests/st_asan_buddy.bpf.c    |  4 +-
 .../bpf/libarena/selftests/st_asan_common.h   |  2 +-
 .../bpf/libarena/selftests/st_buddy.bpf.c     |  2 +-
 .../selftests/bpf/libarena/src/asan.bpf.c     | 38 ++++-----
 .../selftests/bpf/libarena/src/buddy.bpf.c    | 80 +++++++++----------
 .../selftests/bpf/libarena/src/common.bpf.c   |  6 +-
 .../selftests/bpf/progs/arena_spin_lock.c     |  1 +
 13 files changed, 84 insertions(+), 100 deletions(-)

diff --git a/tools/testing/selftests/bpf/bpf_arena_htab.h b/tools/testing/selftests/bpf/bpf_arena_htab.h
index acc01a876668..eb80d67c3870 100644
--- a/tools/testing/selftests/bpf/bpf_arena_htab.h
+++ b/tools/testing/selftests/bpf/bpf_arena_htab.h
@@ -14,9 +14,8 @@ struct htab {
 	htab_bucket_t *buckets;
 	int n_buckets;
 };
-typedef struct htab __arena htab_t;
 
-static inline htab_bucket_t *__select_bucket(htab_t *htab, __u32 hash)
+static inline htab_bucket_t *__select_bucket(struct htab __arena *htab, __u32 hash)
 {
 	htab_bucket_t *b = htab->buckets;
 
@@ -24,7 +23,7 @@ static inline htab_bucket_t *__select_bucket(htab_t *htab, __u32 hash)
 	return &b[hash & (htab->n_buckets - 1)];
 }
 
-static inline arena_list_head_t *select_bucket(htab_t *htab, __u32 hash)
+static inline arena_list_head_t *select_bucket(struct htab __arena *htab, __u32 hash)
 {
 	return &__select_bucket(htab, hash)->head;
 }
@@ -53,7 +52,7 @@ static int htab_hash(int key)
 	return key;
 }
 
-__weak int htab_lookup_elem(htab_t *htab __arg_arena, int key)
+__weak int htab_lookup_elem(struct htab __arena *htab __arg_arena, int key)
 {
 	hashtab_elem_t *l_old;
 	arena_list_head_t *head;
@@ -66,7 +65,7 @@ __weak int htab_lookup_elem(htab_t *htab __arg_arena, int key)
 	return 0;
 }
 
-__weak int htab_update_elem(htab_t *htab __arg_arena, int key, int value)
+__weak int htab_update_elem(struct htab __arena *htab __arg_arena, int key, int value)
 {
 	hashtab_elem_t *l_new = NULL, *l_old;
 	arena_list_head_t *head;
@@ -90,7 +89,7 @@ __weak int htab_update_elem(htab_t *htab __arg_arena, int key, int value)
 	return 0;
 }
 
-void htab_init(htab_t *htab)
+void htab_init(struct htab __arena *htab)
 {
 	void __arena *buckets = bpf_arena_alloc_pages(&arena, NULL, 2, NUMA_NO_NODE, 0);
 
diff --git a/tools/testing/selftests/bpf/bpf_arena_strsearch.h b/tools/testing/selftests/bpf/bpf_arena_strsearch.h
index f0d575daef5a..10a70667c8bf 100644
--- a/tools/testing/selftests/bpf/bpf_arena_strsearch.h
+++ b/tools/testing/selftests/bpf/bpf_arena_strsearch.h
@@ -3,7 +3,7 @@
 #pragma once
 #include <bpf_arena_common.h>
 
-__noinline int bpf_arena_strlen(const char __arena *s __arg_arena)
+__noinline int bpf_arena_strlen(const char __arena *s)
 {
 	const char __arena *sc;
 
@@ -40,7 +40,7 @@ __noinline int bpf_arena_strlen(const char __arena *s __arg_arena)
  *
  * An opening bracket without a matching close is matched literally.
  */
-__noinline bool glob_match(char const __arena *pat __arg_arena, char const __arena *str __arg_arena)
+__noinline bool glob_match(char const __arena *pat, char const __arena *str)
 {
 	/*
 	 * Backtrack to previous * on mismatch and retry starting one
diff --git a/tools/testing/selftests/bpf/libarena/include/bpf_arena_common.h b/tools/testing/selftests/bpf/libarena/include/bpf_arena_common.h
index 445be3c4edec..9e5b4616f796 100644
--- a/tools/testing/selftests/bpf/libarena/include/bpf_arena_common.h
+++ b/tools/testing/selftests/bpf/libarena/include/bpf_arena_common.h
@@ -38,7 +38,7 @@
 #define cast_kern(ptr) /* nop for bpf prog. emitted by LLVM */
 #define cast_user(ptr) /* nop for bpf prog. emitted by LLVM */
 #else
-#define __arena
+#define __arena __attribute__((btf_type_tag("arena")))
 #define __arena_global SEC(".addr_space.1")
 #define cast_kern(ptr) bpf_addr_space_cast(ptr, 0, 1)
 #define cast_user(ptr) bpf_addr_space_cast(ptr, 1, 0)
diff --git a/tools/testing/selftests/bpf/libarena/include/bpf_arena_spin_lock.h b/tools/testing/selftests/bpf/libarena/include/bpf_arena_spin_lock.h
index 164638690a4d..ae6b72d15bb6 100644
--- a/tools/testing/selftests/bpf/libarena/include/bpf_arena_spin_lock.h
+++ b/tools/testing/selftests/bpf/libarena/include/bpf_arena_spin_lock.h
@@ -16,10 +16,6 @@
 #define EOPNOTSUPP 95
 #define ETIMEDOUT 110
 
-#ifndef __arena
-#define __arena __attribute__((address_space(1)))
-#endif
-
 extern unsigned long CONFIG_NR_CPUS __kconfig;
 
 /*
@@ -246,7 +242,7 @@ static __always_inline int arena_spin_trylock(arena_spinlock_t __arena *lock)
 }
 
 __noinline __weak
-int arena_spin_lock_slowpath(arena_spinlock_t __arena __arg_arena *lock, u32 val)
+int arena_spin_lock_slowpath(arena_spinlock_t __arena *lock, u32 val)
 {
 	struct arena_mcs_spinlock __arena *prev, *next, *node0, *node;
 	int ret = -ETIMEDOUT;
diff --git a/tools/testing/selftests/bpf/libarena/include/libarena/asan.h b/tools/testing/selftests/bpf/libarena/include/libarena/asan.h
index eb9fc69d9eb0..900267159292 100644
--- a/tools/testing/selftests/bpf/libarena/include/libarena/asan.h
+++ b/tools/testing/selftests/bpf/libarena/include/libarena/asan.h
@@ -25,12 +25,10 @@ extern volatile bool asan_report_once;
 
 #ifdef BPF_ARENA_ASAN
 
-typedef s8 __arena s8a;
-
 static inline
-s8a *mem_to_shadow(void __arena __arg_arena *addr)
+s8 __arena *mem_to_shadow(void __arena *addr)
 {
-	return (s8a *)(((u32)(u64)addr >> ASAN_SHADOW_SHIFT) +
+	return (s8 __arena *)(((u32)(u64)addr >> ASAN_SHADOW_SHIFT) +
 			__asan_shadow_memory_dynamic_address);
 }
 
diff --git a/tools/testing/selftests/bpf/libarena/include/libarena/buddy.h b/tools/testing/selftests/bpf/libarena/include/libarena/buddy.h
index 00e2437128ef..4d57fc1b5c26 100644
--- a/tools/testing/selftests/bpf/libarena/include/libarena/buddy.h
+++ b/tools/testing/selftests/bpf/libarena/include/libarena/buddy.h
@@ -2,12 +2,6 @@
 /* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
 #pragma once
 
-struct buddy_chunk;
-typedef struct buddy_chunk __arena buddy_chunk_t;
-
-struct buddy_header;
-typedef struct buddy_header __arena buddy_header_t;
-
 enum buddy_consts {
 	/*
 	 * Minimum allocation is 1 << BUDDY_MIN_ALLOC_SHIFT.
@@ -68,25 +62,21 @@ struct buddy_chunk {
 	u8		allocated[BUDDY_CHUNK_ITEMS / 8];
 	/* Freelists for O(1) allocation. */
 	u64		freelists[BUDDY_CHUNK_NUM_ORDERS];
-	buddy_chunk_t	*next;
+	struct buddy_chunk __arena	*next;
 };
 
 struct buddy {
-	buddy_chunk_t *first_chunk;		/* Pointer to the chunk linked list. */
+	struct buddy_chunk __arena *first_chunk;		/* Pointer to the chunk linked list. */
 	arena_spinlock_t lock;			/* Allocator lock */
 	u64 vaddr;				/* Allocation into reserved vaddr */
 };
 
-typedef struct buddy __arena buddy_t;
-
 #ifdef __BPF__
 
-int buddy_init(buddy_t *buddy);
-int buddy_destroy(buddy_t *buddy);
-int buddy_free_internal(buddy_t *buddy, u64 free);
-#define buddy_free(buddy, ptr) buddy_free_internal((buddy), (u64)(ptr))
-u64 buddy_alloc_internal(buddy_t *buddy, size_t size);
+int buddy_init(struct buddy __arena *buddy);
+int buddy_destroy(struct buddy __arena *buddy);
+int buddy_free(struct buddy __arena *buddy, void __arena *free);
+u64 buddy_alloc_internal(struct buddy __arena *buddy, size_t size);
 #define buddy_alloc(alloc, size) ((void __arena *)buddy_alloc_internal((alloc), (size)))
 
-
 #endif /* __BPF__  */
diff --git a/tools/testing/selftests/bpf/libarena/selftests/st_asan_buddy.bpf.c b/tools/testing/selftests/bpf/libarena/selftests/st_asan_buddy.bpf.c
index 97acd50ffa5c..686caba2c643 100644
--- a/tools/testing/selftests/bpf/libarena/selftests/st_asan_buddy.bpf.c
+++ b/tools/testing/selftests/bpf/libarena/selftests/st_asan_buddy.bpf.c
@@ -8,7 +8,7 @@
 /* Required for parsing the ASAN call stacks. */
 #include "test_progs_compat.h"
 
-extern buddy_t buddy;
+extern struct buddy __arena buddy;
 
 #ifdef BPF_ARENA_ASAN
 
@@ -54,7 +54,7 @@ static __always_inline int asan_test_buddy_oob_single(size_t alloc_size)
  * Factored out because asan_validate_addr is complex enough to cause
  * verification failures if verified with the rest of asan_test_buddy_uaf_single.
  */
-__weak int asan_test_buddy_byte(u8 __arena __arg_arena *mem, int i, bool freed)
+__weak int asan_test_buddy_byte(u8 __arena *mem, int i, bool freed)
 {
 	int ret;
 
diff --git a/tools/testing/selftests/bpf/libarena/selftests/st_asan_common.h b/tools/testing/selftests/bpf/libarena/selftests/st_asan_common.h
index 1d3edc4372ac..34a7918cb4cf 100644
--- a/tools/testing/selftests/bpf/libarena/selftests/st_asan_common.h
+++ b/tools/testing/selftests/bpf/libarena/selftests/st_asan_common.h
@@ -9,7 +9,7 @@ static inline void print_asan_map_state(void __arena *addr)
 {
 	arena_stdout("%s:%d ASAN %p -> (val: %x gran: %x set: [%s])",
 			__func__, __LINE__, addr,
-			*(s8a *)(addr), ASAN_GRANULE(addr),
+			*(s8 __arena *)(addr), ASAN_GRANULE(addr),
 			asan_shadow_set(addr) ? "yes" : "no");
 }
 
diff --git a/tools/testing/selftests/bpf/libarena/selftests/st_buddy.bpf.c b/tools/testing/selftests/bpf/libarena/selftests/st_buddy.bpf.c
index 79e6f0baabfe..b45a306816c0 100644
--- a/tools/testing/selftests/bpf/libarena/selftests/st_buddy.bpf.c
+++ b/tools/testing/selftests/bpf/libarena/selftests/st_buddy.bpf.c
@@ -6,7 +6,7 @@
 #include <libarena/asan.h>
 #include <libarena/buddy.h>
 
-extern buddy_t buddy;
+extern struct buddy __arena buddy;
 
 struct segarr_entry {
 	u8 __arena *block;
diff --git a/tools/testing/selftests/bpf/libarena/src/asan.bpf.c b/tools/testing/selftests/bpf/libarena/src/asan.bpf.c
index 64c5b990086c..5135d5c72a46 100644
--- a/tools/testing/selftests/bpf/libarena/src/asan.bpf.c
+++ b/tools/testing/selftests/bpf/libarena/src/asan.bpf.c
@@ -110,7 +110,7 @@ volatile bool asan_report_once = false;
  * to exit due to a missing implementation. Provide a simple implementation
  * just for memset to use it for poisoning/unpoisoning the map.
  */
-__weak int asan_memset(s8a __arg_arena *dst, s8 val, size_t size)
+__weak int asan_memset(s8 __arena *dst, s8 val, size_t size)
 {
 	size_t i;
 
@@ -121,9 +121,9 @@ __weak int asan_memset(s8a __arg_arena *dst, s8 val, size_t size)
 }
 
 /* Validate a 1-byte access, always within a single byte. */
-static __always_inline bool memory_is_poisoned_1(s8a *addr)
+static __always_inline bool memory_is_poisoned_1(s8 __arena *addr)
 {
-	s8 shadow_value = *(s8a *)mem_to_shadow(addr);
+	s8 shadow_value = *(s8 __arena *)mem_to_shadow(addr);
 
 	/* Byte is 0, access is valid. */
 	if (likely(!shadow_value))
@@ -139,7 +139,7 @@ static __always_inline bool memory_is_poisoned_1(s8a *addr)
 }
 
 /* Validate a 2- 4-, 8-byte access, shadow spans up to 2 bytes. */
-static __always_inline bool memory_is_poisoned_2_4_8(s8a *addr, u64 size)
+static __always_inline bool memory_is_poisoned_2_4_8(s8 __arena *addr, u64 size)
 {
 	u64 end = (u64)addr + size - 1;
 
@@ -148,17 +148,17 @@ static __always_inline bool memory_is_poisoned_2_4_8(s8a *addr, u64 size)
 	 * overflow above ASAN_GRANULE).
 	 */
 	if (likely(ASAN_GRANULE(end) >= size - 1))
-		return memory_is_poisoned_1((s8a *)end);
+		return memory_is_poisoned_1((s8 __arena *)end);
 
 	/*
 	 * Otherwise first byte must be fully unpoisoned, and second byte
 	 * must be unpoisoned up to the end of the accessed region.
 	 */
 
-	return *(s8a *)mem_to_shadow(addr) || memory_is_poisoned_1((s8a *)end);
+	return *(s8 __arena *)mem_to_shadow(addr) || memory_is_poisoned_1((s8 __arena *)end);
 }
 
-__weak bool asan_shadow_set(void __arena __arg_arena *addr)
+__weak bool asan_shadow_set(void __arena *addr)
 {
 	return memory_is_poisoned_1(addr);
 }
@@ -166,7 +166,7 @@ __weak bool asan_shadow_set(void __arena __arg_arena *addr)
 static __always_inline u64 first_nonzero_byte(u64 addr, size_t size)
 {
 	while (size && can_loop) {
-		if (unlikely(*(s8a *)addr))
+		if (unlikely(*(s8 __arena *)addr))
 			return addr;
 		addr += 1;
 		size -= 1;
@@ -175,7 +175,7 @@ static __always_inline u64 first_nonzero_byte(u64 addr, size_t size)
 	return SHADOW_ALL_ZEROES;
 }
 
-static __always_inline bool memory_is_poisoned_n(s8a *addr, u64 size)
+static __always_inline bool memory_is_poisoned_n(s8 __arena *addr, u64 size)
 {
 	u64 ret;
 	u64 start;
@@ -189,10 +189,10 @@ static __always_inline bool memory_is_poisoned_n(s8a *addr, u64 size)
 	if (likely(ret == SHADOW_ALL_ZEROES))
 		return false;
 
-	return unlikely(ret != end || ASAN_GRANULE(addr + size - 1) >= *(s8a *)end);
+	return unlikely(ret != end || ASAN_GRANULE(addr + size - 1) >= *(s8 __arena *)end);
 }
 
-__weak int asan_report(s8a __arg_arena *addr, size_t sz, u32 flags)
+__weak int asan_report(s8 __arena *addr, size_t sz, u32 flags)
 {
 	u32 reported = __sync_val_compare_and_swap(&asan_reported, false, true);
 
@@ -211,7 +211,7 @@ __weak int asan_report(s8a __arg_arena *addr, size_t sz, u32 flags)
 	return 0;
 }
 
-static __always_inline bool check_asan_args(s8a *addr, size_t size,
+static __always_inline bool check_asan_args(s8 __arena *addr, size_t size,
 					    bool *result)
 {
 	bool valid = true;
@@ -253,7 +253,7 @@ static __always_inline bool check_asan_args(s8a *addr, size_t size,
 static __always_inline bool check_region_inline(intptr_t ptr, size_t size,
 						u32 flags)
 {
-	s8a *addr = (s8a *)(u64)ptr;
+	s8 __arena *addr = (s8 __arena *)(u64)ptr;
 	bool is_poisoned, is_valid;
 
 	if (check_asan_args(addr, size, &is_valid)) {
@@ -305,19 +305,19 @@ static __always_inline bool check_region_inline(intptr_t ptr, size_t size,
 	}                                                             \
 	__hidden void __asan_report_store##size(intptr_t addr)           \
 	{                                                             \
-		asan_report((s8a *)addr, size, ASAN_WRITE);           \
+		asan_report((s8 __arena *)addr, size, ASAN_WRITE);           \
 	}                                                             \
 	__hidden void __asan_report_store##size##_noabort(intptr_t addr) \
 	{                                                             \
-		asan_report((s8a *)addr, size, ASAN_WRITE);           \
+		asan_report((s8 __arena *)addr, size, ASAN_WRITE);           \
 	}                                                             \
 	__hidden void __asan_report_load##size(intptr_t addr)            \
 	{                                                             \
-		asan_report((s8a *)addr, size, ASAN_READ);            \
+		asan_report((s8 __arena *)addr, size, ASAN_READ);            \
 	}                                                             \
 	__hidden void __asan_report_load##size##_noabort(intptr_t addr)  \
 	{                                                             \
-		asan_report((s8a *)addr, size, ASAN_READ);            \
+		asan_report((s8 __arena *)addr, size, ASAN_READ);            \
 	}
 
 DEFINE_ASAN_LOAD_STORE(1);
@@ -385,7 +385,7 @@ void *__asan_memset(void *p, int c, size_t n)
  */
 __hidden __noasan int asan_poison(void __arena *addr, s8 val, size_t size)
 {
-	s8a *shadow;
+	s8 __arena *shadow;
 	size_t len;
 
 	/*
@@ -443,7 +443,7 @@ __hidden __noasan int asan_poison(void __arena *addr, s8 val, size_t size)
 __hidden __noasan int asan_unpoison(void __arena *addr, size_t size)
 {
 	size_t partial = size & ASAN_GRANULE_MASK;
-	s8a *shadow;
+	s8 __arena *shadow;
 	size_t len;
 
 	/*
diff --git a/tools/testing/selftests/bpf/libarena/src/buddy.bpf.c b/tools/testing/selftests/bpf/libarena/src/buddy.bpf.c
index 865e00803daa..f4ed4c3abb4b 100644
--- a/tools/testing/selftests/bpf/libarena/src/buddy.bpf.c
+++ b/tools/testing/selftests/bpf/libarena/src/buddy.bpf.c
@@ -45,12 +45,12 @@ enum {
 	BUDDY_CHUNK_PAGES	= BUDDY_CHUNK_BYTES / __PAGE_SIZE
 };
 
-static inline int buddy_lock(buddy_t *buddy)
+static inline int buddy_lock(struct buddy __arena *buddy)
 {
 	return arena_spin_lock(&buddy->lock);
 }
 
-static inline void buddy_unlock(buddy_t *buddy)
+static inline void buddy_unlock(struct buddy __arena *buddy)
 {
 	arena_spin_unlock(&buddy->lock);
 }
@@ -61,7 +61,7 @@ static inline void buddy_unlock(buddy_t *buddy)
  * page alloc kfuncs do not support aligning to a boundary (in this
  * case 1 MiB, see buddy.h on how this is derived).
  */
-static int buddy_reserve_arena_vaddr(buddy_t *buddy)
+static int buddy_reserve_arena_vaddr(struct buddy __arena *buddy)
 {
 	buddy->vaddr = 0;
 
@@ -73,7 +73,7 @@ static int buddy_reserve_arena_vaddr(buddy_t *buddy)
 /*
  * Free up any unused address space. Used only during teardown.
  */
-static void buddy_unreserve_arena_vaddr(buddy_t *buddy)
+static void buddy_unreserve_arena_vaddr(struct buddy __arena *buddy)
 {
 	bpf_arena_free_pages(
 		&arena, (void __arena *)(BUDDY_VADDR_OFFSET + buddy->vaddr),
@@ -94,7 +94,7 @@ static void buddy_unreserve_arena_vaddr(buddy_t *buddy)
  * However, bump allocation must still be atomic because this function
  * is called without the buddy lock from multiple threads concurrently.
  */
-__weak int buddy_alloc_arena_vaddr(buddy_t __arg_arena *buddy, u64 *vaddrp)
+__weak int buddy_alloc_arena_vaddr(struct buddy __arena *buddy, u64 *vaddrp)
 {
 	u64 vaddr, old, new;
 
@@ -134,7 +134,7 @@ static u64 arena_next_pow2(__u64 n)
 }
 
 __weak
-int idx_set_allocated(buddy_chunk_t __arg_arena *chunk, u64 idx, bool allocated)
+int idx_set_allocated(struct buddy_chunk __arena *chunk, u64 idx, bool allocated)
 {
 	bool already_allocated;
 
@@ -160,7 +160,7 @@ int idx_set_allocated(buddy_chunk_t __arg_arena *chunk, u64 idx, bool allocated)
 	return 0;
 }
 
-static int idx_is_allocated(buddy_chunk_t *chunk, u64 idx, bool *allocated)
+static int idx_is_allocated(struct buddy_chunk __arena *chunk, u64 idx, bool *allocated)
 {
 	if (unlikely(idx >= BUDDY_CHUNK_ITEMS)) {
 		arena_stderr("getting state of invalid idx (%llu, max %d)\n", idx,
@@ -173,7 +173,7 @@ static int idx_is_allocated(buddy_chunk_t *chunk, u64 idx, bool *allocated)
 }
 
 __weak
-int idx_set_order(buddy_chunk_t __arg_arena *chunk, u64 idx, u8 order)
+int idx_set_order(struct buddy_chunk __arena *chunk, u64 idx, u8 order)
 {
 	u8 prev_order;
 
@@ -206,7 +206,7 @@ int idx_set_order(buddy_chunk_t __arg_arena *chunk, u64 idx, u8 order)
 	return 0;
 }
 
-static u8 idx_get_order(buddy_chunk_t *chunk, u64 idx)
+static u8 idx_get_order(struct buddy_chunk __arena *chunk, u64 idx)
 {
 	u8 result;
 
@@ -223,7 +223,7 @@ static u8 idx_get_order(buddy_chunk_t *chunk, u64 idx)
 	return (idx & 0x1) ? (result & 0xf) : (result >> 4);
 }
 
-static void __arena *idx_to_addr(buddy_chunk_t *chunk, size_t idx)
+static void __arena *idx_to_addr(struct buddy_chunk __arena *chunk, size_t idx)
 {
 	u64 address;
 
@@ -246,7 +246,7 @@ static void __arena *idx_to_addr(buddy_chunk_t *chunk, size_t idx)
 	return (void __arena *)address;
 }
 
-static buddy_header_t *idx_to_header(buddy_chunk_t *chunk, size_t idx)
+static struct buddy_header __arena *idx_to_header(struct buddy_chunk __arena *chunk, size_t idx)
 {
 	bool allocated;
 	u64 address;
@@ -283,13 +283,13 @@ static buddy_header_t *idx_to_header(buddy_chunk_t *chunk, size_t idx)
 	 * less probable.
 	 */
 
-	return (buddy_header_t *)(address + BUDDY_HEADER_OFF);
+	return (struct buddy_header __arena *)(address + BUDDY_HEADER_OFF);
 }
 
-static void header_add_freelist(buddy_chunk_t *chunk, buddy_header_t *header,
+static void header_add_freelist(struct buddy_chunk __arena *chunk, struct buddy_header __arena *header,
 		u64 idx, u8 order)
 {
-	buddy_header_t *tmp_header;
+	struct buddy_header __arena *tmp_header;
 
 	idx_set_order(chunk, idx, order);
 
@@ -304,10 +304,10 @@ static void header_add_freelist(buddy_chunk_t *chunk, buddy_header_t *header,
 	chunk->freelists[order] = idx;
 }
 
-static void header_remove_freelist(buddy_chunk_t  *chunk,
-				   buddy_header_t *header, u8 order)
+static void header_remove_freelist(struct buddy_chunk __arena  *chunk,
+				   struct buddy_header __arena *header, u8 order)
 {
-	buddy_header_t *tmp_header;
+	struct buddy_header __arena *tmp_header;
 
 	if (header->prev_index != BUDDY_CHUNK_ITEMS) {
 		tmp_header = idx_to_header(chunk, header->prev_index);
@@ -356,10 +356,10 @@ static u64 size_to_order(size_t size)
 }
 
 __weak
-int add_leftovers_to_freelist(buddy_chunk_t __arg_arena *chunk, u32 cur_idx,
+int add_leftovers_to_freelist(struct buddy_chunk __arena *chunk, u32 cur_idx,
 		u64 min_order, u64 max_order)
 {
-	buddy_header_t *header;
+	struct buddy_header __arena *header;
 	u64 ord;
 	u32 idx;
 
@@ -381,10 +381,10 @@ int add_leftovers_to_freelist(buddy_chunk_t __arg_arena *chunk, u32 cur_idx,
 	return 0;
 }
 
-static buddy_chunk_t *buddy_chunk_get(buddy_t *buddy)
+static struct buddy_chunk __arena *buddy_chunk_get(struct buddy __arena *buddy)
 {
 	u64 order, ord, min_order, max_order;
-	buddy_chunk_t  *chunk;
+	struct buddy_chunk __arena  *chunk;
 	size_t left;
 	int power2;
 	u64 vaddr;
@@ -561,9 +561,9 @@ static buddy_chunk_t *buddy_chunk_get(buddy_t *buddy)
 	return chunk;
 }
 
-__weak int buddy_init(buddy_t __arg_arena *buddy)
+__weak int buddy_init(struct buddy __arena *buddy)
 {
-	buddy_chunk_t *chunk;
+	struct buddy_chunk __arena *chunk;
 	int ret;
 
 	if (!asan_ready())
@@ -602,9 +602,9 @@ __weak int buddy_init(buddy_t __arg_arena *buddy)
  * We do not take a lock because we are freeing arena pages, and nobody should
  * be using the allocator at that point in the execution.
  */
-__weak int buddy_destroy(buddy_t __arg_arena *buddy)
+__weak int buddy_destroy(struct buddy __arena *buddy)
 {
-	buddy_chunk_t *chunk, *next;
+	struct buddy_chunk __arena *chunk, *next;
 
 	if (!buddy)
 		return -EINVAL;
@@ -631,9 +631,9 @@ __weak int buddy_destroy(buddy_t __arg_arena *buddy)
 	return 0;
 }
 
-__weak u64 buddy_chunk_alloc(buddy_chunk_t __arg_arena *chunk, int order_req)
+__weak u64 buddy_chunk_alloc(struct buddy_chunk __arena *chunk, int order_req)
 {
-	buddy_header_t *header, *tmp_header, *next_header;
+	struct buddy_header __arena *header, *tmp_header, *next_header;
 	u32 idx, tmpidx, retidx;
 	u64 address;
 	u64 order = 0;
@@ -709,9 +709,9 @@ __weak u64 buddy_chunk_alloc(buddy_chunk_t __arg_arena *chunk, int order_req)
 }
 
 /* Scan the existing chunks for available memory. */
-static u64 buddy_alloc_from_existing_chunks(buddy_t *buddy, int order)
+static u64 buddy_alloc_from_existing_chunks(struct buddy __arena *buddy, int order)
 {
-	buddy_chunk_t *chunk;
+	struct buddy_chunk __arena *chunk;
 	u64 address;
 
 	for (chunk = buddy->first_chunk; chunk != NULL && can_loop;
@@ -728,7 +728,7 @@ static u64 buddy_alloc_from_existing_chunks(buddy_t *buddy, int order)
  * Try an allocation from a newly allocated chunk. Also
  * incorporate the chunk into the linked list.
  */
-static u64 buddy_alloc_from_new_chunk(buddy_t *buddy, buddy_chunk_t *chunk, int order)
+static u64 buddy_alloc_from_new_chunk(struct buddy __arena *buddy, struct buddy_chunk __arena *chunk, int order)
 {
 	u64 address;
 
@@ -750,10 +750,10 @@ static u64 buddy_alloc_from_new_chunk(buddy_t *buddy, buddy_chunk_t *chunk, int
 	return (u64)address;
 }
 __weak
-u64 buddy_alloc_internal(buddy_t __arg_arena *buddy, size_t size)
+u64 buddy_alloc_internal(struct buddy __arena *buddy, size_t size)
 {
-	buddy_chunk_t *chunk;
 	u64 address = (u64)NULL;
+	struct buddy_chunk __arena *chunk;
 	int order;
 
 	if (!buddy)
@@ -788,20 +788,20 @@ u64 buddy_alloc_internal(buddy_t __arg_arena *buddy, size_t size)
 	 * data is smaller than the header, we must poison any
 	 * unused bytes that were part of the header.
 	 */
-	if (size < BUDDY_HEADER_OFF + sizeof(buddy_header_t))
-		asan_poison((u8 __arena *)address + BUDDY_HEADER_OFF,
-			    BUDDY_POISONED, sizeof(buddy_header_t));
+	if (size < BUDDY_HEADER_OFF + sizeof(struct buddy_header __arena))
+		asan_poison((u8 __arena *)address + BUDDY_HEADER_OFF, BUDDY_POISONED,
+			    sizeof(struct buddy_header __arena));
 
 	asan_unpoison((u8 __arena *)address, size);
 
 	return address;
 }
 
-static __always_inline int buddy_free_unlocked(buddy_t *buddy, u64 addr)
+static __always_inline int buddy_free_unlocked(struct buddy __arena *buddy, u64 addr)
 {
-	buddy_header_t *header, *buddy_header;
+	struct buddy_header __arena *header, *buddy_header;
 	u64 idx, buddy_idx, tmp_idx;
-	buddy_chunk_t *chunk;
+	struct buddy_chunk __arena *chunk;
 	bool allocated;
 	u8 order;
 	int ret;
@@ -878,7 +878,7 @@ static __always_inline int buddy_free_unlocked(buddy_t *buddy, u64 addr)
 	return 0;
 }
 
-__weak int buddy_free_internal(buddy_t __arg_arena *buddy, u64 addr)
+__weak int buddy_free(struct buddy __arena *buddy, void __arena *addr)
 {
 	int ret;
 
@@ -893,7 +893,7 @@ __weak int buddy_free_internal(buddy_t __arg_arena *buddy, u64 addr)
 	if (ret)
 		return ret;
 
-	ret = buddy_free_unlocked(buddy, addr);
+	ret = buddy_free_unlocked(buddy, (u64)addr);
 
 	buddy_unlock(buddy);
 
diff --git a/tools/testing/selftests/bpf/libarena/src/common.bpf.c b/tools/testing/selftests/bpf/libarena/src/common.bpf.c
index 544bf9e1cb38..ec9de29e6f3e 100644
--- a/tools/testing/selftests/bpf/libarena/src/common.bpf.c
+++ b/tools/testing/selftests/bpf/libarena/src/common.bpf.c
@@ -6,7 +6,7 @@
 
 const volatile u32 zero = 0;
 
-buddy_t buddy;
+struct buddy __arena buddy;
 
 int arena_fls(__u64 word)
 {
@@ -43,9 +43,9 @@ __weak u64 arena_malloc_internal(size_t size)
 	return buddy_alloc_internal(&buddy, size);
 }
 
-__weak void arena_free(void __arg_arena __arena *ptr)
+__weak void arena_free(void __arena *ptr)
 {
-	buddy_free_internal(&buddy, (u64)ptr);
+	buddy_free(&buddy, ptr);
 }
 
 
diff --git a/tools/testing/selftests/bpf/progs/arena_spin_lock.c b/tools/testing/selftests/bpf/progs/arena_spin_lock.c
index 7236d92d382f..cf7cda79c16c 100644
--- a/tools/testing/selftests/bpf/progs/arena_spin_lock.c
+++ b/tools/testing/selftests/bpf/progs/arena_spin_lock.c
@@ -4,6 +4,7 @@
 #include <bpf/bpf_tracing.h>
 #include <bpf/bpf_helpers.h>
 #include "bpf_misc.h"
+#include <bpf_arena_common.h>
 #include <bpf_arena_spin_lock.h>
 
 struct {
-- 
2.54.0


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

* [PATCH bpf-next v2 5/5] selftests/bpf: libarena: Directly return arena pointers from functions
  2026-05-30  0:22 [PATCH bpf-next v2 0/5] bpf: Minimize annotations for arena programs Emil Tsalapatis
                   ` (3 preceding siblings ...)
  2026-05-30  0:22 ` [PATCH bpf-next v2 4/5] selftests/bpf: Remove __arg_arena from the codebase Emil Tsalapatis
@ 2026-05-30  0:22 ` Emil Tsalapatis
  2026-06-01 19:07   ` Eduard Zingerman
  4 siblings, 1 reply; 20+ messages in thread
From: Emil Tsalapatis @ 2026-05-30  0:22 UTC (permalink / raw)
  To: bpf
  Cc: ast, andrii, memxor, daniel, eddyz87, song, mattbobrowski,
	Emil Tsalapatis

Now that the __arena annotation includes a BTF type tag, and the
verifier can identify arena pointers at BTF loading time, return
arena pointers as their true type instead of casting to u64. Remove the
preprocessor typecast wrappers used to hide this from the caller.

Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com>
---
 .../bpf/libarena/include/libarena/buddy.h     |  3 +--
 .../bpf/libarena/include/libarena/common.h    |  3 +--
 .../selftests/bpf/libarena/src/buddy.bpf.c    | 20 +++++++++----------
 .../selftests/bpf/libarena/src/common.bpf.c   |  4 ++--
 4 files changed, 14 insertions(+), 16 deletions(-)

diff --git a/tools/testing/selftests/bpf/libarena/include/libarena/buddy.h b/tools/testing/selftests/bpf/libarena/include/libarena/buddy.h
index 4d57fc1b5c26..528c69a1f38e 100644
--- a/tools/testing/selftests/bpf/libarena/include/libarena/buddy.h
+++ b/tools/testing/selftests/bpf/libarena/include/libarena/buddy.h
@@ -76,7 +76,6 @@ struct buddy {
 int buddy_init(struct buddy __arena *buddy);
 int buddy_destroy(struct buddy __arena *buddy);
 int buddy_free(struct buddy __arena *buddy, void __arena *free);
-u64 buddy_alloc_internal(struct buddy __arena *buddy, size_t size);
-#define buddy_alloc(alloc, size) ((void __arena *)buddy_alloc_internal((alloc), (size)))
+void __arena *buddy_alloc(struct buddy __arena *buddy, size_t size);
 
 #endif /* __BPF__  */
diff --git a/tools/testing/selftests/bpf/libarena/include/libarena/common.h b/tools/testing/selftests/bpf/libarena/include/libarena/common.h
index ca1a6c1d6477..a3eb1641ac36 100644
--- a/tools/testing/selftests/bpf/libarena/include/libarena/common.h
+++ b/tools/testing/selftests/bpf/libarena/include/libarena/common.h
@@ -48,8 +48,7 @@ extern volatile u64 asan_violated;
 
 int arena_fls(__u64 word);
 
-u64 arena_malloc_internal(size_t size);
-#define arena_malloc(size) ((void __arena *)arena_malloc_internal((size)))
+void __arena *arena_malloc(size_t size);
 void arena_free(void __arena *ptr);
 
 /*
diff --git a/tools/testing/selftests/bpf/libarena/src/buddy.bpf.c b/tools/testing/selftests/bpf/libarena/src/buddy.bpf.c
index f4ed4c3abb4b..c674ee5cfcc1 100644
--- a/tools/testing/selftests/bpf/libarena/src/buddy.bpf.c
+++ b/tools/testing/selftests/bpf/libarena/src/buddy.bpf.c
@@ -750,25 +750,25 @@ static u64 buddy_alloc_from_new_chunk(struct buddy __arena *buddy, struct buddy_
 	return (u64)address;
 }
 __weak
-u64 buddy_alloc_internal(struct buddy __arena *buddy, size_t size)
+void __arena *buddy_alloc(struct buddy __arena *buddy, size_t size)
 {
-	u64 address = (u64)NULL;
+	void __arena *address = NULL;
 	struct buddy_chunk __arena *chunk;
 	int order;
 
 	if (!buddy)
-		return (u64)NULL;
+		return NULL;
 
 	order = size_to_order(size);
 	if (order >= BUDDY_CHUNK_NUM_ORDERS || order < 0) {
 		arena_stderr("invalid order %d (sz %lu)\n", order, size);
-		return (u64)NULL;
+		return NULL;
 	}
 
 	if (buddy_lock(buddy))
-		return (u64)NULL;
+		return NULL;
 
-	address = buddy_alloc_from_existing_chunks(buddy, order);
+	address = (u8 __arena *)buddy_alloc_from_existing_chunks(buddy, order);
 	buddy_unlock(buddy);
 	if (address)
 		goto done;
@@ -776,12 +776,12 @@ u64 buddy_alloc_internal(struct buddy __arena *buddy, size_t size)
 	/* Get a new chunk. */
 	chunk = buddy_chunk_get(buddy);
 	if (chunk)
-		address = buddy_alloc_from_new_chunk(buddy, chunk, order);
+		address = (u8 __arena *)buddy_alloc_from_new_chunk(buddy, chunk, order);
 
 done:
 	/* If we failed to allocate memory, return NULL. */
 	if (!address)
-		return (u64)NULL;
+		return NULL;
 
 	/*
 	 * Unpoison exactly the amount of bytes requested. If the
@@ -789,10 +789,10 @@ u64 buddy_alloc_internal(struct buddy __arena *buddy, size_t size)
 	 * unused bytes that were part of the header.
 	 */
 	if (size < BUDDY_HEADER_OFF + sizeof(struct buddy_header __arena))
-		asan_poison((u8 __arena *)address + BUDDY_HEADER_OFF, BUDDY_POISONED,
+		asan_poison(address + BUDDY_HEADER_OFF, BUDDY_POISONED,
 			    sizeof(struct buddy_header __arena));
 
-	asan_unpoison((u8 __arena *)address, size);
+	asan_unpoison(address, size);
 
 	return address;
 }
diff --git a/tools/testing/selftests/bpf/libarena/src/common.bpf.c b/tools/testing/selftests/bpf/libarena/src/common.bpf.c
index ec9de29e6f3e..50be57213dfb 100644
--- a/tools/testing/selftests/bpf/libarena/src/common.bpf.c
+++ b/tools/testing/selftests/bpf/libarena/src/common.bpf.c
@@ -38,9 +38,9 @@ __weak int arena_buddy_reset(void)
 	return buddy_init(&buddy);
 }
 
-__weak u64 arena_malloc_internal(size_t size)
+__weak void __arena *arena_malloc(size_t size)
 {
-	return buddy_alloc_internal(&buddy, size);
+	return buddy_alloc(&buddy, size);
 }
 
 __weak void arena_free(void __arena *ptr)
-- 
2.54.0


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

* Re: [PATCH bpf-next v2 2/5] verifier: parse BTF type tags for function arguments
  2026-05-30  0:22 ` [PATCH bpf-next v2 2/5] verifier: parse BTF type tags for function arguments Emil Tsalapatis
@ 2026-05-30  0:59   ` sashiko-bot
  2026-06-01  5:12     ` Emil Tsalapatis
  2026-06-01 18:37   ` Eduard Zingerman
  1 sibling, 1 reply; 20+ messages in thread
From: sashiko-bot @ 2026-05-30  0:59 UTC (permalink / raw)
  To: Emil Tsalapatis; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] btf_scan_type_tags incorrectly rejects valid BPF programs containing unrecognized type tags (e.g., __user, __rcu) by returning -EOPNOTSUPP.
- [High] btf_scan_type_tags fails to discover valid type tags if they are separated from the pointer by other BTF modifiers (like CONST, VOLATILE, or TYPEDEF).
--

commit c938604636d812339744a9ba91c4659c3d79392c
Author: Emil Tsalapatis <emil@etsalapatis.com>

verifier: parse BTF type tags for function arguments

This patch extracts BTF decl tag parsing into a new function and
introduces type tag parsing for function arguments, specifically
looking for the arena type tag on pointers.

> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index 17d4ab0a82068..c6a930aca67ec 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c

[ ... ]

> +static int btf_scan_type_tags(struct bpf_verifier_env *env,
> +			      const struct btf *btf, u32 type_id,
> +			      u32 *tags)
> +{
> +	const struct btf_type *t;
> +
> +	/* Find the first pointer type in the chain. */
> +	t = btf_type_skip_modifiers(btf, type_id, NULL);
> +	if (!t || !btf_type_is_ptr(t))
> +		return 0;
> +
> +	/* We got a pointer, get all associated type tags. */
> +	t = btf_type_by_id(btf, t->type);
> +	while (t && btf_type_is_type_tag(t)) {

[Severity: High]
Does this loop terminate prematurely if the type tag is preceded by other
modifiers like CONST, VOLATILE, or TYPEDEF?

If the target type of the pointer is structured as:
PTR -> CONST -> TYPE_TAG("arena") -> INT

It appears btf_type_is_type_tag(t) would evaluate to false at the CONST
modifier, causing the loop to exit and valid arena tags to be missed
entirely.

> +		const char *tag = __btf_name_by_offset(btf, t->name_off);
> +
> +		if (strcmp(tag, "arena") == 0) {
> +			*tags |= ARG_TAG_ARENA;
> +		} else {
> +			bpf_log(&env->log, "function signature member has unsupported type tag '%s'\n",
> +				tag);
> +			return -EOPNOTSUPP;
> +		}

[Severity: High]
Will this unconditionally reject valid BPF programs that contain
unrecognized type tags like __user or __rcu?

Prior to this change, standard kernel type tags on pointer arguments were
naturally skipped by the verifier. Returning -EOPNOTSUPP here for anything
other than arena seems like it might cause a backward compatibility
regression for existing global subprograms that accept standard tagged
pointers.

> +
> +		t = btf_type_by_id(btf, t->type);
> +	}
> +
> +	return 0;
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260530002259.4505-1-emil@etsalapatis.com?part=2

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

* Re: [PATCH bpf-next v2 3/5] bpf: Allow subprogs to return arena pointers
  2026-05-30  0:22 ` [PATCH bpf-next v2 3/5] bpf: Allow subprogs to return arena pointers Emil Tsalapatis
@ 2026-05-30  4:54   ` sashiko-bot
  2026-06-01  5:09     ` Emil Tsalapatis
  2026-06-01 19:01   ` Eduard Zingerman
  1 sibling, 1 reply; 20+ messages in thread
From: sashiko-bot @ 2026-05-30  4:54 UTC (permalink / raw)
  To: Emil Tsalapatis; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Global subprograms returning arena pointers have their return value unconditionally marked as a scalar at the call site.
- [Medium] The verifier bypasses BTF type enforcement for global subprogram return values.
- [Low] The error message logged when a global function has an invalid return type is outdated and confusing.
--

commit 0192bc82392a7d1bde4b6ab66bef8ece2f90c7cf
Author: Emil Tsalapatis <emil@etsalapatis.com>

bpf: Allow subprogs to return arena pointers

This patch expands the verifier to permit returning an arena pointer to the
caller from a subprogram. The main subprog is still not allowed to return
an arena pointer.

> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index c6a930aca67ec..7a8101879f84d 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c

[ ... ]

> @@ -7963,17 +7993,15 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
[ ... ]
> -	if (!btf_type_is_void(t) && !btf_type_is_int(t) && !btf_is_any_enum(t)) {
> -		if (!is_global)
> -			return -EINVAL;
> -		bpf_log(log,
> -			"Global function %s() return value not void or scalar. "
> -			"Only those are supported.\n",
> -			tname);
> +
> +	err = btf_validate_return_type(env, btf, t, subprog);
> +	if (err) {
> +		if (is_global) {
> +			bpf_log(log,
> +				"Global function %s() return value not void or scalar. "
> +				"Only those are supported.\n",
> +				tname);
> +		}
>  		return -EINVAL;
>  	}

[Severity: Low]
Since arena pointers are now supported, should this error message be
updated to avoid confusing users? It still states that only void or
scalars are supported.

> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index c8d980fdd7099..64d7954e93ea7 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -9375,7 +9375,7 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
>  		clear_caller_saved_regs(env, caller->regs);
>  		invalidate_outgoing_stack_args(env, cur_func(env));
>  
> -		/* All non-void global functions return a 64-bit SCALAR_VALUE. */
> +		/* All non-void global functions return a 64-bit SCALAR_VALUE or PTR_TO_ARENA. */
>  		if (!subprog_returns_void(env, subprog)) {
>  			mark_reg_unknown(env, caller->regs, BPF_REG_0);
>  			caller->regs[BPF_REG_0].subreg_def = DEF_NOT_SUBREG;
>  		}

[Severity: High]
Does this unconditionally mark the caller's R0 register as a SCALAR_VALUE?

When returning from a global subprogram, mark_reg_unknown() clears the
register state. If the subprogram actually returned an arena pointer,
it seems the caller will receive an untracked scalar instead. Any attempt
by the caller to dereference it would then fail verification.

Should we inspect the subprogram's BTF return type here to dynamically
set caller->regs[BPF_REG_0].type = PTR_TO_ARENA when appropriate?

> @@ -16645,6 +16645,10 @@ static int check_global_subprog_return_code(struct bpf_verifier_env *env)
>  	if (err)
>  		return err;
>  
> +	/* Pointers to arena are safe to pass between subprograms. */
> +	if (is_arena_reg(env, BPF_REG_0))
> +		return 0;
> +
>  	if (is_pointer_value(env, BPF_REG_0)) {
>  		verbose(env, "R%d leaks addr as return value\n", BPF_REG_0);
>  		return -EACCES;

[Severity: Medium]
Does this bypass BTF type enforcement for the return value?

By just checking if R0 is an arena register and returning 0, a global
subprogram declared in BTF as returning an int could successfully return
a PTR_TO_ARENA. Conversely, a subprogram declared as returning an arena
pointer could successfully return a SCALAR_VALUE, since the fallback scalar
check later in the function would allow it.

Should we verify that the actual register type matches the subprogram's
BTF return type signature?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260530002259.4505-1-emil@etsalapatis.com?part=3

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

* Re: [PATCH bpf-next v2 4/5] selftests/bpf: Remove __arg_arena from the codebase
  2026-05-30  0:22 ` [PATCH bpf-next v2 4/5] selftests/bpf: Remove __arg_arena from the codebase Emil Tsalapatis
@ 2026-05-30  5:03   ` sashiko-bot
  2026-05-31  5:18     ` Alexei Starovoitov
  2026-06-01 19:06   ` Eduard Zingerman
  1 sibling, 1 reply; 20+ messages in thread
From: sashiko-bot @ 2026-05-30  5:03 UTC (permalink / raw)
  To: Emil Tsalapatis; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Incomplete removal of `__arg_arena` annotation
--

commit 936642e9509313ae2959c6a3c4255651cca7040f
Author: Emil Tsalapatis <emil@etsalapatis.com>

selftests/bpf: Remove __arg_arena from the codebase

This commit removes the __arg_arena annotation from the codebase because
it has been subsumed by the __arena BTF type tag. The typedefs previously
used to minimize __arena annotations are removed, and the pointer types
are directly annotated when declaring variables instead.

> diff --git a/tools/testing/selftests/bpf/bpf_arena_htab.h b/tools/testing/selftests/bpf/bpf_arena_htab.h
> index acc01a876668e..eb80d67c38708 100644
> --- a/tools/testing/selftests/bpf/bpf_arena_htab.h
> +++ b/tools/testing/selftests/bpf/bpf_arena_htab.h

[ ... ]

> @@ -53,7 +52,7 @@ static int htab_hash(int key)
>  	return key;
>  }
>  
> -__weak int htab_lookup_elem(htab_t *htab __arg_arena, int key)
> +__weak int htab_lookup_elem(struct htab __arena *htab __arg_arena, int key)

[Severity: Low]
Is the __arg_arena annotation incorrectly retained here? The commit
message mentions removing __arg_arena from the codebase, but it seems to
still be present in the signature of htab_lookup_elem().

Additionally, is it possible that an empty macro definition for __arg_arena
still remains in the userspace section of bpf_arena_common.h?

>  {
>  	hashtab_elem_t *l_old;
>  	arena_list_head_t *head;

[ ... ]

> @@ -66,7 +65,7 @@ __weak int htab_lookup_elem(htab_t *htab __arg_arena, int key)
>  	return 0;
>  }
>  
> -__weak int htab_update_elem(htab_t *htab __arg_arena, int key, int value)
> +__weak int htab_update_elem(struct htab __arena *htab __arg_arena, int key, int value)

[Severity: Low]
Was __arg_arena intended to be removed from the signature of
htab_update_elem() here as well?

>  {
>  	hashtab_elem_t *l_new = NULL, *l_old;
>  	arena_list_head_t *head;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260530002259.4505-1-emil@etsalapatis.com?part=4

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

* Re: [PATCH bpf-next v2 4/5] selftests/bpf: Remove __arg_arena from the codebase
  2026-05-30  5:03   ` sashiko-bot
@ 2026-05-31  5:18     ` Alexei Starovoitov
  2026-06-01  5:03       ` Emil Tsalapatis
  0 siblings, 1 reply; 20+ messages in thread
From: Alexei Starovoitov @ 2026-05-31  5:18 UTC (permalink / raw)
  To: sashiko-reviews, Emil Tsalapatis; +Cc: bpf

On Fri May 29, 2026 at 10:03 PM PDT, sashiko-bot wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Low] Incomplete removal of `__arg_arena` annotation
> --
>
> commit 936642e9509313ae2959c6a3c4255651cca7040f
> Author: Emil Tsalapatis <emil@etsalapatis.com>
>
> selftests/bpf: Remove __arg_arena from the codebase
>
> This commit removes the __arg_arena annotation from the codebase because
> it has been subsumed by the __arena BTF type tag. The typedefs previously
> used to minimize __arena annotations are removed, and the pointer types
> are directly annotated when declaring variables instead.
>
>> diff --git a/tools/testing/selftests/bpf/bpf_arena_htab.h b/tools/testing/selftests/bpf/bpf_arena_htab.h
>> index acc01a876668e..eb80d67c38708 100644
>> --- a/tools/testing/selftests/bpf/bpf_arena_htab.h
>> +++ b/tools/testing/selftests/bpf/bpf_arena_htab.h
>
> [ ... ]
>
>> @@ -53,7 +52,7 @@ static int htab_hash(int key)
>>  	return key;
>>  }
>>  
>> -__weak int htab_lookup_elem(htab_t *htab __arg_arena, int key)
>> +__weak int htab_lookup_elem(struct htab __arena *htab __arg_arena, int key)
>
> [Severity: Low]
> Is the __arg_arena annotation incorrectly retained here? The commit
> message mentions removing __arg_arena from the codebase, but it seems to
> still be present in the signature of htab_lookup_elem().

Spotted this manually as well.

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

* Re: [PATCH bpf-next v2 4/5] selftests/bpf: Remove __arg_arena from the codebase
  2026-05-31  5:18     ` Alexei Starovoitov
@ 2026-06-01  5:03       ` Emil Tsalapatis
  0 siblings, 0 replies; 20+ messages in thread
From: Emil Tsalapatis @ 2026-06-01  5:03 UTC (permalink / raw)
  To: Alexei Starovoitov, sashiko-reviews, Emil Tsalapatis; +Cc: bpf

On Sun May 31, 2026 at 1:18 AM EDT, Alexei Starovoitov wrote:
> On Fri May 29, 2026 at 10:03 PM PDT, sashiko-bot wrote:
>> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
>> - [Low] Incomplete removal of `__arg_arena` annotation
>> --
>>
>> commit 936642e9509313ae2959c6a3c4255651cca7040f
>> Author: Emil Tsalapatis <emil@etsalapatis.com>
>>
>> selftests/bpf: Remove __arg_arena from the codebase
>>
>> This commit removes the __arg_arena annotation from the codebase because
>> it has been subsumed by the __arena BTF type tag. The typedefs previously
>> used to minimize __arena annotations are removed, and the pointer types
>> are directly annotated when declaring variables instead.
>>
>>> diff --git a/tools/testing/selftests/bpf/bpf_arena_htab.h b/tools/testing/selftests/bpf/bpf_arena_htab.h
>>> index acc01a876668e..eb80d67c38708 100644
>>> --- a/tools/testing/selftests/bpf/bpf_arena_htab.h
>>> +++ b/tools/testing/selftests/bpf/bpf_arena_htab.h
>>
>> [ ... ]
>>
>>> @@ -53,7 +52,7 @@ static int htab_hash(int key)
>>>  	return key;
>>>  }
>>>  
>>> -__weak int htab_lookup_elem(htab_t *htab __arg_arena, int key)
>>> +__weak int htab_lookup_elem(struct htab __arena *htab __arg_arena, int key)
>>
>> [Severity: Low]
>> Is the __arg_arena annotation incorrectly retained here? The commit
>> message mentions removing __arg_arena from the codebase, but it seems to
>> still be present in the signature of htab_lookup_elem().
>
> Spotted this manually as well.

Valid issue, this use was originally left in because arena_htab has a
selftests for FORCE_ASM mode that turns the __arena into a no-op. I will
make sure __arena retains the type tag even with that option on, which
should make the test pass.

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

* Re: [PATCH bpf-next v2 3/5] bpf: Allow subprogs to return arena pointers
  2026-05-30  4:54   ` sashiko-bot
@ 2026-06-01  5:09     ` Emil Tsalapatis
  0 siblings, 0 replies; 20+ messages in thread
From: Emil Tsalapatis @ 2026-06-01  5:09 UTC (permalink / raw)
  To: sashiko-reviews, Emil Tsalapatis; +Cc: bpf

On Sat May 30, 2026 at 12:54 AM EDT, sashiko-bot wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
> - [High] Global subprograms returning arena pointers have their return value unconditionally marked as a scalar at the call site.
> - [Medium] The verifier bypasses BTF type enforcement for global subprogram return values.
> - [Low] The error message logged when a global function has an invalid return type is outdated and confusing.
> --
>
> commit 0192bc82392a7d1bde4b6ab66bef8ece2f90c7cf
> Author: Emil Tsalapatis <emil@etsalapatis.com>
>
> bpf: Allow subprogs to return arena pointers
>
> This patch expands the verifier to permit returning an arena pointer to the
> caller from a subprogram. The main subprog is still not allowed to return
> an arena pointer.
>
>> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
>> index c6a930aca67ec..7a8101879f84d 100644
>> --- a/kernel/bpf/btf.c
>> +++ b/kernel/bpf/btf.c
>
> [ ... ]
>
>> @@ -7963,17 +7993,15 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
> [ ... ]
>> -	if (!btf_type_is_void(t) && !btf_type_is_int(t) && !btf_is_any_enum(t)) {
>> -		if (!is_global)
>> -			return -EINVAL;
>> -		bpf_log(log,
>> -			"Global function %s() return value not void or scalar. "
>> -			"Only those are supported.\n",
>> -			tname);
>> +
>> +	err = btf_validate_return_type(env, btf, t, subprog);
>> +	if (err) {
>> +		if (is_global) {
>> +			bpf_log(log,
>> +				"Global function %s() return value not void or scalar. "
>> +				"Only those are supported.\n",
>> +				tname);
>> +		}
>>  		return -EINVAL;
>>  	}
>
> [Severity: Low]
> Since arena pointers are now supported, should this error message be
> updated to avoid confusing users? It still states that only void or
> scalars are supported.
>

Not necessary imo, the error is there for when things go wrong. Nobody's
going to read it and go "I should change my program to not return arena
pointers." If they're inspecting the verifier source, they have the
logic for allowing __arena tagged pointers in front of them.

>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>> index c8d980fdd7099..64d7954e93ea7 100644
>> --- a/kernel/bpf/verifier.c
>> +++ b/kernel/bpf/verifier.c
>> @@ -9375,7 +9375,7 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
>>  		clear_caller_saved_regs(env, caller->regs);
>>  		invalidate_outgoing_stack_args(env, cur_func(env));
>>  
>> -		/* All non-void global functions return a 64-bit SCALAR_VALUE. */
>> +		/* All non-void global functions return a 64-bit SCALAR_VALUE or PTR_TO_ARENA. */
>>  		if (!subprog_returns_void(env, subprog)) {
>>  			mark_reg_unknown(env, caller->regs, BPF_REG_0);
>>  			caller->regs[BPF_REG_0].subreg_def = DEF_NOT_SUBREG;
>>  		}
>
> [Severity: High]
> Does this unconditionally mark the caller's R0 register as a SCALAR_VALUE?
>
> When returning from a global subprogram, mark_reg_unknown() clears the
> register state. If the subprogram actually returned an arena pointer,
> it seems the caller will receive an untracked scalar instead. Any attempt
> by the caller to dereference it would then fail verification.
>
> Should we inspect the subprogram's BTF return type here to dynamically
> set caller->regs[BPF_REG_0].type = PTR_TO_ARENA when appropriate?

I will change the comment back because it's actually inaccurate.
(Somewhat confusingly) we're returning arena pointers but not
PTR_TO_ARENA - the pointer returned is a SCALAR_VALUE. THe bytecode will
turn this into a PTR_TO_ARENA with an address space cast just fine.

>
>> @@ -16645,6 +16645,10 @@ static int check_global_subprog_return_code(struct bpf_verifier_env *env)
>>  	if (err)
>>  		return err;
>>  
>> +	/* Pointers to arena are safe to pass between subprograms. */
>> +	if (is_arena_reg(env, BPF_REG_0))
>> +		return 0;
>> +
>>  	if (is_pointer_value(env, BPF_REG_0)) {
>>  		verbose(env, "R%d leaks addr as return value\n", BPF_REG_0);
>>  		return -EACCES;
>
> [Severity: Medium]
> Does this bypass BTF type enforcement for the return value?
>
> By just checking if R0 is an arena register and returning 0, a global
> subprogram declared in BTF as returning an int could successfully return
> a PTR_TO_ARENA. Conversely, a subprogram declared as returning an arena
> pointer could successfully return a SCALAR_VALUE, since the fallback scalar
> check later in the function would allow it.
>
> Should we verify that the actual register type matches the subprogram's
> BTF return type signature?

Same thing, fixing the comment should make this clear that this is a
non-concern.

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

* Re: [PATCH bpf-next v2 2/5] verifier: parse BTF type tags for function arguments
  2026-05-30  0:59   ` sashiko-bot
@ 2026-06-01  5:12     ` Emil Tsalapatis
  0 siblings, 0 replies; 20+ messages in thread
From: Emil Tsalapatis @ 2026-06-01  5:12 UTC (permalink / raw)
  To: sashiko-reviews, Emil Tsalapatis; +Cc: bpf

On Fri May 29, 2026 at 8:59 PM EDT, sashiko-bot wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [High] btf_scan_type_tags incorrectly rejects valid BPF programs containing unrecognized type tags (e.g., __user, __rcu) by returning -EOPNOTSUPP.
> - [High] btf_scan_type_tags fails to discover valid type tags if they are separated from the pointer by other BTF modifiers (like CONST, VOLATILE, or TYPEDEF).
> --
>
> commit c938604636d812339744a9ba91c4659c3d79392c
> Author: Emil Tsalapatis <emil@etsalapatis.com>
>
> verifier: parse BTF type tags for function arguments
>
> This patch extracts BTF decl tag parsing into a new function and
> introduces type tag parsing for function arguments, specifically
> looking for the arena type tag on pointers.
>
>> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
>> index 17d4ab0a82068..c6a930aca67ec 100644
>> --- a/kernel/bpf/btf.c
>> +++ b/kernel/bpf/btf.c
>
> [ ... ]
>
>> +static int btf_scan_type_tags(struct bpf_verifier_env *env,
>> +			      const struct btf *btf, u32 type_id,
>> +			      u32 *tags)
>> +{
>> +	const struct btf_type *t;
>> +
>> +	/* Find the first pointer type in the chain. */
>> +	t = btf_type_skip_modifiers(btf, type_id, NULL);
>> +	if (!t || !btf_type_is_ptr(t))
>> +		return 0;
>> +
>> +	/* We got a pointer, get all associated type tags. */
>> +	t = btf_type_by_id(btf, t->type);
>> +	while (t && btf_type_is_type_tag(t)) {
>
> [Severity: High]
> Does this loop terminate prematurely if the type tag is preceded by other
> modifiers like CONST, VOLATILE, or TYPEDEF?
>
> If the target type of the pointer is structured as:
> PTR -> CONST -> TYPE_TAG("arena") -> INT
>
> It appears btf_type_is_type_tag(t) would evaluate to false at the CONST
> modifier, causing the loop to exit and valid arena tags to be missed
> entirely.
>

Fair point, we can make the loop continue until it finds either a base
type or a pointer type.

>> +		const char *tag = __btf_name_by_offset(btf, t->name_off);
>> +
>> +		if (strcmp(tag, "arena") == 0) {
>> +			*tags |= ARG_TAG_ARENA;
>> +		} else {
>> +			bpf_log(&env->log, "function signature member has unsupported type tag '%s'\n",
>> +				tag);
>> +			return -EOPNOTSUPP;
>> +		}
>
> [Severity: High]
> Will this unconditionally reject valid BPF programs that contain
> unrecognized type tags like __user or __rcu?
>
> Prior to this change, standard kernel type tags on pointer arguments were
> naturally skipped by the verifier. Returning -EOPNOTSUPP here for anything
> other than arena seems like it might cause a backward compatibility
> regression for existing global subprograms that accept standard tagged
> pointers.
>

Right now imo we don't want global BPF subprogs to be able to return those
variable types in the first place. We can expand this if we ever do.

>> +
>> +		t = btf_type_by_id(btf, t->type);
>> +	}
>> +
>> +	return 0;
>> +}


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

* Re: [PATCH bpf-next v2 2/5] verifier: parse BTF type tags for function arguments
  2026-05-30  0:22 ` [PATCH bpf-next v2 2/5] verifier: parse BTF type tags for function arguments Emil Tsalapatis
  2026-05-30  0:59   ` sashiko-bot
@ 2026-06-01 18:37   ` Eduard Zingerman
  2026-06-01 19:13     ` Emil Tsalapatis
  1 sibling, 1 reply; 20+ messages in thread
From: Eduard Zingerman @ 2026-06-01 18:37 UTC (permalink / raw)
  To: Emil Tsalapatis, bpf; +Cc: ast, andrii, memxor, daniel, song, mattbobrowski

On Fri, 2026-05-29 at 20:22 -0400, Emil Tsalapatis wrote:
> The BTF parsing logic for function arguments goes through
> the arguments' decl tags, but does not go into their type
> tags. Add type tag parsing for function arguments.
> 
> Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com>
> ---

Acked-by: Eduard Zingerman <eddyz87@gmail.com>

[...]

> +static int btf_scan_type_tags(struct bpf_verifier_env *env,
> +			      const struct btf *btf, u32 type_id,
> +			      u32 *tags)
> +{
> +	const struct btf_type *t;
> +
> +	/* Find the first pointer type in the chain. */
> +	t = btf_type_skip_modifiers(btf, type_id, NULL);
> +	if (!t || !btf_type_is_ptr(t))
> +		return 0;

Nit: do we want to report -EOPNOTSUPP if type tag is attached to a non-pointer type?

> +
> +	/* We got a pointer, get all associated type tags. */
> +	t = btf_type_by_id(btf, t->type);
> +	while (t && btf_type_is_type_tag(t)) {
> +		const char *tag = __btf_name_by_offset(btf, t->name_off);
> +
> +		if (strcmp(tag, "arena") == 0) {
> +			*tags |= ARG_TAG_ARENA;
> +		} else {
> +			bpf_log(&env->log, "function signature member has unsupported type tag '%s'\n",
> +				tag);
> +			return -EOPNOTSUPP;
> +		}
> +
> +		t = btf_type_by_id(btf, t->type);
> +	}
> +
> +	return 0;
> +}
> +

[...]

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

* Re: [PATCH bpf-next v2 3/5] bpf: Allow subprogs to return arena pointers
  2026-05-30  0:22 ` [PATCH bpf-next v2 3/5] bpf: Allow subprogs to return arena pointers Emil Tsalapatis
  2026-05-30  4:54   ` sashiko-bot
@ 2026-06-01 19:01   ` Eduard Zingerman
  2026-06-02  0:06     ` Emil Tsalapatis
  1 sibling, 1 reply; 20+ messages in thread
From: Eduard Zingerman @ 2026-06-01 19:01 UTC (permalink / raw)
  To: Emil Tsalapatis, bpf; +Cc: ast, andrii, memxor, daniel, song, mattbobrowski

On Fri, 2026-05-29 at 20:22 -0400, Emil Tsalapatis wrote:
> BPF subprogs currently only return void or scalar values. However,
> it is also safe to return arena pointers between subprogs in the same
> BPF program: Arena pointers are guaranteed to be safe for both programs
> at any point. Expand the verifier to permit returning an arena pointer
> to the caller.
> 
> The main subprog is still not allowed to return an arena pointer because
> arena pointers are internal to the BPF program, and the return values
> permitted for each main subprog depend on the program type anyway.
> 
> Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com>
> ---

Acked-by: Eduard Zingerman <eddyz87@gmail.com>

[...]

> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index c6a930aca67e..7a8101879f84 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c
> @@ -7880,6 +7880,36 @@ static int btf_scan_type_tags(struct bpf_verifier_env *env,
>  	return 0;
>  }
>  
> +/* 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)
> +{
> +	u32 tags = 0;
> +	int err;
> +
> +	err = btf_scan_type_tags(env, btf, t->type, &tags);
> +	if (err)
> +		return err;
> +	t = btf_type_by_id(btf, t->type);
> +
> +	while (btf_type_is_modifier(t))
> +		t = btf_type_by_id(btf, t->type);

Nit: btf_type_skip_modifiers().

> +
> +	/*
> +	 * We allow all subprogs except for the main one to return any kind of arena pointer.
> +	 * General arena variables are not allowed, since it makes no sense to return by value
> +	 * a variable that's on the heap in the first place.
> +	 */
> +	if (subprog && (tags & ARG_TAG_ARENA) && btf_type_is_ptr(t))
> +		return 0;
> +
> +	/* We always accept void or scalars. */
> +	if (btf_type_is_void(t) || btf_type_is_int(t) || btf_is_any_enum(t))
> +		return 0;
> +
> +	return -EOPNOTSUPP;
> +}
> +
>  /* Process BTF of a function to produce high-level expectation of function
>   * arguments (like ARG_PTR_TO_CTX, or ARG_PTR_TO_MEM, etc). This information
>   * is cached in subprog info for reuse.
> @@ -7963,17 +7993,15 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
>  			tname, nargs, MAX_BPF_FUNC_REG_ARGS);
>  		return -EINVAL;
>  	}
> -	/* check that function is void or returns int, exception cb also requires this */
> -	t = btf_type_by_id(btf, t->type);
> -	while (btf_type_is_modifier(t))
> -		t = btf_type_by_id(btf, t->type);
> -	if (!btf_type_is_void(t) && !btf_type_is_int(t) && !btf_is_any_enum(t)) {
> -		if (!is_global)
> -			return -EINVAL;
> -		bpf_log(log,
> -			"Global function %s() return value not void or scalar. "
> -			"Only those are supported.\n",
> -			tname);
> +
> +	err = btf_validate_return_type(env, btf, t, subprog);
> +	if (err) {
> +		if (is_global) {

I understand that this is how it was implemented previously,
but do we report a sane error if non-global function returns
non-void/int/enum?

> +			bpf_log(log,
> +				"Global function %s() return value not void or scalar. "
> +				"Only those are supported.\n",
> +				tname);
> +		}
>  		return -EINVAL;

Nit: 'return err'?

>  	}
>  

[...]

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

* Re: [PATCH bpf-next v2 4/5] selftests/bpf: Remove __arg_arena from the codebase
  2026-05-30  0:22 ` [PATCH bpf-next v2 4/5] selftests/bpf: Remove __arg_arena from the codebase Emil Tsalapatis
  2026-05-30  5:03   ` sashiko-bot
@ 2026-06-01 19:06   ` Eduard Zingerman
  1 sibling, 0 replies; 20+ messages in thread
From: Eduard Zingerman @ 2026-06-01 19:06 UTC (permalink / raw)
  To: Emil Tsalapatis, bpf; +Cc: ast, andrii, memxor, daniel, song, mattbobrowski

On Fri, 2026-05-29 at 20:22 -0400, Emil Tsalapatis wrote:
> Now that BPF __arg_arena has been subsumed by __arena, remove
> __arg_arena from the codebase. This way the user has one fewer
> annotation to worry about.
> 
> To remove __arg_arena we remove the typedefs we were previously
> using to minimize __arena annotations. This is because __arena
> now also includes a BTF type tag, which is ignored for non-pointer
> types. As a result, we cannot capture the whole __arena annotation
> inside a typedef and need to directly annotate the pointer type when
> declaring the variable.
> 
> The extra verbosity is worth it because the use of the __arena tag
> is intuitive to the programmer and removes the __arg_arena tag that
> has been a consistent source of confusion for users. The typedefs
> can be reintroduced later (without __arg_arena) once compilers start
> supporting BTF type tags for non-pointer types.
> 
> Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com>
> ---

Do we want to add a dedicated test in verifier_arena_globals1.c?

[...]

Acked-by: Eduard Zingerman <eddyz87@gmail.com>

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

* Re: [PATCH bpf-next v2 5/5] selftests/bpf: libarena: Directly return arena pointers from functions
  2026-05-30  0:22 ` [PATCH bpf-next v2 5/5] selftests/bpf: libarena: Directly return arena pointers from functions Emil Tsalapatis
@ 2026-06-01 19:07   ` Eduard Zingerman
  0 siblings, 0 replies; 20+ messages in thread
From: Eduard Zingerman @ 2026-06-01 19:07 UTC (permalink / raw)
  To: Emil Tsalapatis, bpf; +Cc: ast, andrii, memxor, daniel, song, mattbobrowski

On Fri, 2026-05-29 at 20:22 -0400, Emil Tsalapatis wrote:
> Now that the __arena annotation includes a BTF type tag, and the
> verifier can identify arena pointers at BTF loading time, return
> arena pointers as their true type instead of casting to u64. Remove the
> preprocessor typecast wrappers used to hide this from the caller.
> 
> Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com>
> ---

Acked-by: Eduard Zingerman <eddyz87@gmail.com>

[...]

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

* Re: [PATCH bpf-next v2 2/5] verifier: parse BTF type tags for function arguments
  2026-06-01 18:37   ` Eduard Zingerman
@ 2026-06-01 19:13     ` Emil Tsalapatis
  2026-06-01 19:14       ` Eduard Zingerman
  0 siblings, 1 reply; 20+ messages in thread
From: Emil Tsalapatis @ 2026-06-01 19:13 UTC (permalink / raw)
  To: Eduard Zingerman, Emil Tsalapatis, bpf
  Cc: ast, andrii, memxor, daniel, song, mattbobrowski

On Mon Jun 1, 2026 at 2:37 PM EDT, Eduard Zingerman wrote:
> On Fri, 2026-05-29 at 20:22 -0400, Emil Tsalapatis wrote:
>> The BTF parsing logic for function arguments goes through
>> the arguments' decl tags, but does not go into their type
>> tags. Add type tag parsing for function arguments.
>> 
>> Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com>
>> ---
>
> Acked-by: Eduard Zingerman <eddyz87@gmail.com>
>
> [...]
>
>> +static int btf_scan_type_tags(struct bpf_verifier_env *env,
>> +			      const struct btf *btf, u32 type_id,
>> +			      u32 *tags)
>> +{
>> +	const struct btf_type *t;
>> +
>> +	/* Find the first pointer type in the chain. */
>> +	t = btf_type_skip_modifiers(btf, type_id, NULL);
>> +	if (!t || !btf_type_is_ptr(t))
>> +		return 0;
>
> Nit: do we want to report -EOPNOTSUPP if type tag is attached to a non-pointer type?
>

Let's do that. Since compilers can't really produce that yet that would
only be the case for handcrafted BTF, right?

>> +
>> +	/* We got a pointer, get all associated type tags. */
>> +	t = btf_type_by_id(btf, t->type);
>> +	while (t && btf_type_is_type_tag(t)) {
>> +		const char *tag = __btf_name_by_offset(btf, t->name_off);
>> +
>> +		if (strcmp(tag, "arena") == 0) {
>> +			*tags |= ARG_TAG_ARENA;
>> +		} else {
>> +			bpf_log(&env->log, "function signature member has unsupported type tag '%s'\n",
>> +				tag);
>> +			return -EOPNOTSUPP;
>> +		}
>> +
>> +		t = btf_type_by_id(btf, t->type);
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>
> [...]


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

* Re: [PATCH bpf-next v2 2/5] verifier: parse BTF type tags for function arguments
  2026-06-01 19:13     ` Emil Tsalapatis
@ 2026-06-01 19:14       ` Eduard Zingerman
  0 siblings, 0 replies; 20+ messages in thread
From: Eduard Zingerman @ 2026-06-01 19:14 UTC (permalink / raw)
  To: Emil Tsalapatis, bpf; +Cc: ast, andrii, memxor, daniel, song, mattbobrowski

On Mon, 2026-06-01 at 15:13 -0400, Emil Tsalapatis wrote:
> On Mon Jun 1, 2026 at 2:37 PM EDT, Eduard Zingerman wrote:
> > On Fri, 2026-05-29 at 20:22 -0400, Emil Tsalapatis wrote:
> > > The BTF parsing logic for function arguments goes through
> > > the arguments' decl tags, but does not go into their type
> > > tags. Add type tag parsing for function arguments.
> > > 
> > > Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com>
> > > ---
> > 
> > Acked-by: Eduard Zingerman <eddyz87@gmail.com>
> > 
> > [...]
> > 
> > > +static int btf_scan_type_tags(struct bpf_verifier_env *env,
> > > +			      const struct btf *btf, u32 type_id,
> > > +			      u32 *tags)
> > > +{
> > > +	const struct btf_type *t;
> > > +
> > > +	/* Find the first pointer type in the chain. */
> > > +	t = btf_type_skip_modifiers(btf, type_id, NULL);
> > > +	if (!t || !btf_type_is_ptr(t))
> > > +		return 0;
> > 
> > Nit: do we want to report -EOPNOTSUPP if type tag is attached to a non-pointer type?

As far as I understand, yes.

> > 
> 
> Let's do that. Since compilers can't really produce that yet that would
> only be the case for handcrafted BTF, right?
> 
> > > +
> > > +	/* We got a pointer, get all associated type tags. */
> > > +	t = btf_type_by_id(btf, t->type);
> > > +	while (t && btf_type_is_type_tag(t)) {
> > > +		const char *tag = __btf_name_by_offset(btf, t->name_off);
> > > +
> > > +		if (strcmp(tag, "arena") == 0) {
> > > +			*tags |= ARG_TAG_ARENA;
> > > +		} else {
> > > +			bpf_log(&env->log, "function signature member has unsupported type tag '%s'\n",
> > > +				tag);
> > > +			return -EOPNOTSUPP;
> > > +		}
> > > +
> > > +		t = btf_type_by_id(btf, t->type);
> > > +	}
> > > +
> > > +	return 0;
> > > +}
> > > +
> > 
> > [...]

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

* Re: [PATCH bpf-next v2 3/5] bpf: Allow subprogs to return arena pointers
  2026-06-01 19:01   ` Eduard Zingerman
@ 2026-06-02  0:06     ` Emil Tsalapatis
  0 siblings, 0 replies; 20+ messages in thread
From: Emil Tsalapatis @ 2026-06-02  0:06 UTC (permalink / raw)
  To: Eduard Zingerman, Emil Tsalapatis, bpf
  Cc: ast, andrii, memxor, daniel, song, mattbobrowski

On Mon Jun 1, 2026 at 3:01 PM EDT, Eduard Zingerman wrote:
> On Fri, 2026-05-29 at 20:22 -0400, Emil Tsalapatis wrote:
>> BPF subprogs currently only return void or scalar values. However,
>> it is also safe to return arena pointers between subprogs in the same
>> BPF program: Arena pointers are guaranteed to be safe for both programs
>> at any point. Expand the verifier to permit returning an arena pointer
>> to the caller.
>> 
>> The main subprog is still not allowed to return an arena pointer because
>> arena pointers are internal to the BPF program, and the return values
>> permitted for each main subprog depend on the program type anyway.
>> 
>> Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com>
>> ---
>
> Acked-by: Eduard Zingerman <eddyz87@gmail.com>
>
> [...]
>
>> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
>> index c6a930aca67e..7a8101879f84 100644
>> --- a/kernel/bpf/btf.c
>> +++ b/kernel/bpf/btf.c
>> @@ -7880,6 +7880,36 @@ static int btf_scan_type_tags(struct bpf_verifier_env *env,
>>  	return 0;
>>  }
>>  
>> +/* 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)
>> +{
>> +	u32 tags = 0;
>> +	int err;
>> +
>> +	err = btf_scan_type_tags(env, btf, t->type, &tags);
>> +	if (err)
>> +		return err;
>> +	t = btf_type_by_id(btf, t->type);
>> +
>> +	while (btf_type_is_modifier(t))
>> +		t = btf_type_by_id(btf, t->type);
>
> Nit: btf_type_skip_modifiers().
>
>> +
>> +	/*
>> +	 * We allow all subprogs except for the main one to return any kind of arena pointer.
>> +	 * General arena variables are not allowed, since it makes no sense to return by value
>> +	 * a variable that's on the heap in the first place.
>> +	 */
>> +	if (subprog && (tags & ARG_TAG_ARENA) && btf_type_is_ptr(t))
>> +		return 0;
>> +
>> +	/* We always accept void or scalars. */
>> +	if (btf_type_is_void(t) || btf_type_is_int(t) || btf_is_any_enum(t))
>> +		return 0;
>> +
>> +	return -EOPNOTSUPP;
>> +}
>> +
>>  /* Process BTF of a function to produce high-level expectation of function
>>   * arguments (like ARG_PTR_TO_CTX, or ARG_PTR_TO_MEM, etc). This information
>>   * is cached in subprog info for reuse.
>> @@ -7963,17 +7993,15 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
>>  			tname, nargs, MAX_BPF_FUNC_REG_ARGS);
>>  		return -EINVAL;
>>  	}
>> -	/* check that function is void or returns int, exception cb also requires this */
>> -	t = btf_type_by_id(btf, t->type);
>> -	while (btf_type_is_modifier(t))
>> -		t = btf_type_by_id(btf, t->type);
>> -	if (!btf_type_is_void(t) && !btf_type_is_int(t) && !btf_is_any_enum(t)) {
>> -		if (!is_global)
>> -			return -EINVAL;
>> -		bpf_log(log,
>> -			"Global function %s() return value not void or scalar. "
>> -			"Only those are supported.\n",
>> -			tname);
>> +
>> +	err = btf_validate_return_type(env, btf, t, subprog);
>> +	if (err) {
>> +		if (is_global) {
>
> I understand that this is how it was implemented previously,
> but do we report a sane error if non-global function returns
> non-void/int/enum?
>

TL;DR for non-statics we consider the signature buggy and stop using it,
which seems to only matter for tracing/ext programs, and even there it
doesn't always make them fail.

I looked into this a bit, we are reporting -EOPNOTSUPP on all error paths that ends
up marking the subprog's BTF as unreliable. This prevents the function
from being EXT'ed, and forces the JIT to spill more registers in the
trampoline for fentry programs (while also loses all type info about
them). I don't think we need to add anything more for the time being on
the handling.


>> +			bpf_log(log,
>> +				"Global function %s() return value not void or scalar. "
>> +				"Only those are supported.\n",
>> +				tname);
>> +		}
>>  		return -EINVAL;
>
> Nit: 'return err'?
>
>>  	}
>>  
>
> [...]


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

end of thread, other threads:[~2026-06-02  0:06 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-30  0:22 [PATCH bpf-next v2 0/5] bpf: Minimize annotations for arena programs Emil Tsalapatis
2026-05-30  0:22 ` [PATCH bpf-next v2 1/5] selftests/bpf: libarena: Add "arena" BTF type tag to __arena qualifier Emil Tsalapatis
2026-05-30  0:22 ` [PATCH bpf-next v2 2/5] verifier: parse BTF type tags for function arguments Emil Tsalapatis
2026-05-30  0:59   ` sashiko-bot
2026-06-01  5:12     ` Emil Tsalapatis
2026-06-01 18:37   ` Eduard Zingerman
2026-06-01 19:13     ` Emil Tsalapatis
2026-06-01 19:14       ` Eduard Zingerman
2026-05-30  0:22 ` [PATCH bpf-next v2 3/5] bpf: Allow subprogs to return arena pointers Emil Tsalapatis
2026-05-30  4:54   ` sashiko-bot
2026-06-01  5:09     ` Emil Tsalapatis
2026-06-01 19:01   ` Eduard Zingerman
2026-06-02  0:06     ` Emil Tsalapatis
2026-05-30  0:22 ` [PATCH bpf-next v2 4/5] selftests/bpf: Remove __arg_arena from the codebase Emil Tsalapatis
2026-05-30  5:03   ` sashiko-bot
2026-05-31  5:18     ` Alexei Starovoitov
2026-06-01  5:03       ` Emil Tsalapatis
2026-06-01 19:06   ` Eduard Zingerman
2026-05-30  0:22 ` [PATCH bpf-next v2 5/5] selftests/bpf: libarena: Directly return arena pointers from functions Emil Tsalapatis
2026-06-01 19:07   ` Eduard Zingerman

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.