* [PATCH bpf-next v4 00/13] Add arena argument support to kfuncs and struct_ops
@ 2026-08-05 21:04 Kumar Kartikeya Dwivedi
2026-08-05 21:04 ` [PATCH bpf-next v4 01/13] bpf: Rename 'early' BTF checking as a preparation phase Kumar Kartikeya Dwivedi
` (12 more replies)
0 siblings, 13 replies; 28+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-08-05 21:04 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, Tejun Heo, kkd, kernel-team
This is a continuation of patches in [0], with mostly minor changes and
reordering. The motivation is covered in that link. A major change is
moving to two tags (__arena and __arena__nullable) and moving the changes
to JIT to emit more optimized sequences.
Please see commit logs for details.
[0]: https://lore.kernel.org/bpf/20260713024414.3759854-1-tj@kernel.org
Changelog:
----------
v3 -> v4
v3: https://lore.kernel.org/bpf/20260803125115.2264733-1-memxor@gmail.com
* Rename __arena_nullable to __arena__nullable and prioritize the
composite suffix over __nullable during argument classification.
(Sashiko, Eduard)
* Resolve instructions before collecting subprograms and kfuncs so kfunc
prototype validation can use associated arena state.
* Move the arena kfunc and JIT-sequence test entry points into
prog_tests/verifier.c. (Eduard)
* Match the generated L0 target and call in nullable JIT assertions.
(Eduard)
* Route arena kfunc validation through the common argument-checking path.
(Amery)
* Reuse btf_func_model argument flags for struct_ops arena arguments
instead of maintaining separate trampoline slot metadata. (Eduard)
* Check the generic-trampoline arena argument invariant at link time and
warn once on violations. (Eduard)
* Reject tracing attachments to struct_ops programs with arena context
arguments whose indirect trampolines convert the pointers. (Sashiko)
v2 -> v3
v2: https://lore.kernel.org/bpf/20260726013105.3689867-1-memxor@gmail.com
* Rebase onto current bpf-next to resolve conflicts.
v1 -> v2
v1: https://lore.kernel.org/bpf/20260715220052.1590783-1-memxor@gmail.com
* Fix documentation to only mention x86 for now. (Sashiko)
* Move arg bitmap from insn_aux_data to kfunc descriptor. (Eduard)
Kumar Kartikeya Dwivedi (4):
bpf: Rename 'early' BTF checking as a preparation phase
bpf: Split subprogram and kfunc collection
bpf: Collect kfuncs after resolving program resources
bpf: Reject tracing progs for struct_ops with arena args
Tejun Heo (9):
bpf: Support __arena and __arena__nullable kfunc argument suffixes
bpf: Support __arena and __arena__nullable on struct_ops arguments
bpf, x86: JIT __arena kfunc argument rebasing
bpf, x86: Convert struct_ops arena arguments in the trampoline
selftests/bpf: Add kfunc __arena and __arena__nullable argument tests
selftests/bpf: Add JIT-sequence tests for __arena kfunc arguments
selftests/bpf: Add struct_ops __arena and __arena__nullable argument
tests
bpf, x86: Fix stack-passed arguments for indirect trampolines
selftests/bpf: Test stack-passed struct_ops arena arguments
Documentation/bpf/kfuncs.rst | 39 +++
arch/x86/net/bpf_jit_comp.c | 122 +++++++-
include/linux/bpf.h | 16 ++
include/linux/bpf_verifier.h | 4 +-
include/linux/filter.h | 1 +
kernel/bpf/bpf_struct_ops.c | 52 +++-
kernel/bpf/btf.c | 28 +-
kernel/bpf/check_btf.c | 14 +-
kernel/bpf/core.c | 5 +
kernel/bpf/trampoline.c | 37 +++
kernel/bpf/verifier.c | 141 ++++++++--
.../bpf/prog_tests/test_struct_ops_arena.c | 74 +++++
.../selftests/bpf/prog_tests/verifier.c | 11 +
.../testing/selftests/bpf/progs/arena_kfunc.c | 260 ++++++++++++++++++
.../selftests/bpf/progs/arena_kfunc_jit.c | 98 +++++++
.../selftests/bpf/progs/struct_ops_arena.c | 115 ++++++++
.../bpf/progs/struct_ops_arena_fail.c | 20 ++
.../selftests/bpf/test_kmods/bpf_testmod.c | 95 +++++++
.../selftests/bpf/test_kmods/bpf_testmod.h | 6 +
.../bpf/test_kmods/bpf_testmod_kfunc.h | 13 +
20 files changed, 1092 insertions(+), 59 deletions(-)
create mode 100644 tools/testing/selftests/bpf/prog_tests/test_struct_ops_arena.c
create mode 100644 tools/testing/selftests/bpf/progs/arena_kfunc.c
create mode 100644 tools/testing/selftests/bpf/progs/arena_kfunc_jit.c
create mode 100644 tools/testing/selftests/bpf/progs/struct_ops_arena.c
create mode 100644 tools/testing/selftests/bpf/progs/struct_ops_arena_fail.c
base-commit: 11c1e836710dcba03e50454a4eedfdbaf8d3050e
--
2.53.0
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH bpf-next v4 01/13] bpf: Rename 'early' BTF checking as a preparation phase
2026-08-05 21:04 [PATCH bpf-next v4 00/13] Add arena argument support to kfuncs and struct_ops Kumar Kartikeya Dwivedi
@ 2026-08-05 21:04 ` Kumar Kartikeya Dwivedi
2026-08-06 16:29 ` Amery Hung
2026-08-05 21:04 ` [PATCH bpf-next v4 02/13] bpf: Split subprogram and kfunc collection Kumar Kartikeya Dwivedi
` (11 subsequent siblings)
12 siblings, 1 reply; 28+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-08-05 21:04 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, Tejun Heo, kkd, kernel-team
BTF processing is split around subprogram discovery. The first phase gets
program BTF and imports func_info because a BTF-tagged exception callback
may not be referenced by any instruction. Subprogram discovery needs this
metadata to find it.
The later phase validates func_info and line_info against the complete
subprogram table and applies CO-RE relocations. This split breaks a real
dependency cycle rather than merely running the same checks early.
Rename bpf_check_btf_info_early() and check_btf_func_early() to preparation
names that reflect this role. Add short call-site comments to make the two
phases and their responsibilities clear.
No functional change is intended.
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
include/linux/bpf_verifier.h | 4 ++--
kernel/bpf/check_btf.c | 14 +++++++-------
kernel/bpf/verifier.c | 4 +++-
3 files changed, 12 insertions(+), 10 deletions(-)
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index a2a40caca0a0..a9555d17fd8e 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -1177,8 +1177,8 @@ static inline void bpf_trampoline_unpack_key(u64 key, u32 *obj_id, u32 *btf_id)
*btf_id = key & 0x7FFFFFFF;
}
-int bpf_check_btf_info_early(struct bpf_verifier_env *env,
- const union bpf_attr *attr, bpfptr_t uattr);
+int bpf_prepare_btf_info(struct bpf_verifier_env *env,
+ const union bpf_attr *attr, bpfptr_t uattr);
int bpf_check_btf_info(struct bpf_verifier_env *env,
const union bpf_attr *attr, bpfptr_t uattr);
diff --git a/kernel/bpf/check_btf.c b/kernel/bpf/check_btf.c
index 93bebe6fe12e..0e8b3ccc7a5b 100644
--- a/kernel/bpf/check_btf.c
+++ b/kernel/bpf/check_btf.c
@@ -28,9 +28,9 @@ static int check_abnormal_return(struct bpf_verifier_env *env)
#define MIN_BPF_FUNCINFO_SIZE 8
#define MAX_FUNCINFO_REC_SIZE 252
-static int check_btf_func_early(struct bpf_verifier_env *env,
- const union bpf_attr *attr,
- bpfptr_t uattr)
+static int prepare_btf_func(struct bpf_verifier_env *env,
+ const union bpf_attr *attr,
+ bpfptr_t uattr)
{
u32 krec_size = sizeof(struct bpf_func_info);
const struct btf_type *type, *func_proto;
@@ -407,9 +407,9 @@ static int check_core_relo(struct bpf_verifier_env *env,
return err;
}
-int bpf_check_btf_info_early(struct bpf_verifier_env *env,
- const union bpf_attr *attr,
- bpfptr_t uattr)
+int bpf_prepare_btf_info(struct bpf_verifier_env *env,
+ const union bpf_attr *attr,
+ bpfptr_t uattr)
{
struct btf *btf;
int err;
@@ -429,7 +429,7 @@ int bpf_check_btf_info_early(struct bpf_verifier_env *env,
}
env->prog->aux->btf = btf;
- err = check_btf_func_early(env, attr, uattr);
+ err = prepare_btf_func(env, attr, uattr);
if (err)
return err;
return 0;
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index d925197c2e5f..129e50888b90 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -20317,7 +20317,8 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
INIT_LIST_HEAD(&env->explored_states[i]);
INIT_LIST_HEAD(&env->free_list);
- ret = bpf_check_btf_info_early(env, attr, uattr);
+ /* Prepare BTF and func_info needed to discover all subprograms. */
+ ret = bpf_prepare_btf_info(env, attr, uattr);
if (ret < 0)
goto skip_full_check;
@@ -20329,6 +20330,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
if (ret < 0)
goto skip_full_check;
+ /* Validate BTF against the complete subprogram layout and apply CO-RE. */
ret = bpf_check_btf_info(env, attr, uattr);
if (ret < 0)
goto skip_full_check;
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH bpf-next v4 02/13] bpf: Split subprogram and kfunc collection
2026-08-05 21:04 [PATCH bpf-next v4 00/13] Add arena argument support to kfuncs and struct_ops Kumar Kartikeya Dwivedi
2026-08-05 21:04 ` [PATCH bpf-next v4 01/13] bpf: Rename 'early' BTF checking as a preparation phase Kumar Kartikeya Dwivedi
@ 2026-08-05 21:04 ` Kumar Kartikeya Dwivedi
2026-08-05 21:49 ` bot+bpf-ci
2026-08-06 16:31 ` Amery Hung
2026-08-05 21:04 ` [PATCH bpf-next v4 03/13] bpf: Collect kfuncs after resolving program resources Kumar Kartikeya Dwivedi
` (10 subsequent siblings)
12 siblings, 2 replies; 28+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-08-05 21:04 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, Tejun Heo, kkd, kernel-team
add_subprog_and_kfunc() combines two operations with different ordering
requirements. Subprogram discovery must precede validation of func_info and
line_info, while kfunc descriptors are only needed by the verifier after its
initial program setup is complete.
Split the helper into add_subprogs() and add_kfuncs() so each operation can be
placed according to its actual dependencies. Keep both calls adjacent and in
their existing phase for now, and add short comments describing their roles.
No functional change is intended for valid programs.
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
kernel/bpf/verifier.c | 41 ++++++++++++++++++++++++++++++++---------
1 file changed, 32 insertions(+), 9 deletions(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 129e50888b90..24b163c2bd63 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -2842,7 +2842,7 @@ int bpf_add_kfunc_call(struct bpf_verifier_env *env, u32 func_id, u16 offset)
return 0;
}
-static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
+static int add_subprogs(struct bpf_verifier_env *env)
{
struct bpf_subprog_info *subprog = env->subprog_info;
int i, ret, insn_cnt = env->prog->len, ex_cb_insn;
@@ -2854,8 +2854,7 @@ static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
return ret;
for (i = 0; i < insn_cnt; i++, insn++) {
- if (!bpf_pseudo_func(insn) && !bpf_pseudo_call(insn) &&
- !bpf_pseudo_kfunc_call(insn))
+ if (!bpf_pseudo_func(insn) && !bpf_pseudo_call(insn))
continue;
if (!env->bpf_capable) {
@@ -2863,11 +2862,7 @@ static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
return -EPERM;
}
- if (bpf_pseudo_func(insn) || bpf_pseudo_call(insn))
- ret = add_subprog(env, i + insn->imm + 1);
- else
- ret = bpf_add_kfunc_call(env, insn->imm, insn->off);
-
+ ret = add_subprog(env, i + insn->imm + 1);
if (ret < 0)
return ret;
}
@@ -2905,6 +2900,28 @@ static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
return 0;
}
+static int add_kfuncs(struct bpf_verifier_env *env)
+{
+ struct bpf_insn *insn = env->prog->insnsi;
+ int i, ret, insn_cnt = env->prog->len;
+
+ for (i = 0; i < insn_cnt; i++, insn++) {
+ if (!bpf_pseudo_kfunc_call(insn))
+ continue;
+
+ if (!env->bpf_capable) {
+ verbose(env, "loading/calling other bpf or kernel functions are allowed for CAP_BPF and CAP_SYS_ADMIN\n");
+ return -EPERM;
+ }
+
+ ret = bpf_add_kfunc_call(env, insn->imm, insn->off);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
static int check_subprogs(struct bpf_verifier_env *env)
{
int i, subprog_start, subprog_end, off, cur_subprog = 0;
@@ -20322,7 +20339,13 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
if (ret < 0)
goto skip_full_check;
- ret = add_subprog_and_kfunc(env);
+ /* Discover all subprograms before validating their layout and BTF. */
+ ret = add_subprogs(env);
+ if (ret < 0)
+ goto skip_full_check;
+
+ /* Collect the kfunc descriptors used during verification. */
+ ret = add_kfuncs(env);
if (ret < 0)
goto skip_full_check;
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH bpf-next v4 03/13] bpf: Collect kfuncs after resolving program resources
2026-08-05 21:04 [PATCH bpf-next v4 00/13] Add arena argument support to kfuncs and struct_ops Kumar Kartikeya Dwivedi
2026-08-05 21:04 ` [PATCH bpf-next v4 01/13] bpf: Rename 'early' BTF checking as a preparation phase Kumar Kartikeya Dwivedi
2026-08-05 21:04 ` [PATCH bpf-next v4 02/13] bpf: Split subprogram and kfunc collection Kumar Kartikeya Dwivedi
@ 2026-08-05 21:04 ` Kumar Kartikeya Dwivedi
2026-08-06 16:37 ` Amery Hung
2026-08-05 21:04 ` [PATCH bpf-next v4 04/13] bpf: Support __arena and __arena__nullable kfunc argument suffixes Kumar Kartikeya Dwivedi
` (9 subsequent siblings)
12 siblings, 1 reply; 28+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-08-05 21:04 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, Tejun Heo, kkd, kernel-team
The kfunc descriptors include argument prototypes generated while calls are
collected. Some argument classifications need program auxiliary state derived
from referenced maps, such as the arena associated with the program.
This avoids a footgun in get_kfunc_arg_type() checks where we do
validation on whether program has prog->aux->arena and it hasn't been
resolved yet.
check_and_resolve_insns() records used maps and populates that state. It must
remain after bpf_check_btf_info(), which applies kernel-side CO-RE relocations,
so that instruction validation and the program tag observe the relocated
instruction stream.
Move only add_kfuncs() after instruction and resource resolution. Subprogram
discovery and validation remain before the full BTF phase because that phase
needs the complete subprogram layout. Add a short comment describing the
resource resolution phase at the call site.
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
kernel/bpf/verifier.c | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 24b163c2bd63..b62e77949542 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -20344,11 +20344,6 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
if (ret < 0)
goto skip_full_check;
- /* Collect the kfunc descriptors used during verification. */
- ret = add_kfuncs(env);
- if (ret < 0)
- goto skip_full_check;
-
ret = check_subprogs(env);
if (ret < 0)
goto skip_full_check;
@@ -20358,10 +20353,16 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
if (ret < 0)
goto skip_full_check;
+ /* Validate instructions and resolve the program's referenced resources. */
ret = check_and_resolve_insns(env);
if (ret < 0)
goto skip_full_check;
+ /* Build kfunc prototypes after resolving program resources. */
+ ret = add_kfuncs(env);
+ if (ret < 0)
+ goto skip_full_check;
+
if (bpf_prog_is_offloaded(env->prog->aux)) {
ret = bpf_prog_offload_verifier_prep(env->prog);
if (ret)
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH bpf-next v4 04/13] bpf: Support __arena and __arena__nullable kfunc argument suffixes
2026-08-05 21:04 [PATCH bpf-next v4 00/13] Add arena argument support to kfuncs and struct_ops Kumar Kartikeya Dwivedi
` (2 preceding siblings ...)
2026-08-05 21:04 ` [PATCH bpf-next v4 03/13] bpf: Collect kfuncs after resolving program resources Kumar Kartikeya Dwivedi
@ 2026-08-05 21:04 ` Kumar Kartikeya Dwivedi
2026-08-06 17:22 ` Amery Hung
2026-08-07 0:51 ` Eduard Zingerman
2026-08-05 21:04 ` [PATCH bpf-next v4 05/13] bpf: Support __arena and __arena__nullable on struct_ops arguments Kumar Kartikeya Dwivedi
` (8 subsequent siblings)
12 siblings, 2 replies; 28+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-08-05 21:04 UTC (permalink / raw)
To: bpf
Cc: Tejun Heo, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team
From: Tejun Heo <tj@kernel.org>
Passing an arena pointer to a kfunc takes two steps today. There is no
arena pointer argument type, so the pointer crosses the boundary as a
bare scalar, and the kfunc then offsets it by the arena base and casts
it before it can touch the memory. Every such kfunc open-codes the same
translation.
Add the __arena and __arena__nullable argument suffixes to make this more
convenient. The kfunc declares the parameter by its real pointer type
and dereferences it directly, with the JIT rebasing the value at the
call site, rN = kern_vm_start + (u32)rN. No bounds check is needed: the
u32 offset stays within the guard-padded arena kernel mapping, and a
fault on an unpopulated page recovers through the per-arena scratch
page. A suffixed argument accepts a PTR_TO_ARENA or scalar register,
matching global subprog arena arguments.
__arena rebases unconditionally, so the kfunc never sees NULL and a
value with zero in the low 32 bits arrives as the arena base.
__arena__nullable preserves NULL for optional arguments by skipping the
rebase when the truncated value, arena offset 0, is zero. Keeping the
plain form NULL-free saves the NULL test on every call.
The double separator makes the annotations composable:
__arena__nullable also ends in __nullable. Match the composite suffix
first when classifying kfunc arguments and function-model flags so it
retains arena semantics while carrying the nullable flag.
This patch adds the verifier side: the suffixes are recognized in
check_kfunc_args() and distilled into argument flags in the function
model stored in the kfunc descriptor. JITs retrieve the model while
emitting the call, avoiding per-call state in insn_aux_data.
JITs declare support with bpf_jit_supports_arena_args() and verification
fails with -ENOTSUPP elsewhere.
Signed-off-by: Tejun Heo <tj@kernel.org>
Co-developed-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
Documentation/bpf/kfuncs.rst | 29 ++++++++++++++++++++
include/linux/bpf.h | 6 +++++
include/linux/filter.h | 1 +
kernel/bpf/btf.c | 18 ++++++++++++-
kernel/bpf/core.c | 5 ++++
kernel/bpf/verifier.c | 51 +++++++++++++++++++++++++++++++++---
6 files changed, 105 insertions(+), 5 deletions(-)
diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst
index cbde86d082cc..cd9b90d072cf 100644
--- a/Documentation/bpf/kfuncs.rst
+++ b/Documentation/bpf/kfuncs.rst
@@ -278,6 +278,33 @@ An example is given below::
...
}
+2.3.8 __arena and __arena__nullable Annotations
+-----------------------------------------------
+
+Both annotations indicate that the pointer argument points into the
+calling program's arena. The JIT rebases the value at the call site so
+the kfunc receives a directly dereferenceable kernel address, subject to
+the access rules described in :ref:`BPF_kfunc_arena_access` (at most
+``GUARD_SZ / 2``, 32 KiB, past the pointer in a single unchecked access).
+
+With ``__arena`` the rebase is unconditional and the argument is never
+NULL: a value whose lower 32 bits are zero arrives as the arena base
+address (arena offset 0). The kfunc must not check the argument for NULL.
+With ``__arena__nullable`` such a value arrives as NULL instead and the
+kfunc must check before dereferencing.
+
+An example is given below::
+
+ __bpf_kfunc int bpf_process_item(struct item *item__arena)
+ {
+ ...
+ }
+
+Calling such a kfunc requires the program to use an arena map and a JIT with
+arena argument support (currently x86-64); verification fails otherwise. The
+program can pass any value without compromising the kernel. A value that does
+not point into the arena is a program bug.
+
.. _BPF_kfunc_nodef:
2.4 Using an existing kernel function
@@ -515,6 +542,8 @@ In order to accommodate such requirements, the verifier will enforce strict
PTR_TO_BTF_ID type matching if two types have the exact same name, with one
being suffixed with ``___init``.
+.. _BPF_kfunc_arena_access:
+
2.8 Accessing arena memory through kfunc arguments
--------------------------------------------------
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 73bacfc6444d..f47556b56a48 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1210,6 +1210,12 @@ struct bpf_prog_offload {
/* The argument is signed. */
#define BTF_FMODEL_SIGNED_ARG BIT(1)
+/* The argument is an arena pointer. */
+#define BTF_FMODEL_ARENA_ARG BIT(2)
+
+/* The argument is nullable. */
+#define BTF_FMODEL_NULLABLE_ARG BIT(3)
+
struct btf_func_model {
u8 ret_size;
u8 ret_flags;
diff --git a/include/linux/filter.h b/include/linux/filter.h
index 32d5297c557e..36ce3403fe59 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -1183,6 +1183,7 @@ bool bpf_jit_supports_subprog_tailcalls(void);
bool bpf_jit_supports_percpu_insn(void);
bool bpf_jit_supports_kfunc_call(void);
bool bpf_jit_supports_stack_args(void);
+bool bpf_jit_supports_arena_args(void);
bool bpf_jit_supports_far_kfunc_call(void);
bool bpf_jit_supports_exceptions(void);
bool bpf_jit_supports_ptr_xchg(void);
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 42414633cf26..4ff6148ae8e8 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -7539,6 +7539,22 @@ static u8 __get_type_fmodel_flags(const struct btf_type *t)
return flags;
}
+static u8 __get_arg_fmodel_flags(const struct btf *btf,
+ const struct btf_param *arg,
+ const struct btf_type *t)
+{
+ u8 flags = __get_type_fmodel_flags(t);
+
+ if (btf_param_match_suffix(btf, arg, "__arena__nullable"))
+ flags |= BTF_FMODEL_ARENA_ARG | BTF_FMODEL_NULLABLE_ARG;
+ else if (btf_param_match_suffix(btf, arg, "__arena"))
+ flags |= BTF_FMODEL_ARENA_ARG;
+ else if (btf_param_match_suffix(btf, arg, "__nullable"))
+ flags |= BTF_FMODEL_NULLABLE_ARG;
+
+ return flags;
+}
+
int btf_distill_func_proto(struct bpf_verifier_log *log,
struct btf *btf,
const struct btf_type *func,
@@ -7604,7 +7620,7 @@ int btf_distill_func_proto(struct bpf_verifier_log *log,
return -EINVAL;
}
m->arg_size[i] = ret;
- m->arg_flags[i] = __get_type_fmodel_flags(t);
+ m->arg_flags[i] = __get_arg_fmodel_flags(btf, &args[i], t);
}
m->nr_args = nargs;
return 0;
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index e2076667b245..a3e1fae32eac 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -3308,6 +3308,11 @@ bool __weak bpf_jit_supports_stack_args(void)
return false;
}
+bool __weak bpf_jit_supports_arena_args(void)
+{
+ return false;
+}
+
bool __weak bpf_jit_supports_far_kfunc_call(void)
{
return false;
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index b62e77949542..2b7f6f6bbe76 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -10909,9 +10909,16 @@ static bool is_kfunc_arg_refcounted_kptr(const struct btf *btf, const struct btf
return btf_param_match_suffix(btf, arg, "__refcounted_kptr");
}
+static bool is_kfunc_arg_arena_nullable(const struct btf *btf,
+ const struct btf_param *arg)
+{
+ return btf_param_match_suffix(btf, arg, "__arena__nullable");
+}
+
static bool is_kfunc_arg_nullable(const struct btf *btf, const struct btf_param *arg)
{
- return btf_param_match_suffix(btf, arg, "__nullable");
+ return !is_kfunc_arg_arena_nullable(btf, arg) &&
+ btf_param_match_suffix(btf, arg, "__nullable");
}
static bool is_kfunc_arg_nonown_allowed(const struct btf *btf, const struct btf_param *arg)
@@ -10929,6 +10936,12 @@ static bool is_kfunc_arg_irq_flag(const struct btf *btf, const struct btf_param
return btf_param_match_suffix(btf, arg, "__irq_flag");
}
+static bool is_kfunc_arg_arena(const struct btf *btf, const struct btf_param *arg)
+{
+ return is_kfunc_arg_arena_nullable(btf, arg) ||
+ btf_param_match_suffix(btf, arg, "__arena");
+}
+
static bool is_kfunc_arg_scalar_with_name(const struct btf *btf,
const struct btf_param *arg,
const char *name)
@@ -11149,6 +11162,7 @@ enum kfunc_ptr_arg_type {
KF_ARG_PTR_TO_IRQ_FLAG,
KF_ARG_PTR_TO_RES_SPIN_LOCK,
KF_ARG_PTR_TO_TASK_WORK,
+ KF_ARG_PTR_TO_ARENA,
};
enum special_kfunc_type {
@@ -11434,7 +11448,6 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
reg_arg_name(env, argno), btf_type_str(t));
return -EINVAL;
}
-
ref_t = btf_type_skip_modifiers(meta->btf, t->type, NULL);
ref_tname = btf_name_by_offset(meta->btf, ref_t->name_off);
@@ -11483,7 +11496,29 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
arg_type = KF_ARG_PTR_TO_RES_SPIN_LOCK;
else if (is_kfunc_arg_callback(env, meta->btf, &args[arg]))
arg_type = KF_ARG_PTR_TO_CALLBACK;
- else if (arg + 1 < nargs &&
+ else if (is_kfunc_arg_arena(meta->btf, &args[arg])) {
+ if (!bpf_jit_supports_arena_args()) {
+ verbose(env, "JIT does not support kfunc %s() with arena pointer arguments\n",
+ meta->func_name);
+ return -ENOTSUPP;
+ }
+ if (!env->prog->aux->arena) {
+ verbose(env,
+ "%s arena pointer requires a program with an associated arena\n",
+ reg_arg_name(env, argno));
+ return -EINVAL;
+ }
+ if (reg_from_argno(argno) < 0) {
+ verbose(env, "%s arena pointer cannot be a stack argument\n",
+ reg_arg_name(env, argno));
+ return -EINVAL;
+ }
+ /*
+ * Both suffixes accept a constant zero. The function model determines
+ * whether the JIT rebases it to the arena base or preserves NULL.
+ */
+ arg_type = KF_ARG_PTR_TO_ARENA | PTR_MAYBE_NULL;
+ } else if (arg + 1 < nargs &&
(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) &&
@@ -12177,7 +12212,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
t = btf_type_skip_modifiers(btf, args[i].type, NULL);
if (btf_type_is_ptr(t) && (bpf_register_is_null(reg) || type_may_be_null(reg->type)) &&
- !is_kfunc_arg_nullable(meta->btf, &args[i])) {
+ !type_may_be_null(kf_arg_type)) {
verbose(env, "Possibly NULL pointer passed to trusted %s\n",
reg_arg_name(env, argno));
return -EACCES;
@@ -12230,6 +12265,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
case KF_ARG_PTR_TO_TASK_WORK:
case KF_ARG_PTR_TO_IRQ_FLAG:
case KF_ARG_PTR_TO_RES_SPIN_LOCK:
+ case KF_ARG_PTR_TO_ARENA:
break;
case KF_ARG_PTR_TO_DYNPTR:
arg_type = ARG_PTR_TO_DYNPTR;
@@ -12296,6 +12332,13 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
meta->ret_btf_id = ret;
}
break;
+ case KF_ARG_PTR_TO_ARENA:
+ if (reg->type != PTR_TO_ARENA && reg->type != SCALAR_VALUE) {
+ verbose(env, "%s is not a pointer to arena or scalar\n",
+ reg_arg_name(env, argno));
+ return -EINVAL;
+ }
+ break;
case KF_ARG_PTR_TO_ALLOC_BTF_ID:
if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC)) {
if (!is_bpf_obj_drop_kfunc(meta->func_id)) {
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH bpf-next v4 05/13] bpf: Support __arena and __arena__nullable on struct_ops arguments
2026-08-05 21:04 [PATCH bpf-next v4 00/13] Add arena argument support to kfuncs and struct_ops Kumar Kartikeya Dwivedi
` (3 preceding siblings ...)
2026-08-05 21:04 ` [PATCH bpf-next v4 04/13] bpf: Support __arena and __arena__nullable kfunc argument suffixes Kumar Kartikeya Dwivedi
@ 2026-08-05 21:04 ` Kumar Kartikeya Dwivedi
2026-08-05 21:18 ` sashiko-bot
2026-08-07 0:51 ` Eduard Zingerman
2026-08-05 21:04 ` [PATCH bpf-next v4 06/13] bpf, x86: JIT __arena kfunc argument rebasing Kumar Kartikeya Dwivedi
` (7 subsequent siblings)
12 siblings, 2 replies; 28+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-08-05 21:04 UTC (permalink / raw)
To: bpf
Cc: Tejun Heo, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team
From: Tejun Heo <tj@kernel.org>
A struct_ops callback cannot receive an arena pointer directly, so
passing one takes two steps. The pointer arrives as a bare u64 that the
callback casts, and because the two sides address the arena through
different bases it also has to be rebased by hand on the way in.
Add the __arena and __arena__nullable stub argument suffixes to make this
convenient. The callback declares the parameter as an arena pointer,
receives it as a PTR_TO_ARENA register, and dereferences it directly,
while the kernel caller just passes the natural kernel arena address
(kaddr). The trampoline converts the value while saving the arguments
into the BPF ctx, ctx[slot] = (u32)(kaddr - kern_vm_start), so the
program never sees a kernel address and nothing rewrites the ctx after
the fact. The converted value keeps the upper 32 bits clear as the JITs
require of arena pointer registers and behaves like any cast_kern'ed
arena pointer, so cast_user recovers the full user-visible address.
__arena converts unconditionally and the kernel caller must not pass
NULL. __arena__nullable preserves NULL, tested on the full 64-bit kernel
pointer, and surfaces to the verifier as PTR_TO_ARENA (but not as a
PTR_TO_ARENA | PTR_MAYBE_NULL). The reason is that PTR_TO_ARENA in the
program's type state already encompasses NULL-ness, so it is not
meaningful to force a NULL check for the program.
The composite suffix intentionally ends in __nullable. Classify
__arena__nullable before the generic suffix so scalar arena pointees do
not take the generic nullable BTF pointer path.
This patch adds the generic side. bpf_tramp_collect_arena_args() derives
the conversion map from the prog's ctx_arg_info, keyed by the flattened
ctx byte offset since preceding 16-byte arguments occupy two slots.
prepare_arg_info() also records arena and nullable argument flags in the
struct_ops function model for arch trampolines. Only the struct_ops
indirect trampoline converts: it dispatches to a single prog whose arena
is fixed at generation time. Generic trampolines can mix progs with
different arenas and reject arena ctx args defensively, which is
unreachable today as only struct_ops progs carry them. Arch trampolines
that do not implement the conversion are gated out at verification time
with bpf_jit_supports_arena_args().
Signed-off-by: Tejun Heo <tj@kernel.org>
Co-developed-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
Documentation/bpf/kfuncs.rst | 10 +++++++
include/linux/bpf.h | 19 +++++++++++++
kernel/bpf/bpf_struct_ops.c | 53 +++++++++++++++++++++++++----------
kernel/bpf/btf.c | 10 +++++--
kernel/bpf/trampoline.c | 54 ++++++++++++++++++++++++++++++++++++
kernel/bpf/verifier.c | 23 +++++++++++----
6 files changed, 147 insertions(+), 22 deletions(-)
diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst
index cd9b90d072cf..c044659b7689 100644
--- a/Documentation/bpf/kfuncs.rst
+++ b/Documentation/bpf/kfuncs.rst
@@ -305,6 +305,16 @@ arena argument support (currently x86-64); verification fails otherwise. The
program can pass any value without compromising the kernel. A value that does
not point into the arena is a program bug.
+The suffixes have the same meaning on the arguments of struct_ops stub
+functions, with the conversion running in the opposite direction. The
+kernel caller passes the kernel arena address and the trampoline converts
+it while saving the arguments, so the callback receives an arena pointer
+it can dereference directly. With ``__arena`` the kernel caller must not
+pass NULL. With ``__arena__nullable`` a NULL kernel pointer arrives as NULL.
+However, there is no obligation to prove to the verifier that such a pointer is
+non-NULL before use, in-line with existing semantics of arena pointers used in
+a program (or obtained from any other source).
+
.. _BPF_kfunc_nodef:
2.4 Using an existing kernel function
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index f47556b56a48..0d1773af8373 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1289,6 +1289,20 @@ struct bpf_tramp_nodes {
int nr_nodes;
};
+/*
+ * Which 8-byte ctx slots of a struct_ops trampoline hold arena kernel
+ * pointers that save_args() converts to the arena pointer form,
+ * ctx[slot] = (u32)(kaddr - kern_vm_start).
+ */
+struct bpf_tramp_arena_args {
+ u32 slots;
+ u32 nullable_slots; /* subset of @slots where NULL is preserved */
+ u64 kern_vm_start;
+};
+
+bool bpf_tramp_collect_arena_args(struct bpf_tramp_nodes *tnodes, u32 flags,
+ struct bpf_tramp_arena_args *aargs);
+
struct bpf_tramp_run_ctx;
/* Different use cases for BPF trampoline:
@@ -1690,6 +1704,11 @@ struct bpf_ctx_arg_aux {
u32 btf_id;
u32 ref_id;
bool refcounted;
+ /*
+ * We don't encode NULL-ness in the type for the program, but still need
+ * to distinguish it for the purposes of telling JITs what sequence to emit.
+ */
+ bool arena_nullable;
};
struct btf_mod_pair {
diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
index 4e7a48c02be5..ebfb7fff453e 100644
--- a/kernel/bpf/bpf_struct_ops.c
+++ b/kernel/bpf/bpf_struct_ops.c
@@ -147,6 +147,8 @@ void bpf_struct_ops_image_free(void *image)
#define MAYBE_NULL_SUFFIX "__nullable"
#define REFCOUNTED_SUFFIX "__ref"
+#define ARENA_SUFFIX "__arena"
+#define ARENA_MAYBE_NULL_SUFFIX "__arena__nullable"
/* Prepare argument info for every nullable argument of a member of a
* struct_ops type.
@@ -159,7 +161,7 @@ void bpf_struct_ops_image_free(void *image)
* to provide an array of struct bpf_ctx_arg_aux, which in turn provides
* the information that used by the verifier to check the arguments of the
* BPF struct_ops program assigned to the member. Here, we only care about
- * the arguments that are marked as __nullable.
+ * the arguments that are marked as __nullable, __ref or __arena.
*
* The array of struct bpf_ctx_arg_aux is eventually assigned to
* prog->aux->ctx_arg_info of BPF struct_ops programs and passed to the
@@ -172,10 +174,12 @@ static int prepare_arg_info(struct btf *btf,
const char *st_ops_name,
const char *member_name,
const struct btf_type *func_proto, void *stub_func_addr,
+ struct btf_func_model *model,
struct bpf_struct_ops_arg_info *arg_info)
{
const struct btf_type *stub_func_proto, *pointed_type;
- bool is_nullable = false, is_refcounted = false;
+ bool is_nullable = false, is_refcounted = false, is_arena = false;
+ bool is_arena_nullable = false;
const struct btf_param *stub_args, *args;
struct bpf_ctx_arg_aux *info, *info_buf;
u32 nargs, arg_no, info_cnt = 0;
@@ -225,27 +229,35 @@ static int prepare_arg_info(struct btf *btf,
/* Prepare info for every nullable argument */
info = info_buf;
for (arg_no = 0; arg_no < nargs; arg_no++) {
- /* Skip arguments that is not suffixed with
- * "__nullable or __ref".
+ /*
+ * Skip arguments that are not suffixed with "__arena__nullable",
+ * "__arena", "__nullable", or "__ref".
*/
- is_nullable = btf_param_match_suffix(btf, &stub_args[arg_no],
- MAYBE_NULL_SUFFIX);
+ is_arena_nullable = btf_param_match_suffix(btf, &stub_args[arg_no],
+ ARENA_MAYBE_NULL_SUFFIX);
+ is_arena = btf_param_match_suffix(btf, &stub_args[arg_no], ARENA_SUFFIX);
+ is_nullable = !is_arena_nullable &&
+ btf_param_match_suffix(btf, &stub_args[arg_no], MAYBE_NULL_SUFFIX);
is_refcounted = btf_param_match_suffix(btf, &stub_args[arg_no],
REFCOUNTED_SUFFIX);
- if (is_nullable)
+ if (is_arena_nullable)
+ suffix = ARENA_MAYBE_NULL_SUFFIX;
+ else if (is_arena)
+ suffix = ARENA_SUFFIX;
+ else if (is_nullable)
suffix = MAYBE_NULL_SUFFIX;
else if (is_refcounted)
suffix = REFCOUNTED_SUFFIX;
else
continue;
- /* Should be a pointer to struct */
- pointed_type = btf_type_resolve_ptr(btf,
- args[arg_no].type,
- &arg_btf_id);
- if (!pointed_type ||
- !btf_type_is_struct(pointed_type)) {
+ /*
+ * Should be a pointer to struct, or any pointer for __arena or
+ * __arena__nullable.
+ */
+ pointed_type = btf_type_resolve_ptr(btf, args[arg_no].type, &arg_btf_id);
+ if (!pointed_type || (!is_arena && !is_arena_nullable && !btf_type_is_struct(pointed_type))) {
pr_warn("stub function %s has %s tagging to an unsupported type\n",
stub_fname, suffix);
goto err_out;
@@ -268,7 +280,19 @@ static int prepare_arg_info(struct btf *btf,
info->btf_id = arg_btf_id;
info->btf = btf;
info->offset = offset;
- if (is_nullable) {
+ if (is_arena || is_arena_nullable) {
+ /*
+ * Both types get PTR_TO_ARENA. In verifier state,
+ * PTR_TO_ARENA encompasses potential NULL values, but
+ * we do not force the program to check it, or maintain
+ * precision around it, since it has no safety implication.
+ */
+ info->reg_type = PTR_TO_ARENA;
+ info->arena_nullable = is_arena_nullable;
+ model->arg_flags[arg_no] |= BTF_FMODEL_ARENA_ARG;
+ if (is_arena_nullable)
+ model->arg_flags[arg_no] |= BTF_FMODEL_NULLABLE_ARG;
+ } else if (is_nullable) {
info->reg_type = PTR_TRUSTED | PTR_TO_BTF_ID | PTR_MAYBE_NULL;
} else if (is_refcounted) {
info->reg_type = PTR_TRUSTED | PTR_TO_BTF_ID;
@@ -460,6 +484,7 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
stub_func_addr = *(void **)(st_ops->cfi_stubs + moff);
err = prepare_arg_info(btf, st_ops->name, mname,
func_proto, stub_func_addr,
+ &st_ops->func_models[i],
arg_info + i);
if (err)
goto errout;
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 4ff6148ae8e8..6606187ed4f4 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -6963,15 +6963,19 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
return false;
}
- /* check for PTR_TO_RDONLY_BUF_OR_NULL or PTR_TO_RDWR_BUF_OR_NULL */
+ /*
+ * Check for PTR_TO_RDONLY_BUF_OR_NULL, PTR_TO_RDWR_BUF_OR_NULL or
+ * PTR_TO_ARENA (both nullable and non-nullable cases).
+ */
for (i = 0; i < prog->aux->ctx_arg_info_size; i++) {
const struct bpf_ctx_arg_aux *ctx_arg_info = &prog->aux->ctx_arg_info[i];
u32 type, flag;
type = base_type(ctx_arg_info->reg_type);
flag = type_flag(ctx_arg_info->reg_type);
- if (ctx_arg_info->offset == off && type == PTR_TO_BUF &&
- (flag & PTR_MAYBE_NULL)) {
+ if (ctx_arg_info->offset == off &&
+ (type == PTR_TO_ARENA ||
+ (type == PTR_TO_BUF && (flag & PTR_MAYBE_NULL)))) {
info->reg_type = ctx_arg_info->reg_type;
return true;
}
diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
index ed7999ad6c66..67d0ba8a05cd 100644
--- a/kernel/bpf/trampoline.c
+++ b/kernel/bpf/trampoline.c
@@ -529,6 +529,53 @@ bpf_trampoline_get_progs(const struct bpf_trampoline *tr, int *total, bool *ip_a
return tnodes;
}
+static bool bpf_prog_has_arena_ctx_arg(const struct bpf_prog *prog)
+{
+ int i;
+
+ for (i = 0; i < prog->aux->ctx_arg_info_size; i++)
+ if (base_type(prog->aux->ctx_arg_info[i].reg_type) == PTR_TO_ARENA)
+ return true;
+ return false;
+}
+
+/*
+ * Collect which ctx slots of a struct_ops trampoline hold arena kernel
+ * pointers that save_args() must convert to the arena pointer form. Only
+ * the struct_ops indirect trampoline converts: it dispatches to a single
+ * prog whose arena is known at generation time. Return false when there
+ * is nothing to convert.
+ */
+bool bpf_tramp_collect_arena_args(struct bpf_tramp_nodes *tnodes, u32 flags,
+ struct bpf_tramp_arena_args *aargs)
+{
+ const struct bpf_prog *prog;
+ int i;
+
+ memset(aargs, 0, sizeof(*aargs));
+
+ if (!(flags & BPF_TRAMP_F_INDIRECT) ||
+ tnodes[BPF_TRAMP_FENTRY].nr_nodes != 1)
+ return false;
+
+ prog = tnodes[BPF_TRAMP_FENTRY].nodes[0]->link->prog;
+ for (i = 0; i < prog->aux->ctx_arg_info_size; i++) {
+ const struct bpf_ctx_arg_aux *info = &prog->aux->ctx_arg_info[i];
+
+ if (base_type(info->reg_type) != PTR_TO_ARENA)
+ continue;
+ aargs->slots |= BIT(info->offset / 8);
+ if (info->arena_nullable)
+ aargs->nullable_slots |= BIT(info->offset / 8);
+ }
+ if (!aargs->slots)
+ return false;
+ if (WARN_ON_ONCE(!prog->aux->arena))
+ return false;
+ aargs->kern_vm_start = bpf_arena_get_kern_vm_start(prog->aux->arena);
+ return true;
+}
+
static void bpf_tramp_image_free(struct bpf_tramp_image *im)
{
bpf_image_ksym_del(&im->ksym);
@@ -920,6 +967,13 @@ static int __bpf_trampoline_link_prog(struct bpf_tramp_node *node,
int cnt = 0, i;
kind = bpf_attach_type_to_tramp(node->link->prog);
+ /*
+ * Arena ctx args are converted only by struct_ops indirect
+ * trampolines. They must never be attached to a generic trampoline.
+ */
+ if (WARN_ON_ONCE(bpf_prog_has_arena_ctx_arg(node->link->prog)))
+ return -ENOTSUPP;
+
if (tr->extension_prog)
/* cannot attach fentry/fexit if extension prog is attached.
* cannot overwrite extension prog either.
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 2b7f6f6bbe76..6897b08dd010 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -18872,6 +18872,7 @@ static int check_struct_ops_btf_id(struct bpf_verifier_env *env)
{
const struct btf_type *t, *func_proto;
const struct bpf_struct_ops_desc *st_ops_desc;
+ const struct bpf_struct_ops_arg_info *arg_info;
const struct bpf_struct_ops *st_ops;
const struct btf_member *member;
struct bpf_prog *prog = env->prog;
@@ -18950,10 +18951,23 @@ static int check_struct_ops_btf_id(struct bpf_verifier_env *env)
return -EACCES;
}
- for (i = 0; i < st_ops_desc->arg_info[member_idx].cnt; i++) {
- if (st_ops_desc->arg_info[member_idx].info[i].refcounted) {
+ arg_info = &st_ops_desc->arg_info[member_idx];
+ for (i = 0; i < arg_info->cnt; i++) {
+ const struct bpf_ctx_arg_aux *info = &arg_info->info[i];
+
+ if (info->refcounted)
has_refcounted_arg = true;
- break;
+ if (base_type(info->reg_type) == PTR_TO_ARENA) {
+ if (!bpf_jit_supports_arena_args()) {
+ verbose(env, "JIT does not support arena arguments\n");
+ return -ENOTSUPP;
+ }
+ if (!prog->aux->arena) {
+ verbose(env,
+ "arena argument of %s requires a program with an associated arena\n",
+ mname);
+ return -EINVAL;
+ }
}
}
@@ -18974,8 +18988,7 @@ static int check_struct_ops_btf_id(struct bpf_verifier_env *env)
prog->aux->attach_func_name = mname;
env->ops = st_ops->verifier_ops;
- return bpf_prog_ctx_arg_info_init(prog, st_ops_desc->arg_info[member_idx].info,
- st_ops_desc->arg_info[member_idx].cnt);
+ return bpf_prog_ctx_arg_info_init(prog, arg_info->info, arg_info->cnt);
}
#define SECURITY_PREFIX "security_"
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH bpf-next v4 06/13] bpf, x86: JIT __arena kfunc argument rebasing
2026-08-05 21:04 [PATCH bpf-next v4 00/13] Add arena argument support to kfuncs and struct_ops Kumar Kartikeya Dwivedi
` (4 preceding siblings ...)
2026-08-05 21:04 ` [PATCH bpf-next v4 05/13] bpf: Support __arena and __arena__nullable on struct_ops arguments Kumar Kartikeya Dwivedi
@ 2026-08-05 21:04 ` Kumar Kartikeya Dwivedi
2026-08-05 21:04 ` [PATCH bpf-next v4 07/13] bpf, x86: Convert struct_ops arena arguments in the trampoline Kumar Kartikeya Dwivedi
` (6 subsequent siblings)
12 siblings, 0 replies; 28+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-08-05 21:04 UTC (permalink / raw)
To: bpf
Cc: Tejun Heo, Eduard Zingerman, Alexei Starovoitov, Andrii Nakryiko,
Daniel Borkmann, Emil Tsalapatis, kkd, kernel-team
From: Tejun Heo <tj@kernel.org>
Implement arena argument rebasing for kfunc calls on x86. R12 already
holds kern_vm_start whenever the prog has an arena, so each tagged
argument costs two instructions emitted right before the call:
movl %eN, %eN /* truncate, clear the upper 32 bits */
addq %r12, %rN
A nullable argument tests the truncated value and jumps over the add:
movl %eN, %eN
testl %eN, %eN
jz 1f
addq %r12, %rN
1:
addq carries a REX prefix for every argument register and is always
three bytes, so the jz displacement is constant. The sequence is native
code generated after constant blinding has run on the BPF instruction
stream, so blinding never sees the rebase and needs no special handling.
bpf_jit_supports_arena_args() is not flipped yet; that happens when the
struct_ops trampoline side is in place as well.
Signed-off-by: Tejun Heo <tj@kernel.org>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
arch/x86/net/bpf_jit_comp.c | 50 +++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 01e7ce569c1e..817977797e59 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -1678,6 +1678,50 @@ static int emit_spectre_bhb_barrier(u8 **pprog, u8 *ip,
return 0;
}
+/*
+ * Rebase the __arena args of a kfunc call to arena kernel addresses,
+ * rN = kern_vm_start + (u32)rN, with R12 holding kern_vm_start. A nullable
+ * arg preserves NULL by skipping the add, tested on the truncated value as
+ * arena NULL is offset 0. Return the number of emitted bytes.
+ */
+static int emit_kfunc_arena_args(struct bpf_prog *bpf_prog,
+ const struct bpf_insn *insn, u8 **pprog)
+{
+ const struct btf_func_model *fm;
+ u8 *prog = *pprog;
+ u8 *start = prog;
+ int i;
+
+ fm = bpf_jit_find_kfunc_model(bpf_prog, insn);
+ if (!fm)
+ return -EINVAL;
+
+ for (i = 0; i < min_t(int, fm->nr_args, MAX_BPF_FUNC_REG_ARGS); i++) {
+ u8 flags = fm->arg_flags[i];
+ u32 reg = BPF_REG_1 + i;
+
+ if (!(flags & BTF_FMODEL_ARENA_ARG))
+ continue;
+ if (WARN_ON_ONCE(!bpf_prog->aux->arena))
+ return -EINVAL;
+
+ /* mov eN, eN: truncate and clear the upper 32 bits */
+ emit_mov_reg(&prog, false, reg, reg);
+ if (flags & BTF_FMODEL_NULLABLE_ARG) {
+ /* test eN, eN; jz over the 3-byte add */
+ maybe_emit_mod(&prog, reg, reg, false);
+ EMIT2(0x85, add_2reg(0xC0, reg, reg));
+ EMIT2(X86_JE, 3);
+ }
+ /* add rN, r12 */
+ maybe_emit_mod(&prog, reg, X86_REG_R12, true);
+ EMIT2(0x01, add_2reg(0xC0, reg, X86_REG_R12));
+ }
+
+ *pprog = prog;
+ return prog - start;
+}
+
static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *addrs, u8 *image,
u8 *rw_image, int oldproglen, struct jit_context *ctx, bool jmp_padding)
{
@@ -2583,6 +2627,12 @@ st: insn_off = insn->off;
}
if (!imm32)
return -EINVAL;
+ if (src_reg == BPF_PSEUDO_KFUNC_CALL) {
+ err = emit_kfunc_arena_args(bpf_prog, insn, &prog);
+ if (err < 0)
+ return err;
+ ip += err;
+ }
if (priv_frame_ptr) {
push_r9(&prog);
ip += 2;
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH bpf-next v4 07/13] bpf, x86: Convert struct_ops arena arguments in the trampoline
2026-08-05 21:04 [PATCH bpf-next v4 00/13] Add arena argument support to kfuncs and struct_ops Kumar Kartikeya Dwivedi
` (5 preceding siblings ...)
2026-08-05 21:04 ` [PATCH bpf-next v4 06/13] bpf, x86: JIT __arena kfunc argument rebasing Kumar Kartikeya Dwivedi
@ 2026-08-05 21:04 ` Kumar Kartikeya Dwivedi
2026-08-05 21:04 ` [PATCH bpf-next v4 08/13] selftests/bpf: Add kfunc __arena and __arena__nullable argument tests Kumar Kartikeya Dwivedi
` (5 subsequent siblings)
12 siblings, 0 replies; 28+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-08-05 21:04 UTC (permalink / raw)
To: bpf
Cc: Tejun Heo, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team
From: Tejun Heo <tj@kernel.org>
Implement the struct_ops arena argument conversion on x86. save_args()
receives the arena base from bpf_tramp_arena_base() and consults the
btf_func_model argument flags as it copies each native argument into the
BPF ctx, routing a marked argument through RAX:
movl %esrc, %eax /* truncate and clear the upper 32 bits */
subl $base_lo, %eax
movq %rax, ctx_slot
A nullable argument tests the full 64-bit kernel pointer first:
movq %rsrc, %rax
testq %rax, %rax
jz 1f
subl $base_lo, %eax
1:
movq %rax, ctx_slot
The 32-bit subtraction is sufficient since (u32)(kaddr - base) ==
(u32)kaddr - (u32)base, and it clears the upper half as the JITs require
of arena pointer registers. Stack-passed arguments already reload
through RAX, so only the subtraction (and the NULL test) is inserted
there.
Keeping arena and nullable classification in btf_func_model avoids a
parallel trampoline slot bitmap. bpf_tramp_arena_base() returns a base
only for a single-program struct_ops indirect trampoline; other
trampolines pass zero and perform no conversion. The size probe reruns
the same emission with the same model and nodes, so the image size
matches by construction.
With both the kfunc and struct_ops directions implemented, flip
bpf_jit_supports_arena_args() on for x86.
Signed-off-by: Tejun Heo <tj@kernel.org>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
arch/x86/net/bpf_jit_comp.c | 57 +++++++++++++++++++++++++++++++++----
include/linux/bpf.h | 22 ++++----------
kernel/bpf/bpf_struct_ops.c | 1 -
kernel/bpf/trampoline.c | 41 +++++++++++---------------
4 files changed, 74 insertions(+), 47 deletions(-)
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 817977797e59..cb8ee0f3a642 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -3043,8 +3043,35 @@ static int get_nr_used_regs(const struct btf_func_model *m)
return nr_used_regs;
}
+/*
+ * Convert an arena kernel address into the arena pointer form on its way
+ * into the BPF ctx, rax = (u32)(src - kern_vm_start). A nullable arg
+ * preserves NULL, tested on the full 64-bit kernel pointer. The 32-bit
+ * subtraction both truncates and clears the upper half, so the stored
+ * value satisfies the JIT invariant for arena pointer registers.
+ */
+static void emit_arena_arg_conv(u8 **pprog, u32 src_reg, bool nullable, u32 base_lo)
+{
+ u8 *prog = *pprog;
+
+ if (nullable) {
+ if (src_reg != BPF_REG_0)
+ emit_mov_reg(&prog, true, BPF_REG_0, src_reg);
+ /* test rax, rax; jz over the 5-byte sub */
+ EMIT3(0x48, 0x85, 0xC0);
+ EMIT2(X86_JE, 5);
+ } else if (src_reg != BPF_REG_0) {
+ emit_mov_reg(&prog, false, BPF_REG_0, src_reg);
+ }
+ /* sub eax, base_lo */
+ EMIT1_off32(0x2D, base_lo);
+
+ *pprog = prog;
+}
+
static void save_args(const struct btf_func_model *m, u8 **prog,
- int stack_size, bool for_call_origin, u32 flags)
+ int stack_size, bool for_call_origin, u32 flags,
+ u64 arena_base)
{
int arg_regs, first_off = 0, nr_regs = 0, nr_stack_slots = 0;
bool use_jmp = bpf_trampoline_use_jmp(flags);
@@ -3056,6 +3083,9 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
* mov QWORD PTR [rbp-0x8],rsi
*/
for (i = 0; i < min_t(int, m->nr_args, MAX_BPF_FUNC_ARGS); i++) {
+ bool arena_arg = arena_base && (m->arg_flags[i] & BTF_FMODEL_ARENA_ARG);
+ bool nullable = m->arg_flags[i] & BTF_FMODEL_NULLABLE_ARG;
+
arg_regs = (m->arg_size[i] + 7) / 8;
/* According to the research of Yonghong, struct members
@@ -3089,6 +3119,9 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
for (j = 0; j < arg_regs; j++) {
emit_ldx(prog, BPF_DW, BPF_REG_0, BPF_REG_FP,
nr_stack_slots * 8 + 16 + (!use_jmp) * 8);
+ if (arena_arg)
+ emit_arena_arg_conv(prog, BPF_REG_0, nullable,
+ (u32)arena_base);
emit_stx(prog, BPF_DW, BPF_REG_FP, BPF_REG_0,
-stack_size);
@@ -3109,9 +3142,13 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
/* copy the arguments from regs into stack */
for (j = 0; j < arg_regs; j++) {
- emit_stx(prog, BPF_DW, BPF_REG_FP,
- nr_regs == 5 ? X86_REG_R9 : BPF_REG_1 + nr_regs,
- -stack_size);
+ u32 src = nr_regs == 5 ? X86_REG_R9 : BPF_REG_1 + nr_regs;
+
+ if (arena_arg) {
+ emit_arena_arg_conv(prog, src, nullable, (u32)arena_base);
+ src = BPF_REG_0;
+ }
+ emit_stx(prog, BPF_DW, BPF_REG_FP, src, -stack_size);
stack_size -= 8;
nr_regs++;
}
@@ -3407,6 +3444,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
void *orig_call = func_addr;
int cookie_off, cookie_cnt;
u8 **branches = NULL;
+ u64 arena_base;
u64 func_meta;
u8 *prog;
bool save_ret;
@@ -3419,6 +3457,8 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
WARN_ON_ONCE((flags & BPF_TRAMP_F_INDIRECT) &&
(flags & ~(BPF_TRAMP_F_INDIRECT | BPF_TRAMP_F_RET_FENTRY_RET)));
+ arena_base = bpf_tramp_arena_base(m, tnodes, flags);
+
for (i = 0; i < m->nr_args; i++)
nr_regs += (m->arg_size[i] + 7) / 8 - 1;
@@ -3553,7 +3593,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
emit_store_stack_imm64(&prog, BPF_REG_0, -ip_off, (long)func_addr);
}
- save_args(m, &prog, regs_off, false, flags);
+ save_args(m, &prog, regs_off, false, flags, arena_base);
if (flags & BPF_TRAMP_F_CALL_ORIG) {
/* arg1: mov rdi, im */
@@ -3595,7 +3635,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
if (flags & BPF_TRAMP_F_CALL_ORIG) {
restore_regs(m, &prog, regs_off);
- save_args(m, &prog, arg_stack_off, true, flags);
+ save_args(m, &prog, arg_stack_off, true, flags, 0);
if (flags & BPF_TRAMP_F_TAIL_CALL_CTX) {
/* Before calling the original function, load the
@@ -4096,6 +4136,11 @@ bool bpf_jit_supports_stack_args(void)
return true;
}
+bool bpf_jit_supports_arena_args(void)
+{
+ return true;
+}
+
void *bpf_arch_text_copy(void *dst, void *src, size_t len)
{
if (text_poke_copy(dst, src, len) == NULL)
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 0d1773af8373..00aaa23b4f7e 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1290,18 +1290,13 @@ struct bpf_tramp_nodes {
};
/*
- * Which 8-byte ctx slots of a struct_ops trampoline hold arena kernel
- * pointers that save_args() converts to the arena pointer form,
- * ctx[slot] = (u32)(kaddr - kern_vm_start).
+ * The arena base against which a struct_ops trampoline converts the
+ * arguments marked with BTF_FMODEL_ARENA_ARG while saving them into the BPF
+ * ctx, ctx[arg] = (u32)(kaddr - kern_vm_start). Zero when the trampoline
+ * converts nothing.
*/
-struct bpf_tramp_arena_args {
- u32 slots;
- u32 nullable_slots; /* subset of @slots where NULL is preserved */
- u64 kern_vm_start;
-};
-
-bool bpf_tramp_collect_arena_args(struct bpf_tramp_nodes *tnodes, u32 flags,
- struct bpf_tramp_arena_args *aargs);
+u64 bpf_tramp_arena_base(const struct btf_func_model *m,
+ struct bpf_tramp_nodes *tnodes, u32 flags);
struct bpf_tramp_run_ctx;
@@ -1704,11 +1699,6 @@ struct bpf_ctx_arg_aux {
u32 btf_id;
u32 ref_id;
bool refcounted;
- /*
- * We don't encode NULL-ness in the type for the program, but still need
- * to distinguish it for the purposes of telling JITs what sequence to emit.
- */
- bool arena_nullable;
};
struct btf_mod_pair {
diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
index ebfb7fff453e..ced7b18246a9 100644
--- a/kernel/bpf/bpf_struct_ops.c
+++ b/kernel/bpf/bpf_struct_ops.c
@@ -288,7 +288,6 @@ static int prepare_arg_info(struct btf *btf,
* precision around it, since it has no safety implication.
*/
info->reg_type = PTR_TO_ARENA;
- info->arena_nullable = is_arena_nullable;
model->arg_flags[arg_no] |= BTF_FMODEL_ARENA_ARG;
if (is_arena_nullable)
model->arg_flags[arg_no] |= BTF_FMODEL_NULLABLE_ARG;
diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
index 67d0ba8a05cd..b349e0817184 100644
--- a/kernel/bpf/trampoline.c
+++ b/kernel/bpf/trampoline.c
@@ -540,40 +540,33 @@ static bool bpf_prog_has_arena_ctx_arg(const struct bpf_prog *prog)
}
/*
- * Collect which ctx slots of a struct_ops trampoline hold arena kernel
- * pointers that save_args() must convert to the arena pointer form. Only
- * the struct_ops indirect trampoline converts: it dispatches to a single
- * prog whose arena is known at generation time. Return false when there
- * is nothing to convert.
+ * The arena base against which save_args() converts the arguments marked
+ * with BTF_FMODEL_ARENA_ARG. Only the struct_ops indirect trampoline
+ * converts: it dispatches to a single prog whose arena is known at
+ * generation time. Return 0 when there is nothing to convert.
*/
-bool bpf_tramp_collect_arena_args(struct bpf_tramp_nodes *tnodes, u32 flags,
- struct bpf_tramp_arena_args *aargs)
+u64 bpf_tramp_arena_base(const struct btf_func_model *m,
+ struct bpf_tramp_nodes *tnodes, u32 flags)
{
const struct bpf_prog *prog;
int i;
- memset(aargs, 0, sizeof(*aargs));
-
if (!(flags & BPF_TRAMP_F_INDIRECT) ||
tnodes[BPF_TRAMP_FENTRY].nr_nodes != 1)
- return false;
+ return 0;
- prog = tnodes[BPF_TRAMP_FENTRY].nodes[0]->link->prog;
- for (i = 0; i < prog->aux->ctx_arg_info_size; i++) {
- const struct bpf_ctx_arg_aux *info = &prog->aux->ctx_arg_info[i];
+ for (i = 0; i < m->nr_args; i++)
+ if (m->arg_flags[i] & BTF_FMODEL_ARENA_ARG)
+ break;
+ if (i == m->nr_args)
+ return 0;
- if (base_type(info->reg_type) != PTR_TO_ARENA)
- continue;
- aargs->slots |= BIT(info->offset / 8);
- if (info->arena_nullable)
- aargs->nullable_slots |= BIT(info->offset / 8);
- }
- if (!aargs->slots)
- return false;
+ /* Verification rejects an arena argument without an arena. */
+ prog = tnodes[BPF_TRAMP_FENTRY].nodes[0]->link->prog;
if (WARN_ON_ONCE(!prog->aux->arena))
- return false;
- aargs->kern_vm_start = bpf_arena_get_kern_vm_start(prog->aux->arena);
- return true;
+ return 0;
+
+ return bpf_arena_get_kern_vm_start(prog->aux->arena);
}
static void bpf_tramp_image_free(struct bpf_tramp_image *im)
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH bpf-next v4 08/13] selftests/bpf: Add kfunc __arena and __arena__nullable argument tests
2026-08-05 21:04 [PATCH bpf-next v4 00/13] Add arena argument support to kfuncs and struct_ops Kumar Kartikeya Dwivedi
` (6 preceding siblings ...)
2026-08-05 21:04 ` [PATCH bpf-next v4 07/13] bpf, x86: Convert struct_ops arena arguments in the trampoline Kumar Kartikeya Dwivedi
@ 2026-08-05 21:04 ` Kumar Kartikeya Dwivedi
2026-08-05 21:04 ` [PATCH bpf-next v4 09/13] selftests/bpf: Add JIT-sequence tests for __arena kfunc arguments Kumar Kartikeya Dwivedi
` (4 subsequent siblings)
12 siblings, 0 replies; 28+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-08-05 21:04 UTC (permalink / raw)
To: bpf
Cc: Tejun Heo, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team
From: Tejun Heo <tj@kernel.org>
Add arena-argument kfuncs to bpf_testmod, which also exercises the
argument rebasing on module kfuncs, and tests covering the accepted
argument forms (arena pointer, low 32 bits as a scalar, full user
address as a scalar), the exact rebase semantics via capture kfuncs
returning the raw argument (zero low 32 bits arrive as the arena kernel
base under __arena and as NULL under __arena__nullable), a NULL round
trip through a nullable deref kfunc, five arena arguments in one call, a
mixed __arena plus __arena__nullable call exercising both bitmasks on one
call site, a kernel-side dereference of an unpopulated page recovering
through the scratch page, and the rejections (no arena in the program,
incompatible register type).
The tests run on x86-64 and skip elsewhere, as programs with
arena-tagged kfunc args fail verification where the JIT lacks support.
Signed-off-by: Tejun Heo <tj@kernel.org>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
.../selftests/bpf/prog_tests/verifier.c | 8 +
.../testing/selftests/bpf/progs/arena_kfunc.c | 260 ++++++++++++++++++
.../selftests/bpf/test_kmods/bpf_testmod.c | 57 ++++
.../bpf/test_kmods/bpf_testmod_kfunc.h | 10 +
4 files changed, 335 insertions(+)
create mode 100644 tools/testing/selftests/bpf/progs/arena_kfunc.c
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index b79bafca68f7..7eaee71783b6 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -2,6 +2,7 @@
#include <test_progs.h>
+#include "arena_kfunc.skel.h"
#include "cap_helpers.h"
#include "verifier_align.skel.h"
#include "verifier_and.skel.h"
@@ -161,6 +162,13 @@ static void run_tests_aux(const char *skel_name,
#define RUN(skel) run_tests_aux(#skel, skel##__elf_bytes, NULL)
+/*
+ * The test kfuncs live in bpf_testmod. Resolving kfuncs against module
+ * BTFs needs CAP_SYS_ADMIN, so run with full capabilities instead of
+ * through the verifier tests' capability-restricted runner.
+ */
+void test_arena_kfunc(void) { RUN_TESTS(arena_kfunc); }
+
void test_verifier_align(void) { RUN(verifier_align); }
void test_verifier_and(void) { RUN(verifier_and); }
void test_verifier_arena(void) { RUN(verifier_arena); }
diff --git a/tools/testing/selftests/bpf/progs/arena_kfunc.c b/tools/testing/selftests/bpf/progs/arena_kfunc.c
new file mode 100644
index 000000000000..d6c382ce81af
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/arena_kfunc.c
@@ -0,0 +1,260 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#define BPF_NO_KFUNC_PROTOTYPES
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+#include "bpf_experimental.h"
+#include <bpf_arena_common.h>
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARENA);
+ __uint(map_flags, BPF_F_MMAPABLE);
+ /* page 0 hosts the arena global, page 1 is for allocations */
+ __uint(max_entries, 2);
+} arena SEC(".maps");
+
+/*
+ * Occupies page 0 so no allocation lands at arena offset 0, which the
+ * nullable tests below must be able to tell apart from NULL.
+ */
+u64 __arena arena_pad;
+
+/* volatile to force the scalar reloads below */
+volatile u64 stash;
+
+SEC("syscall")
+__arch_x86_64
+__success __retval(0)
+int arena_arg_forms(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ u64 __arena *val;
+ u64 ret;
+
+ val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!val)
+ return 1;
+
+ /* PTR_TO_ARENA argument */
+ *val = 41;
+ ret = bpf_kfunc_arena_arg_test((u64 *)val);
+ if (ret != 41 || *val != 42)
+ return 2;
+
+ /* the low 32 bits as a scalar */
+ stash = (u32)(u64)val;
+ ret = bpf_kfunc_arena_arg_test((u64 *)stash);
+ if (ret != 42 || *val != 43)
+ return 3;
+
+ /* the full user address as a scalar */
+ stash = (u64)val;
+ bpf_addr_space_cast(stash, 1, 0);
+ ret = bpf_kfunc_arena_arg_test((u64 *)stash);
+ if (ret != 43 || *val != 44)
+ return 4;
+
+ bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+ return 0;
+}
+
+/*
+ * Pin the rebase semantics using the capture kfuncs, which return the raw
+ * argument value: __arena rebases unconditionally, so zero low 32 bits
+ * arrive as the arena kernel base, while __arena__nullable turns them into
+ * NULL.
+ */
+SEC("syscall")
+__arch_x86_64
+__success __retval(0)
+int arena_arg_rebase(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ u64 __arena *val;
+ u64 base, off;
+
+ val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!val)
+ return 1;
+
+ base = bpf_kfunc_arena_cap_test(NULL);
+ if (!base)
+ return 2;
+
+ /* only the low 32 bits contribute */
+ stash = 0xbadc0ffe00000000;
+ if (bpf_kfunc_arena_cap_test((u64 *)stash) != base)
+ return 3;
+
+ off = (u32)(u64)val;
+ if (bpf_kfunc_arena_cap_test((u64 *)val) != base + off)
+ return 4;
+
+ if (bpf_kfunc_arena_cap_nullable_test(NULL) != 0)
+ return 5;
+
+ stash = 0xbadc0ffe00000000;
+ if (bpf_kfunc_arena_cap_nullable_test((u64 *)stash) != 0)
+ return 6;
+
+ if (bpf_kfunc_arena_cap_nullable_test((u64 *)val) != base + off)
+ return 7;
+
+ bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+ return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__success __retval(0)
+int arena_arg_nullable(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ u64 __arena *val;
+ u64 ret;
+
+ val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!val)
+ return 1;
+
+ *val = 41;
+ ret = bpf_kfunc_arena_nullable_arg_test((u64 *)val);
+ if (ret != 41 || *val != 42)
+ return 2;
+
+ if (bpf_kfunc_arena_nullable_arg_test(NULL) != 0xdeadbeef)
+ return 3;
+
+ bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+ return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__success __retval(0)
+int arena_args5(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ u64 __arena *val;
+
+ val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!val)
+ return 1;
+
+ val[0] = 1;
+ val[1] = 2;
+ val[2] = 4;
+ val[3] = 8;
+ val[4] = 16;
+
+ if (bpf_kfunc_arena_args5_test((u64 *)&val[0], (u64 *)&val[1],
+ (u64 *)&val[2], (u64 *)&val[3],
+ (u64 *)&val[4]) != 31)
+ return 2;
+ if (bpf_kfunc_arena_args5_test((u64 *)&val[0], (u64 *)&val[1],
+ (u64 *)&val[2], (u64 *)&val[3], NULL) != 15)
+ return 3;
+
+ bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+ return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__success __retval(0)
+int arena_arg_mixed(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ u64 __arena *val;
+
+ val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!val)
+ return 1;
+
+ val[0] = 7;
+ val[1] = 5;
+
+ if (bpf_kfunc_arena_mixed_test((u64 *)&val[0], NULL) != 7)
+ return 2;
+
+ if (bpf_kfunc_arena_mixed_test((u64 *)&val[0], (u64 *)&val[1]) != 12)
+ return 3;
+
+ bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+ return 0;
+}
+
+/* kernel-side faults on unpopulated pages recover via the scratch page */
+SEC("syscall")
+__arch_x86_64
+__success __retval(0)
+int arena_arg_unpopulated(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ u64 __arena *val;
+
+ val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!val)
+ return 1;
+
+ stash = (u64)val + PAGE_SIZE;
+ bpf_kfunc_arena_arg_test((u64 *)stash);
+
+ bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+ return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__failure __msg("arena pointer requires a program with an associated arena")
+int arena_arg_no_arena(void *ctx)
+{
+ bpf_kfunc_arena_arg_test((u64 *)1);
+ return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__failure __msg("is not a pointer to arena or scalar")
+int arena_arg_bad_reg(void *ctx)
+{
+ u64 buf = 0;
+
+ /* use the arena so the program passes the arena presence check */
+ bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ bpf_kfunc_arena_arg_test(&buf);
+ return 0;
+}
+
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST) && \
+ defined(__BPF_FEATURE_STACK_ARGUMENT)
+SEC("syscall")
+__arch_x86_64
+__failure __msg("arena pointer cannot be a stack argument")
+int arena_arg_stack(void *ctx)
+{
+ bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ bpf_kfunc_arena_stack_arg_test(1, 2, 3, 4, 5, (u64 *)1);
+ return 0;
+}
+#else
+SEC("syscall")
+__arch_x86_64
+__description("arena_arg_stack: not supported, dummy test")
+__success
+int arena_arg_stack(void *ctx)
+{
+ 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 eb0f9b5e18d8..b92ac1e5df1d 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -237,6 +237,56 @@ __bpf_kfunc void bpf_kfunc_common_test(void)
{
}
+__bpf_kfunc u64 bpf_kfunc_arena_arg_test(u64 *val__arena)
+{
+ u64 old;
+
+ old = *val__arena;
+ *val__arena = old + 1;
+ return old;
+}
+
+__bpf_kfunc u64 bpf_kfunc_arena_nullable_arg_test(u64 *val__arena__nullable)
+{
+ u64 old;
+
+ if (!val__arena__nullable)
+ return 0xdeadbeef;
+
+ old = *val__arena__nullable;
+ *val__arena__nullable = old + 1;
+ return old;
+}
+
+__bpf_kfunc u64 bpf_kfunc_arena_cap_test(u64 *val__arena)
+{
+ return (u64)val__arena;
+}
+
+__bpf_kfunc u64 bpf_kfunc_arena_cap_nullable_test(u64 *val__arena__nullable)
+{
+ return (u64)val__arena__nullable;
+}
+
+__bpf_kfunc u64 bpf_kfunc_arena_args5_test(u64 *a__arena, u64 *b__arena,
+ u64 *c__arena, u64 *d__arena,
+ u64 *e__arena__nullable)
+{
+ return *a__arena + *b__arena + *c__arena + *d__arena +
+ (e__arena__nullable ? *e__arena__nullable : 0);
+}
+
+__bpf_kfunc u64 bpf_kfunc_arena_stack_arg_test(u64 a, u64 b, u64 c, u64 d, u64 e,
+ u64 *f__arena)
+{
+ return a + b + c + d + e + *f__arena;
+}
+
+__bpf_kfunc u64 bpf_kfunc_arena_mixed_test(u64 *a__arena, u64 *b__arena__nullable)
+{
+ return *a__arena + (b__arena__nullable ? *b__arena__nullable : 0);
+}
+
__bpf_kfunc void bpf_kfunc_dynptr_test(struct bpf_dynptr *ptr,
struct bpf_dynptr *ptr__nullable)
{
@@ -755,6 +805,13 @@ BTF_ID_FLAGS(func, bpf_iter_testmod_seq_next, KF_ITER_NEXT | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_iter_testmod_seq_destroy, KF_ITER_DESTROY)
BTF_ID_FLAGS(func, bpf_iter_testmod_seq_value)
BTF_ID_FLAGS(func, bpf_kfunc_common_test)
+BTF_ID_FLAGS(func, bpf_kfunc_arena_arg_test)
+BTF_ID_FLAGS(func, bpf_kfunc_arena_nullable_arg_test)
+BTF_ID_FLAGS(func, bpf_kfunc_arena_cap_test)
+BTF_ID_FLAGS(func, bpf_kfunc_arena_cap_nullable_test)
+BTF_ID_FLAGS(func, bpf_kfunc_arena_args5_test)
+BTF_ID_FLAGS(func, bpf_kfunc_arena_stack_arg_test)
+BTF_ID_FLAGS(func, bpf_kfunc_arena_mixed_test)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_mem_len_pass1)
BTF_ID_FLAGS(func, bpf_kfunc_dynptr_test)
BTF_ID_FLAGS(func, bpf_kfunc_nested_acquire_nonzero_offset_test, KF_ACQUIRE)
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 c36bb911defa..3317436b03a3 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
@@ -98,6 +98,16 @@ void bpf_kfunc_call_test_release(struct prog_test_ref_kfunc *p) __ksym;
void bpf_kfunc_call_test_ref(struct prog_test_ref_kfunc *p) __ksym;
void bpf_kfunc_call_test_mem_len_pass1(void *mem, int len) __ksym;
+__u64 bpf_kfunc_arena_arg_test(__u64 *val__arena) __ksym;
+__u64 bpf_kfunc_arena_nullable_arg_test(__u64 *val__arena__nullable) __ksym;
+__u64 bpf_kfunc_arena_cap_test(__u64 *val__arena) __ksym;
+__u64 bpf_kfunc_arena_cap_nullable_test(__u64 *val__arena__nullable) __ksym;
+__u64 bpf_kfunc_arena_args5_test(__u64 *a__arena, __u64 *b__arena,
+ __u64 *c__arena, __u64 *d__arena,
+ __u64 *e__arena__nullable) __ksym;
+__u64 bpf_kfunc_arena_stack_arg_test(__u64 a, __u64 b, __u64 c, __u64 d, __u64 e,
+ __u64 *f__arena) __ksym;
+__u64 bpf_kfunc_arena_mixed_test(__u64 *a__arena, __u64 *b__arena__nullable) __ksym;
int *bpf_kfunc_call_test_get_rdwr_mem(struct prog_test_ref_kfunc *p, const int rdwr_buf_size) __ksym;
int *bpf_kfunc_call_test_get_rdonly_mem(struct prog_test_ref_kfunc *p, const int rdonly_buf_size) __ksym;
int *bpf_kfunc_call_test_acq_rdonly_mem(struct prog_test_ref_kfunc *p, const int rdonly_buf_size) __ksym;
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH bpf-next v4 09/13] selftests/bpf: Add JIT-sequence tests for __arena kfunc arguments
2026-08-05 21:04 [PATCH bpf-next v4 00/13] Add arena argument support to kfuncs and struct_ops Kumar Kartikeya Dwivedi
` (7 preceding siblings ...)
2026-08-05 21:04 ` [PATCH bpf-next v4 08/13] selftests/bpf: Add kfunc __arena and __arena__nullable argument tests Kumar Kartikeya Dwivedi
@ 2026-08-05 21:04 ` Kumar Kartikeya Dwivedi
2026-08-05 21:04 ` [PATCH bpf-next v4 10/13] selftests/bpf: Add struct_ops __arena and __arena__nullable argument tests Kumar Kartikeya Dwivedi
` (3 subsequent siblings)
12 siblings, 0 replies; 28+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-08-05 21:04 UTC (permalink / raw)
To: bpf
Cc: Tejun Heo, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team
From: Tejun Heo <tj@kernel.org>
Pin the exact rebase sequences the JITs emit for __arena and
__arena__nullable kfunc arguments with __jited assertions on x86-64: the
unconditional truncate-and-add, the nullable test-and-skip variant, and
all five argument registers in one call, which also covers the
REX-prefixed encoding of r8 on x86. The capture kfuncs take the argument
without dereferencing, so only the emitted code is under test. The
tests skip without LLVM disassembler support.
Signed-off-by: Tejun Heo <tj@kernel.org>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
.../selftests/bpf/prog_tests/verifier.c | 3 +
.../selftests/bpf/progs/arena_kfunc_jit.c | 98 +++++++++++++++++++
2 files changed, 101 insertions(+)
create mode 100644 tools/testing/selftests/bpf/progs/arena_kfunc_jit.c
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index 7eaee71783b6..110a33b6e1c2 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -3,6 +3,7 @@
#include <test_progs.h>
#include "arena_kfunc.skel.h"
+#include "arena_kfunc_jit.skel.h"
#include "cap_helpers.h"
#include "verifier_align.skel.h"
#include "verifier_and.skel.h"
@@ -169,6 +170,8 @@ static void run_tests_aux(const char *skel_name,
*/
void test_arena_kfunc(void) { RUN_TESTS(arena_kfunc); }
+void test_arena_kfunc_jit(void) { RUN_TESTS(arena_kfunc_jit); }
+
void test_verifier_align(void) { RUN(verifier_align); }
void test_verifier_and(void) { RUN(verifier_and); }
void test_verifier_arena(void) { RUN(verifier_arena); }
diff --git a/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c b/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c
new file mode 100644
index 000000000000..c9b918662616
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/arena_kfunc_jit.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+/*
+ * Verify the JIT-emitted rebase sequences for __arena and __arena__nullable
+ * kfunc arguments. The capture kfuncs take the argument without
+ * dereferencing it, so these tests pin only the emitted code.
+ */
+#define BPF_NO_KFUNC_PROTOTYPES
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+#include "bpf_experimental.h"
+#include <bpf_arena_common.h>
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARENA);
+ __uint(map_flags, BPF_F_MMAPABLE);
+ __uint(max_entries, 1);
+} arena SEC(".maps");
+
+/* volatile to force the scalar reloads below */
+volatile u64 stash;
+
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+
+SEC("syscall")
+__arch_x86_64
+__jited("...")
+__jited(" movl %edi, %edi")
+__jited(" addq %r12, %rdi")
+__jited("...")
+__jited(" callq {{.*}}")
+__success
+int arena_arg_jit_rebase(void *ctx)
+{
+ stash = (u64)bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ bpf_kfunc_arena_cap_test((u64 *)stash);
+ return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__jited("...")
+__jited(" movl %edi, %edi")
+__jited(" testl %edi, %edi")
+__jited(" je L0")
+__jited(" addq %r12, %rdi")
+__jited("L0: callq {{.*}}")
+__success
+int arena_arg_jit_nullable(void *ctx)
+{
+ stash = (u64)bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ bpf_kfunc_arena_cap_nullable_test((u64 *)stash);
+ return 0;
+}
+
+SEC("syscall")
+__arch_x86_64
+__jited("...")
+__jited(" movl %edi, %edi")
+__jited(" addq %r12, %rdi")
+__jited(" movl %esi, %esi")
+__jited(" addq %r12, %rsi")
+__jited(" movl %edx, %edx")
+__jited(" addq %r12, %rdx")
+__jited(" movl %ecx, %ecx")
+__jited(" addq %r12, %rcx")
+__jited(" movl %r8d, %r8d")
+__jited(" testl %r8d, %r8d")
+__jited(" je L0")
+__jited(" addq %r12, %r8")
+__jited("L0: callq {{.*}}")
+__success
+int arena_arg_jit_args5(void *ctx)
+{
+ u64 __arena *val;
+
+ val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!val)
+ return 1;
+
+ val[0] = 1;
+ val[1] = 2;
+ val[2] = 4;
+ val[3] = 8;
+ val[4] = 16;
+
+ bpf_kfunc_arena_args5_test((u64 *)&val[0], (u64 *)&val[1],
+ (u64 *)&val[2], (u64 *)&val[3],
+ (u64 *)&val[4]);
+ return 0;
+}
+
+#endif /* __BPF_FEATURE_ADDR_SPACE_CAST */
+
+char _license[] SEC("license") = "GPL";
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH bpf-next v4 10/13] selftests/bpf: Add struct_ops __arena and __arena__nullable argument tests
2026-08-05 21:04 [PATCH bpf-next v4 00/13] Add arena argument support to kfuncs and struct_ops Kumar Kartikeya Dwivedi
` (8 preceding siblings ...)
2026-08-05 21:04 ` [PATCH bpf-next v4 09/13] selftests/bpf: Add JIT-sequence tests for __arena kfunc arguments Kumar Kartikeya Dwivedi
@ 2026-08-05 21:04 ` Kumar Kartikeya Dwivedi
2026-08-05 21:15 ` sashiko-bot
2026-08-05 21:04 ` [PATCH bpf-next v4 11/13] bpf, x86: Fix stack-passed arguments for indirect trampolines Kumar Kartikeya Dwivedi
` (2 subsequent siblings)
12 siblings, 1 reply; 28+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-08-05 21:04 UTC (permalink / raw)
To: bpf
Cc: Tejun Heo, Eduard Zingerman, Alexei Starovoitov, Andrii Nakryiko,
Daniel Borkmann, Emil Tsalapatis, kkd, kernel-team
From: Tejun Heo <tj@kernel.org>
Add test_arena and test_arena_nullable members to bpf_testmod_ops3 with
arena-tagged stub arguments and kfuncs that forward a caller-provided
pointer to them. The kfuncs take arena-tagged arguments, so each round
trip exercises both conversion directions end to end: the kfunc receives
a kernel arena address and the trampoline converts it back to an arena
pointer for the callback.
The non-nullable callback dereferences its argument with no NULL branch
and captures the raw ctx value, which the trigger program compares
against the arena offset of the passed object, pinning the exact
(u32)(kaddr - kern_vm_start) conversion. The nullable callback verifies
that only a true kernel NULL arrives as NULL. Failure coverage: a
program with no arena is rejected when it loads. The tests run on x86-64
and skip elsewhere, as the programs fail verification where the JIT
lacks arena argument support.
Signed-off-by: Tejun Heo <tj@kernel.org>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
.../bpf/prog_tests/test_struct_ops_arena.c | 74 +++++++++++++++
.../selftests/bpf/progs/struct_ops_arena.c | 94 +++++++++++++++++++
.../bpf/progs/struct_ops_arena_fail.c | 20 ++++
.../selftests/bpf/test_kmods/bpf_testmod.c | 24 +++++
.../selftests/bpf/test_kmods/bpf_testmod.h | 3 +
.../bpf/test_kmods/bpf_testmod_kfunc.h | 2 +
6 files changed, 217 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/test_struct_ops_arena.c
create mode 100644 tools/testing/selftests/bpf/progs/struct_ops_arena.c
create mode 100644 tools/testing/selftests/bpf/progs/struct_ops_arena_fail.c
diff --git a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_arena.c b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_arena.c
new file mode 100644
index 000000000000..323d707c543f
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_arena.c
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <test_progs.h>
+
+#include "struct_ops_arena.skel.h"
+#include "struct_ops_arena_fail.skel.h"
+
+#if defined(__x86_64__)
+/*
+ * Attach callbacks with __arena and __arena__nullable arguments and drive
+ * them through the bpf_testmod_ops3_call_test_arena*() kfuncs.
+ */
+static void arena_arg(void)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, topts);
+ struct struct_ops_arena *skel;
+ struct bpf_link *link = NULL;
+ int err;
+
+ skel = struct_ops_arena__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "struct_ops_arena__open_and_load"))
+ return;
+
+ link = bpf_map__attach_struct_ops(skel->maps.testmod_arena);
+ if (!ASSERT_OK_PTR(link, "attach_struct_ops"))
+ goto out;
+
+ err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.trigger),
+ &topts);
+ ASSERT_OK(err, "test_run");
+ ASSERT_EQ(topts.retval, 0, "trigger_retval");
+
+out:
+ bpf_link__destroy(link);
+ struct_ops_arena__destroy(skel);
+}
+
+/*
+ * A program with no arena cannot attach to a member with an __arena
+ * argument.
+ */
+static void arena_arg_fail(void)
+{
+ struct struct_ops_arena_fail *skel;
+
+ skel = struct_ops_arena_fail__open_and_load();
+ if (ASSERT_ERR_PTR(skel, "struct_ops_arena_fail__open_and_load"))
+ return;
+
+ struct_ops_arena_fail__destroy(skel);
+}
+#endif
+
+/*
+ * Serialized because it attaches the singleton bpf_testmod_ops3, which
+ * test_struct_ops_private_stack also attaches; registering it twice fails
+ * with -EEXIST.
+ */
+void serial_test_struct_ops_arena(void)
+{
+ /*
+ * Arena struct_ops arguments need JIT support, currently x86-64 only.
+ * Elsewhere verification fails with "JIT does not support arena
+ * arguments", so the programs cannot even load.
+ */
+#if defined(__x86_64__)
+ if (test__start_subtest("arena_arg"))
+ arena_arg();
+ if (test__start_subtest("arena_arg_fail"))
+ arena_arg_fail();
+#else
+ test__skip();
+#endif
+}
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_arena.c b/tools/testing/selftests/bpf/progs/struct_ops_arena.c
new file mode 100644
index 000000000000..40c856a748d2
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/struct_ops_arena.c
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#define BPF_NO_KFUNC_PROTOTYPES
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_experimental.h"
+#include <bpf_arena_common.h>
+#include "../test_kmods/bpf_testmod.h"
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+char _license[] SEC("license") = "GPL";
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARENA);
+ __uint(map_flags, BPF_F_MMAPABLE);
+ /* page 0 hosts the arena globals, page 1 is for allocations */
+ __uint(max_entries, 2);
+} arena SEC(".maps");
+
+/* also associates the callbacks with the arena */
+u64 __arena arena_touch;
+/* raw value of the last __arena ctx argument, captured by test_arena_cb */
+u64 __arena cb_ptr_val;
+
+SEC("struct_ops/test_arena")
+int test_arena_cb(unsigned long long *ctx)
+{
+ u64 __arena *ptr = (u64 __arena *)ctx[0];
+
+ arena_touch++;
+ cb_ptr_val = ctx[0];
+ *ptr += 1;
+ return 0;
+}
+
+SEC("struct_ops/test_arena_nullable")
+int test_arena_nullable_cb(unsigned long long *ctx)
+{
+ u64 __arena *ptr = (u64 __arena *)ctx[0];
+
+ arena_touch++;
+ if (!ptr)
+ return 0xbee;
+ *ptr += 1;
+ return 0;
+}
+
+SEC(".struct_ops.link")
+struct bpf_testmod_ops3 testmod_arena = {
+ .test_arena = (void *)test_arena_cb,
+ .test_arena_nullable = (void *)test_arena_nullable_cb,
+};
+
+SEC("syscall")
+int trigger(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+ u64 __arena *val;
+ int ret;
+
+ val = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
+ if (!val)
+ return 1;
+
+ *val = 41;
+ ret = bpf_testmod_ops3_call_test_arena((u64 *)val);
+ if (ret)
+ return 2;
+ if (*val != 42)
+ return 3;
+
+ /*
+ * The callback must have seen exactly (u32)(kaddr - kern_vm_start),
+ * which is the arena offset of val with the upper 32 bits clear.
+ */
+ if (cb_ptr_val != (u32)(u64)val)
+ return 4;
+
+ ret = bpf_testmod_ops3_call_test_arena_nullable((u64 *)val);
+ if (ret)
+ return 5;
+ if (*val != 43)
+ return 6;
+
+ /* NULL survives the nullable kfunc and the trampoline as NULL */
+ ret = bpf_testmod_ops3_call_test_arena_nullable(NULL);
+ if (ret != 0xbee)
+ return 7;
+
+ bpf_arena_free_pages(&arena, (void __arena *)val, 1);
+#endif
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_arena_fail.c b/tools/testing/selftests/bpf/progs/struct_ops_arena_fail.c
new file mode 100644
index 000000000000..1c0ec727d637
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/struct_ops_arena_fail.c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "../test_kmods/bpf_testmod.h"
+
+char _license[] SEC("license") = "GPL";
+
+/* No arena in the program: attaching to test_arena must be rejected. */
+SEC("struct_ops/test_arena")
+int test_arena_no_arena(unsigned long long *ctx)
+{
+ return 0;
+}
+
+SEC(".struct_ops.link")
+struct bpf_testmod_ops3 testmod_arena_fail = {
+ .test_arena = (void *)test_arena_no_arena,
+};
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index b92ac1e5df1d..64ca43744c59 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -397,9 +397,21 @@ static int bpf_testmod_test_4(void)
return 0;
}
+static int bpf_testmod_ops3__test_arena(u64 *ptr__arena)
+{
+ return 0;
+}
+
+static int bpf_testmod_ops3__test_arena_nullable(u64 *ptr__arena__nullable)
+{
+ return 0;
+}
+
static struct bpf_testmod_ops3 __bpf_testmod_ops3 = {
.test_1 = bpf_testmod_test_3,
.test_2 = bpf_testmod_test_4,
+ .test_arena = bpf_testmod_ops3__test_arena,
+ .test_arena_nullable = bpf_testmod_ops3__test_arena_nullable,
};
static void bpf_testmod_test_struct_ops3(void)
@@ -418,6 +430,16 @@ __bpf_kfunc void bpf_testmod_ops3_call_test_2(void)
st_ops3->test_2();
}
+__bpf_kfunc int bpf_testmod_ops3_call_test_arena(u64 *ptr__arena)
+{
+ return st_ops3->test_arena(ptr__arena);
+}
+
+__bpf_kfunc int bpf_testmod_ops3_call_test_arena_nullable(u64 *ptr__arena__nullable)
+{
+ return st_ops3->test_arena_nullable(ptr__arena__nullable);
+}
+
struct bpf_testmod_btf_type_tag_1 {
int a;
};
@@ -827,6 +849,8 @@ BTF_ID_FLAGS(func, bpf_testmod_ctx_create, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_testmod_ctx_release, KF_RELEASE)
BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_1)
BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_2)
+BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena)
+BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_nullable)
BTF_ID_FLAGS(func, bpf_kfunc_get_default_trusted_ptr_test);
BTF_ID_FLAGS(func, bpf_kfunc_put_default_trusted_ptr_test);
BTF_KFUNCS_END(bpf_testmod_common_kfunc_ids)
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h
index 863fd10f1619..c367ec856776 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h
@@ -106,6 +106,9 @@ struct bpf_testmod_ops2 {
struct bpf_testmod_ops3 {
int (*test_1)(void);
int (*test_2)(void);
+ /* Used to test arena pointer arguments. */
+ int (*test_arena)(u64 *ptr);
+ int (*test_arena_nullable)(u64 *ptr);
};
struct st_ops_args {
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 3317436b03a3..a5b6b5db0dd5 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
@@ -121,6 +121,8 @@ u32 bpf_kfunc_call_test_static_unused_arg(u32 arg, u32 unused) __ksym;
#endif
void bpf_testmod_test_mod_kfunc(int i) __ksym;
+int bpf_testmod_ops3_call_test_arena(__u64 *ptr__arena) __ksym;
+int bpf_testmod_ops3_call_test_arena_nullable(__u64 *ptr__arena__nullable) __ksym;
__u64 bpf_kfunc_call_test1(struct sock *sk, __u32 a, __u64 b,
__u32 c, __u64 d) __ksym;
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH bpf-next v4 11/13] bpf, x86: Fix stack-passed arguments for indirect trampolines
2026-08-05 21:04 [PATCH bpf-next v4 00/13] Add arena argument support to kfuncs and struct_ops Kumar Kartikeya Dwivedi
` (9 preceding siblings ...)
2026-08-05 21:04 ` [PATCH bpf-next v4 10/13] selftests/bpf: Add struct_ops __arena and __arena__nullable argument tests Kumar Kartikeya Dwivedi
@ 2026-08-05 21:04 ` Kumar Kartikeya Dwivedi
2026-08-05 21:15 ` sashiko-bot
2026-08-05 21:04 ` [PATCH bpf-next v4 12/13] selftests/bpf: Test stack-passed struct_ops arena arguments Kumar Kartikeya Dwivedi
2026-08-05 21:04 ` [PATCH bpf-next v4 13/13] bpf: Reject tracing progs for struct_ops with arena args Kumar Kartikeya Dwivedi
12 siblings, 1 reply; 28+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-08-05 21:04 UTC (permalink / raw)
To: bpf
Cc: Jiri Olsa, Tejun Heo, Eduard Zingerman, Alexei Starovoitov,
Andrii Nakryiko, Daniel Borkmann, Emil Tsalapatis, kkd,
kernel-team
From: Tejun Heo <tj@kernel.org>
save_args() reads stack-passed arguments relative to rbp assuming two
return addresses sit between the saved rbp and the arguments, which
holds when the trampoline is entered through the fentry call from a
traced function. An indirect trampoline is called through a function
pointer, so only the caller's return address is on the stack and the
arguments start at rbp + 16, not rbp + 24. Every stack-passed argument
of a struct_ops callback with more than six argument slots is read one
slot off.
This has gone unnoticed because no in-tree struct_ops member passes
arguments on the stack. The jmp-entry form already accounts for having
a single return address; treat BPF_TRAMP_F_INDIRECT the same way.
Fixes: 473e3150e30a ("bpf, x86: allow function arguments up to 12 for TRACING")
Cc: Jiri Olsa <jolsa@kernel.org>
Signed-off-by: Tejun Heo <tj@kernel.org>
Tested-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
arch/x86/net/bpf_jit_comp.c | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index cb8ee0f3a642..5c5c32aba239 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -3075,6 +3075,7 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
{
int arg_regs, first_off = 0, nr_regs = 0, nr_stack_slots = 0;
bool use_jmp = bpf_trampoline_use_jmp(flags);
+ int stack_args_off = (use_jmp || (flags & BPF_TRAMP_F_INDIRECT)) ? 16 : 24;
int i, j;
/* Store function arguments to stack.
@@ -3109,16 +3110,16 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
/* copy function arguments from origin stack frame
* into current stack frame.
*
- * The starting address of the arguments on-stack
- * is:
- * rbp + 8(push rbp) +
- * 8(return addr of origin call) +
- * 8(return addr of the caller)
- * which means: rbp + 24
+ * The arguments on-stack start above the saved rbp
+ * and the return addresses: two return addresses
+ * (origin call and caller) when the trampoline is
+ * entered through the fentry call, so rbp + 24, and
+ * a single one when it is entered with a jmp or
+ * called indirectly, so rbp + 16.
*/
for (j = 0; j < arg_regs; j++) {
emit_ldx(prog, BPF_DW, BPF_REG_0, BPF_REG_FP,
- nr_stack_slots * 8 + 16 + (!use_jmp) * 8);
+ nr_stack_slots * 8 + stack_args_off);
if (arena_arg)
emit_arena_arg_conv(prog, BPF_REG_0, nullable,
(u32)arena_base);
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH bpf-next v4 12/13] selftests/bpf: Test stack-passed struct_ops arena arguments
2026-08-05 21:04 [PATCH bpf-next v4 00/13] Add arena argument support to kfuncs and struct_ops Kumar Kartikeya Dwivedi
` (10 preceding siblings ...)
2026-08-05 21:04 ` [PATCH bpf-next v4 11/13] bpf, x86: Fix stack-passed arguments for indirect trampolines Kumar Kartikeya Dwivedi
@ 2026-08-05 21:04 ` Kumar Kartikeya Dwivedi
2026-08-05 21:04 ` [PATCH bpf-next v4 13/13] bpf: Reject tracing progs for struct_ops with arena args Kumar Kartikeya Dwivedi
12 siblings, 0 replies; 28+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-08-05 21:04 UTC (permalink / raw)
To: bpf
Cc: Tejun Heo, Eduard Zingerman, Alexei Starovoitov, Andrii Nakryiko,
Daniel Borkmann, Emil Tsalapatis, kkd, kernel-team
From: Tejun Heo <tj@kernel.org>
Add a test_arena_stack member with eight leading scalar arguments so the
arena pointer is passed on the stack.
The callback validates the first and last scalar ctx slots before
dereferencing the pointer in ctx[8]. This exercises the indirect
trampoline stack layout and arena conversion together, and prevents a
regression where stack arguments are read one slot late.
Signed-off-by: Tejun Heo <tj@kernel.org>
Tested-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
.../selftests/bpf/progs/struct_ops_arena.c | 21 +++++++++++++++++++
.../selftests/bpf/test_kmods/bpf_testmod.c | 14 +++++++++++++
.../selftests/bpf/test_kmods/bpf_testmod.h | 3 +++
.../bpf/test_kmods/bpf_testmod_kfunc.h | 1 +
4 files changed, 39 insertions(+)
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_arena.c b/tools/testing/selftests/bpf/progs/struct_ops_arena.c
index 40c856a748d2..ba04c73d8d96 100644
--- a/tools/testing/selftests/bpf/progs/struct_ops_arena.c
+++ b/tools/testing/selftests/bpf/progs/struct_ops_arena.c
@@ -46,10 +46,24 @@ int test_arena_nullable_cb(unsigned long long *ctx)
return 0;
}
+SEC("struct_ops/test_arena_stack")
+int test_arena_stack_cb(unsigned long long *ctx)
+{
+ u64 __arena *ptr = (u64 __arena *)ctx[8];
+
+ arena_touch++;
+ /* pin the slot layout: the leading args fill ctx[0]..ctx[7] */
+ if (ctx[0] != 1 || ctx[7] != 8)
+ return 0xbad;
+ *ptr += 1;
+ return 0;
+}
+
SEC(".struct_ops.link")
struct bpf_testmod_ops3 testmod_arena = {
.test_arena = (void *)test_arena_cb,
.test_arena_nullable = (void *)test_arena_nullable_cb,
+ .test_arena_stack = (void *)test_arena_stack_cb,
};
SEC("syscall")
@@ -88,6 +102,13 @@ int trigger(void *ctx)
if (ret != 0xbee)
return 7;
+ /* the arena pointer is stack-passed into the trampoline here */
+ ret = bpf_testmod_ops3_call_test_arena_stack((u64 *)val);
+ if (ret)
+ return 8;
+ if (*val != 44)
+ return 9;
+
bpf_arena_free_pages(&arena, (void __arena *)val, 1);
#endif
return 0;
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index 64ca43744c59..2963c29f96ba 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -407,11 +407,19 @@ static int bpf_testmod_ops3__test_arena_nullable(u64 *ptr__arena__nullable)
return 0;
}
+static int bpf_testmod_ops3__test_arena_stack(u64 a, u64 b, u64 c, u64 d,
+ u64 e, u64 f, u64 g, u64 h,
+ u64 *ptr__arena)
+{
+ return 0;
+}
+
static struct bpf_testmod_ops3 __bpf_testmod_ops3 = {
.test_1 = bpf_testmod_test_3,
.test_2 = bpf_testmod_test_4,
.test_arena = bpf_testmod_ops3__test_arena,
.test_arena_nullable = bpf_testmod_ops3__test_arena_nullable,
+ .test_arena_stack = bpf_testmod_ops3__test_arena_stack,
};
static void bpf_testmod_test_struct_ops3(void)
@@ -440,6 +448,11 @@ __bpf_kfunc int bpf_testmod_ops3_call_test_arena_nullable(u64 *ptr__arena__nulla
return st_ops3->test_arena_nullable(ptr__arena__nullable);
}
+__bpf_kfunc int bpf_testmod_ops3_call_test_arena_stack(u64 *ptr__arena)
+{
+ return st_ops3->test_arena_stack(1, 2, 3, 4, 5, 6, 7, 8, ptr__arena);
+}
+
struct bpf_testmod_btf_type_tag_1 {
int a;
};
@@ -851,6 +864,7 @@ BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_1)
BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_2)
BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena)
BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_nullable)
+BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_stack)
BTF_ID_FLAGS(func, bpf_kfunc_get_default_trusted_ptr_test);
BTF_ID_FLAGS(func, bpf_kfunc_put_default_trusted_ptr_test);
BTF_KFUNCS_END(bpf_testmod_common_kfunc_ids)
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h
index c367ec856776..33f2af5b7085 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h
@@ -109,6 +109,9 @@ struct bpf_testmod_ops3 {
/* Used to test arena pointer arguments. */
int (*test_arena)(u64 *ptr);
int (*test_arena_nullable)(u64 *ptr);
+ /* enough leading args to force @ptr onto the stack on x86 and arm64 */
+ int (*test_arena_stack)(u64 a, u64 b, u64 c, u64 d, u64 e, u64 f,
+ u64 g, u64 h, u64 *ptr);
};
struct st_ops_args {
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 a5b6b5db0dd5..dc7c40bd1299 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
@@ -123,6 +123,7 @@ u32 bpf_kfunc_call_test_static_unused_arg(u32 arg, u32 unused) __ksym;
void bpf_testmod_test_mod_kfunc(int i) __ksym;
int bpf_testmod_ops3_call_test_arena(__u64 *ptr__arena) __ksym;
int bpf_testmod_ops3_call_test_arena_nullable(__u64 *ptr__arena__nullable) __ksym;
+int bpf_testmod_ops3_call_test_arena_stack(__u64 *ptr__arena) __ksym;
__u64 bpf_kfunc_call_test1(struct sock *sk, __u32 a, __u64 b,
__u32 c, __u64 d) __ksym;
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH bpf-next v4 13/13] bpf: Reject tracing progs for struct_ops with arena args
2026-08-05 21:04 [PATCH bpf-next v4 00/13] Add arena argument support to kfuncs and struct_ops Kumar Kartikeya Dwivedi
` (11 preceding siblings ...)
2026-08-05 21:04 ` [PATCH bpf-next v4 12/13] selftests/bpf: Test stack-passed struct_ops arena arguments Kumar Kartikeya Dwivedi
@ 2026-08-05 21:04 ` Kumar Kartikeya Dwivedi
2026-08-05 22:02 ` bot+bpf-ci
12 siblings, 1 reply; 28+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-08-05 21:04 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, Tejun Heo, kkd, kernel-team
Reject tracing attachments to a target program with arena context
arguments. The struct_ops indirect trampoline converts those arguments
before entering the target, so a generic tracing trampoline would
otherwise expose arena offsets using the target BTF pointer type.
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
include/linux/bpf.h | 1 +
kernel/bpf/trampoline.c | 10 ----------
kernel/bpf/verifier.c | 21 +++++++++++++++++++++
3 files changed, 22 insertions(+), 10 deletions(-)
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 00aaa23b4f7e..68ff66787ae1 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -2367,6 +2367,7 @@ static inline int bpf_fsession_cookie_cnt(struct bpf_tramp_nodes *nodes)
int bpf_prog_ctx_arg_info_init(struct bpf_prog *prog,
const struct bpf_ctx_arg_aux *info, u32 cnt);
+bool bpf_prog_has_arena_ctx_arg(const struct bpf_prog *prog);
#if defined(CONFIG_CGROUP_BPF) && defined(CONFIG_BPF_LSM)
int bpf_trampoline_link_cgroup_shim(struct bpf_prog *prog,
diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
index b349e0817184..e07af35ed040 100644
--- a/kernel/bpf/trampoline.c
+++ b/kernel/bpf/trampoline.c
@@ -529,16 +529,6 @@ bpf_trampoline_get_progs(const struct bpf_trampoline *tr, int *total, bool *ip_a
return tnodes;
}
-static bool bpf_prog_has_arena_ctx_arg(const struct bpf_prog *prog)
-{
- int i;
-
- for (i = 0; i < prog->aux->ctx_arg_info_size; i++)
- if (base_type(prog->aux->ctx_arg_info[i].reg_type) == PTR_TO_ARENA)
- return true;
- return false;
-}
-
/*
* The arena base against which save_args() converts the arguments marked
* with BTF_FMODEL_ARENA_ARG. Only the struct_ops indirect trampoline
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 6897b08dd010..60d1ea9a094a 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -18868,6 +18868,16 @@ int bpf_prog_ctx_arg_info_init(struct bpf_prog *prog,
return prog->aux->ctx_arg_info ? 0 : -ENOMEM;
}
+bool bpf_prog_has_arena_ctx_arg(const struct bpf_prog *prog)
+{
+ int i;
+
+ for (i = 0; i < prog->aux->ctx_arg_info_size; i++)
+ if (base_type(prog->aux->ctx_arg_info[i].reg_type) == PTR_TO_ARENA)
+ return true;
+ return false;
+}
+
static int check_struct_ops_btf_id(struct bpf_verifier_env *env)
{
const struct btf_type *t, *func_proto;
@@ -19255,6 +19265,17 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,
bpf_log(log, "Subprog %s doesn't exist\n", tname);
return -EINVAL;
}
+ /*
+ * A struct_ops indirect trampoline converts arena arguments
+ * before invoking its program. A tracing program attached to the
+ * main program would see the converted offset as a regular BTF
+ * pointer.
+ */
+ if (prog_tracing && subprog == 0 &&
+ bpf_prog_has_arena_ctx_arg(tgt_prog)) {
+ bpf_log(log, "Cannot trace a target with arena context arguments\n");
+ return -EOPNOTSUPP;
+ }
if (aux->func && aux->func[subprog]->aux->exception_cb) {
bpf_log(log,
"%s programs cannot attach to exception callback\n",
--
2.53.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* Re: [PATCH bpf-next v4 11/13] bpf, x86: Fix stack-passed arguments for indirect trampolines
2026-08-05 21:04 ` [PATCH bpf-next v4 11/13] bpf, x86: Fix stack-passed arguments for indirect trampolines Kumar Kartikeya Dwivedi
@ 2026-08-05 21:15 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-08-05 21:15 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Incomplete fix for stack-passed arguments in struct_ops trampolines leaves identical bugs unfixed on ARM64, RISC-V, and LoongArch
--
--- Patch [11]: [PATCH bpf-next v4 11/13] bpf, x86: Fix stack-passed arguments for indirect trampolines ---
commit 08733054ec575a092fdc6b844fb5b2af4d47355a
Author: Tejun Heo <tj@kernel.org>
bpf, x86: Fix stack-passed arguments for indirect trampolines
This commit corrects the stack offset for reading stack-passed arguments
in indirect trampolines on x86. It adjusts the offset from rbp + 24 to
rbp + 16 to account for the single return address on the stack when the
trampoline is called through a function pointer.
> diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
> index cb8ee0f3a6423..5c5c32aba2396 100644
> --- a/arch/x86/net/bpf_jit_comp.c
> +++ b/arch/x86/net/bpf_jit_comp.c
> @@ -3075,6 +3075,7 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
> {
> int arg_regs, first_off = 0, nr_regs = 0, nr_stack_slots = 0;
> bool use_jmp = bpf_trampoline_use_jmp(flags);
> + int stack_args_off = (use_jmp || (flags & BPF_TRAMP_F_INDIRECT)) ? 16 : 24;
[Severity: High]
This is a pre-existing issue, but while fixing this for x86, are arm64,
riscv, and loongarch also affected by the same struct_ops stack-passed
arguments bug?
Looking at arm64 in arch/arm64/net/bpf_jit_comp.c:save_args(), the offset
seems unconditionally hardcoded to 32, assuming an fentry stack frame layout:
soff = 32; /* on stack arguments start from FP + 32 */
Similarly for riscv in arch/riscv/net/bpf_jit_comp64.c:store_args():
emit_ld(RV_REG_T1, 16 + (i - RV_MAX_REG_ARGS) * 8, RV_REG_FP, ctx);
And loongarch in arch/loongarch/net/bpf_jit.c:store_args():
emit_insn(ctx, ldd, LOONGARCH_GPR_T1, LOONGARCH_GPR_FP,
16 + (i - LOONGARCH_MAX_REG_ARGS) * 8);
Do these architectures also need a similar adjustment for
BPF_TRAMP_F_INDIRECT to avoid reading out-of-bounds or garbage values from
the stack for struct_ops callbacks with more than 8 arguments?
> int i, j;
>
> /* Store function arguments to stack.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260805210427.3218326-1-memxor@gmail.com?part=11
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH bpf-next v4 10/13] selftests/bpf: Add struct_ops __arena and __arena__nullable argument tests
2026-08-05 21:04 ` [PATCH bpf-next v4 10/13] selftests/bpf: Add struct_ops __arena and __arena__nullable argument tests Kumar Kartikeya Dwivedi
@ 2026-08-05 21:15 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-08-05 21:15 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Missing NULL check for the global pointer `st_ops3` and its function pointers in `bpf_testmod_ops3_call_test_arena` and `bpf_testmod_ops3_call_test_arena_nullable` kfuncs.
--
--- Patch [10]: [PATCH bpf-next v4 10/13] selftests/bpf: Add struct_ops __arena and __arena__nullable argument tests ---
commit c92a05fea3ca93b4550591e23b6734b40facb5bc
Author: Tejun Heo <tj@kernel.org>
selftests/bpf: Add struct_ops __arena and __arena__nullable argument tests
Add test_arena and test_arena_nullable members to bpf_testmod_ops3 with
arena-tagged stub arguments and kfuncs that forward a caller-provided
pointer to them.
> diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
> index b92ac1e5df1d2..64ca43744c593 100644
> --- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
> +++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
[ ... ]
> @@ -418,6 +430,16 @@ __bpf_kfunc void bpf_testmod_ops3_call_test_2(void)
> st_ops3->test_2();
> }
>
> +__bpf_kfunc int bpf_testmod_ops3_call_test_arena(u64 *ptr__arena)
> +{
> + return st_ops3->test_arena(ptr__arena);
[Severity: Medium]
Will this cause a kernel panic if a BPF program calls this kfunc before
the struct_ops is attached, leaving st_ops3 as NULL?
Additionally, if a user provides a struct_ops map that omits the test_arena
callback, could st_ops3->test_arena be NULL, leading to a NULL function
pointer dereference?
> +}
> +
> +__bpf_kfunc int bpf_testmod_ops3_call_test_arena_nullable(u64 *ptr__arena__nullable)
> +{
> + return st_ops3->test_arena_nullable(ptr__arena__nullable);
[Severity: Medium]
Similarly, can st_ops3 or st_ops3->test_arena_nullable be NULL here under the
same conditions, causing a panic?
> +}
> +
> struct bpf_testmod_btf_type_tag_1 {
> int a;
> };
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260805210427.3218326-1-memxor@gmail.com?part=10
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH bpf-next v4 05/13] bpf: Support __arena and __arena__nullable on struct_ops arguments
2026-08-05 21:04 ` [PATCH bpf-next v4 05/13] bpf: Support __arena and __arena__nullable on struct_ops arguments Kumar Kartikeya Dwivedi
@ 2026-08-05 21:18 ` sashiko-bot
2026-08-07 0:51 ` Eduard Zingerman
1 sibling, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-08-05 21:18 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] Extension (`freplace`) and tracing (`fentry`/`fexit`) programs attached to `struct_ops` programs with `__arena` arguments bypass type safety, treating user-controlled 32-bit arena offsets as trusted kernel pointers (`PTR_TO_BTF_ID`).
--
--- Patch [5]: [PATCH bpf-next v4 05/13] bpf: Support __arena and __arena__nullable on struct_ops arguments ---
(Note: The formatting of this report is altered due to system recitation restrictions. I am providing a free-form summary of the findings rather than quoting the original code inline.)
commit fe44e46765df675218e6d1c19ad3b275b47f1966
Author: Tejun Heo <tj@kernel.org>
Subject: bpf: Support __arena and __arena__nullable on struct_ops arguments
This patch adds support for __arena and __arena__nullable suffixes on BPF
struct_ops arguments. It allows passing arena pointers directly to struct_ops
callbacks by converting kernel addresses to arena offsets in the trampoline
context array.
[Severity: Critical]
Does this allow extension (freplace) and tracing (fentry/fexit) programs to
bypass type safety when attaching to struct_ops programs that use __arena
arguments?
In kernel/bpf/btf.c:btf_ctx_access(), the verifier iterates over
prog->aux->ctx_arg_info_size to enforce PTR_TO_ARENA. However, for extension
and tracing programs, ctx_arg_info_size is 0. This causes the verifier to skip
the arena type check and fall through, incorrectly typing the argument as a
trusted kernel pointer (PTR_TO_BTF_ID) via the target program translation.
During attachment in kernel/bpf/trampoline.c:__bpf_trampoline_link_prog(),
the safeguard calls bpf_prog_has_arena_ctx_arg(node->link->prog) on the
attaching program instead of the target program. Because the attaching program
lacks ctx_arg_info, this check returns false and allows the unsafe attachment
to proceed.
At runtime, the struct_ops trampoline converts the kernel pointer to a 32-bit
arena offset in the context array. Since the extension or tracing program was
verified believing this argument is a PTR_TO_BTF_ID, could this allow it to
dereference a user-controlled 32-bit arena offset as a trusted kernel pointer?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260805210427.3218326-1-memxor@gmail.com?part=5
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH bpf-next v4 02/13] bpf: Split subprogram and kfunc collection
2026-08-05 21:04 ` [PATCH bpf-next v4 02/13] bpf: Split subprogram and kfunc collection Kumar Kartikeya Dwivedi
@ 2026-08-05 21:49 ` bot+bpf-ci
2026-08-06 16:31 ` Amery Hung
1 sibling, 0 replies; 28+ messages in thread
From: bot+bpf-ci @ 2026-08-05 21:49 UTC (permalink / raw)
To: memxor, bpf
Cc: ast, andrii, daniel, eddyz87, emil, tj, kkd, kernel-team, ast,
andrii, daniel, martin.lau, eddyz87, yonghong.song, clm,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2450 bytes --]
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 129e50888b90..24b163c2bd63 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -2842,7 +2842,7 @@ int bpf_add_kfunc_call(struct bpf_verifier_env *env, u32 func_id, u16 offset)
> return 0;
> }
>
> -static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
> +static int add_subprogs(struct bpf_verifier_env *env)
The rename of add_subprog_and_kfunc() leaves a dangling reference in
kernel/sched/ext/ext.c. The comment in scx_kfunc_filter() still says:
/*
* add_subprog_and_kfunc() collects all kfunc calls, including dead code
* guarded by bpf_ksym_exists(), before check_attach_btf_id() sets
* prog->aux->st_ops. Allow all kfuncs when st_ops is not yet set;
* do_check_main() re-runs the filter with st_ops set and enforces the
* actual restrictions.
*/
Should this be updated to reference add_kfuncs() instead?
> {
> struct bpf_subprog_info *subprog = env->subprog_info;
> int i, ret, insn_cnt = env->prog->len, ex_cb_insn;
[ ... ]
> @@ -2905,6 +2900,28 @@ static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
> return 0;
> }
>
> +static int add_kfuncs(struct bpf_verifier_env *env)
> +{
> + struct bpf_insn *insn = env->prog->insnsi;
> + int i, ret, insn_cnt = env->prog->len;
> +
> + for (i = 0; i < insn_cnt; i++, insn++) {
> + if (!bpf_pseudo_kfunc_call(insn))
> + continue;
> +
> + if (!env->bpf_capable) {
> + verbose(env, "loading/calling other bpf or kernel functions are allowed for CAP_BPF and CAP_SYS_ADMIN\n");
> + return -EPERM;
> + }
> +
> + ret = bpf_add_kfunc_call(env, insn->imm, insn->off);
> + if (ret < 0)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
[ ... ]
> @@ -20322,7 +20339,13 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
> if (ret < 0)
> goto skip_full_check;
>
> - ret = add_subprog_and_kfunc(env);
> + /* Discover all subprograms before validating their layout and BTF. */
> + ret = add_subprogs(env);
> + if (ret < 0)
> + goto skip_full_check;
> +
> + /* Collect the kfunc descriptors used during verification. */
> + ret = add_kfuncs(env);
> if (ret < 0)
> goto skip_full_check;
---
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/31047716344
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH bpf-next v4 13/13] bpf: Reject tracing progs for struct_ops with arena args
2026-08-05 21:04 ` [PATCH bpf-next v4 13/13] bpf: Reject tracing progs for struct_ops with arena args Kumar Kartikeya Dwivedi
@ 2026-08-05 22:02 ` bot+bpf-ci
0 siblings, 0 replies; 28+ messages in thread
From: bot+bpf-ci @ 2026-08-05 22:02 UTC (permalink / raw)
To: memxor, bpf
Cc: ast, andrii, daniel, eddyz87, emil, tj, kkd, kernel-team, ast,
andrii, daniel, martin.lau, eddyz87, yonghong.song, clm,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 4171 bytes --]
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index 00aaa23b4f7e..68ff66787ae1 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -2367,6 +2367,7 @@ static inline int bpf_fsession_cookie_cnt(struct bpf_tramp_nodes *nodes)
>
> int bpf_prog_ctx_arg_info_init(struct bpf_prog *prog,
> const struct bpf_ctx_arg_aux *info, u32 cnt);
> +bool bpf_prog_has_arena_ctx_arg(const struct bpf_prog *prog);
>
> #if defined(CONFIG_CGROUP_BPF) && defined(CONFIG_BPF_LSM)
> int bpf_trampoline_link_cgroup_shim(struct bpf_prog *prog,
> diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
> index b349e0817184..e07af35ed040 100644
> --- a/kernel/bpf/trampoline.c
> +++ b/kernel/bpf/trampoline.c
> @@ -529,16 +529,6 @@ bpf_trampoline_get_progs(const struct bpf_trampoline *tr, int *total, bool *ip_a
> return tnodes;
> }
>
> -static bool bpf_prog_has_arena_ctx_arg(const struct bpf_prog *prog)
> -{
> - int i;
> -
> - for (i = 0; i < prog->aux->ctx_arg_info_size; i++)
> - if (base_type(prog->aux->ctx_arg_info[i].reg_type) == PTR_TO_ARENA)
> - return true;
> - return false;
> -}
> -
Moving bpf_prog_has_arena_ctx_arg() from kernel/bpf/trampoline.c (where it
was static) to kernel/bpf/verifier.c creates a link-time undefined symbol
with CONFIG_BPF_JIT=y and CONFIG_BPF_SYSCALL=n?
The two objects have different Kconfig gates in kernel/bpf/Makefile:
obj-$(CONFIG_BPF_SYSCALL) += syscall.o verifier.o inode.o helpers.o ...
obj-$(CONFIG_BPF_JIT) += trampoline.o
So trampoline.o can be built without verifier.o. But the trampoline still
calls bpf_prog_has_arena_ctx_arg() here:
kernel/bpf/trampoline.c:__bpf_trampoline_link_prog() {
kind = bpf_attach_type_to_tramp(node->link->prog);
/*
* Arena ctx args are converted only by struct_ops indirect
* trampolines. They must never be attached to a generic trampoline.
*/
if (WARN_ON_ONCE(bpf_prog_has_arena_ctx_arg(node->link->prog)))
return -ENOTSUPP;
The call is not guarded by any conditional. The function that contains it
is also non-static and exported (bpf_trampoline_link_prog() calls it from
outside any Kconfig guard).
The new prototype in include/linux/bpf.h sits outside every Kconfig block
as well (just under the _LINUX_BPF_H include guard), and there is no stub
for the !CONFIG_BPF_SYSCALL case.
arch/powerpc/configs/mpc885_ads_defconfig sets CONFIG_BPF_JIT=y and
CONFIG_NET=y (which selects BPF) but never sets CONFIG_BPF_SYSCALL, and
nothing it enables selects BPF_SYSCALL either. CONFIG_BPF_JIT only depends
on BPF and HAVE_CBPF_JIT || HAVE_EBPF_JIT; PPC selects HAVE_EBPF_JIT
unconditionally so that config is reachable.
Other cross-object calls from trampoline.c follow one of two patterns. The
other verifier.c symbol, bpf_check_attach_btf_id_multi(), is called only
inside a block that explicitly includes defined(CONFIG_BPF_SYSCALL). And
bpf_prog_inc_misses_counter(), a syscall.c symbol, is declared inside
ifdef CONFIG_BPF_SYSCALL with a static inline stub in the else.
Would it work to either add the same stub pattern for
bpf_prog_has_arena_ctx_arg() in the header, or keep it static in
trampoline.c and give verifier.c its own copy?
> /*
> * The arena base against which save_args() converts the arguments marked
> * with BTF_FMODEL_ARENA_ARG. Only the struct_ops indirect trampoline
[ ... ]
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 6897b08dd010..60d1ea9a094a 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -18868,6 +18868,16 @@ int bpf_prog_ctx_arg_info_init(struct bpf_prog *prog,
> return prog->aux->ctx_arg_info ? 0 : -ENOMEM;
> }
>
> +bool bpf_prog_has_arena_ctx_arg(const struct bpf_prog *prog)
> +{
> + int i;
> +
> + for (i = 0; i < prog->aux->ctx_arg_info_size; i++)
> + if (base_type(prog->aux->ctx_arg_info[i].reg_type) == PTR_TO_ARENA)
> + return true;
> + return false;
> +}
> +
[ ... ]
---
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/31047716344
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH bpf-next v4 01/13] bpf: Rename 'early' BTF checking as a preparation phase
2026-08-05 21:04 ` [PATCH bpf-next v4 01/13] bpf: Rename 'early' BTF checking as a preparation phase Kumar Kartikeya Dwivedi
@ 2026-08-06 16:29 ` Amery Hung
0 siblings, 0 replies; 28+ messages in thread
From: Amery Hung @ 2026-08-06 16:29 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi
Cc: bpf, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, Tejun Heo, kkd, kernel-team
On Wed, Aug 5, 2026 at 2:05 PM Kumar Kartikeya Dwivedi <memxor@gmail.com> wrote:
>
> BTF processing is split around subprogram discovery. The first phase gets
> program BTF and imports func_info because a BTF-tagged exception callback
> may not be referenced by any instruction. Subprogram discovery needs this
> metadata to find it.
>
> The later phase validates func_info and line_info against the complete
> subprogram table and applies CO-RE relocations. This split breaks a real
> dependency cycle rather than merely running the same checks early.
>
> Rename bpf_check_btf_info_early() and check_btf_func_early() to preparation
> names that reflect this role. Add short call-site comments to make the two
> phases and their responsibilities clear.
>
> No functional change is intended.
>
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Reviewed-by: Amery Hung <ameryhung@gmail.com>
> ---
> include/linux/bpf_verifier.h | 4 ++--
> kernel/bpf/check_btf.c | 14 +++++++-------
> kernel/bpf/verifier.c | 4 +++-
> 3 files changed, 12 insertions(+), 10 deletions(-)
>
> diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
> index a2a40caca0a0..a9555d17fd8e 100644
> --- a/include/linux/bpf_verifier.h
> +++ b/include/linux/bpf_verifier.h
> @@ -1177,8 +1177,8 @@ static inline void bpf_trampoline_unpack_key(u64 key, u32 *obj_id, u32 *btf_id)
> *btf_id = key & 0x7FFFFFFF;
> }
>
> -int bpf_check_btf_info_early(struct bpf_verifier_env *env,
> - const union bpf_attr *attr, bpfptr_t uattr);
> +int bpf_prepare_btf_info(struct bpf_verifier_env *env,
> + const union bpf_attr *attr, bpfptr_t uattr);
> int bpf_check_btf_info(struct bpf_verifier_env *env,
> const union bpf_attr *attr, bpfptr_t uattr);
>
> diff --git a/kernel/bpf/check_btf.c b/kernel/bpf/check_btf.c
> index 93bebe6fe12e..0e8b3ccc7a5b 100644
> --- a/kernel/bpf/check_btf.c
> +++ b/kernel/bpf/check_btf.c
> @@ -28,9 +28,9 @@ static int check_abnormal_return(struct bpf_verifier_env *env)
> #define MIN_BPF_FUNCINFO_SIZE 8
> #define MAX_FUNCINFO_REC_SIZE 252
>
> -static int check_btf_func_early(struct bpf_verifier_env *env,
> - const union bpf_attr *attr,
> - bpfptr_t uattr)
> +static int prepare_btf_func(struct bpf_verifier_env *env,
> + const union bpf_attr *attr,
> + bpfptr_t uattr)
> {
> u32 krec_size = sizeof(struct bpf_func_info);
> const struct btf_type *type, *func_proto;
> @@ -407,9 +407,9 @@ static int check_core_relo(struct bpf_verifier_env *env,
> return err;
> }
>
> -int bpf_check_btf_info_early(struct bpf_verifier_env *env,
> - const union bpf_attr *attr,
> - bpfptr_t uattr)
> +int bpf_prepare_btf_info(struct bpf_verifier_env *env,
> + const union bpf_attr *attr,
> + bpfptr_t uattr)
> {
> struct btf *btf;
> int err;
> @@ -429,7 +429,7 @@ int bpf_check_btf_info_early(struct bpf_verifier_env *env,
> }
> env->prog->aux->btf = btf;
>
> - err = check_btf_func_early(env, attr, uattr);
> + err = prepare_btf_func(env, attr, uattr);
> if (err)
> return err;
> return 0;
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index d925197c2e5f..129e50888b90 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -20317,7 +20317,8 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
> INIT_LIST_HEAD(&env->explored_states[i]);
> INIT_LIST_HEAD(&env->free_list);
>
> - ret = bpf_check_btf_info_early(env, attr, uattr);
> + /* Prepare BTF and func_info needed to discover all subprograms. */
> + ret = bpf_prepare_btf_info(env, attr, uattr);
> if (ret < 0)
> goto skip_full_check;
>
> @@ -20329,6 +20330,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
> if (ret < 0)
> goto skip_full_check;
>
> + /* Validate BTF against the complete subprogram layout and apply CO-RE. */
> ret = bpf_check_btf_info(env, attr, uattr);
> if (ret < 0)
> goto skip_full_check;
> --
> 2.53.0
>
>
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH bpf-next v4 02/13] bpf: Split subprogram and kfunc collection
2026-08-05 21:04 ` [PATCH bpf-next v4 02/13] bpf: Split subprogram and kfunc collection Kumar Kartikeya Dwivedi
2026-08-05 21:49 ` bot+bpf-ci
@ 2026-08-06 16:31 ` Amery Hung
1 sibling, 0 replies; 28+ messages in thread
From: Amery Hung @ 2026-08-06 16:31 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi
Cc: bpf, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, Tejun Heo, kkd, kernel-team
On Wed, Aug 5, 2026 at 2:05 PM Kumar Kartikeya Dwivedi <memxor@gmail.com> wrote:
>
> add_subprog_and_kfunc() combines two operations with different ordering
> requirements. Subprogram discovery must precede validation of func_info and
> line_info, while kfunc descriptors are only needed by the verifier after its
> initial program setup is complete.
>
> Split the helper into add_subprogs() and add_kfuncs() so each operation can be
> placed according to its actual dependencies. Keep both calls adjacent and in
> their existing phase for now, and add short comments describing their roles.
>
> No functional change is intended for valid programs.
>
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Reviewed-by: Amery Hung <ameryhung@gmail.com>
> ---
> kernel/bpf/verifier.c | 41 ++++++++++++++++++++++++++++++++---------
> 1 file changed, 32 insertions(+), 9 deletions(-)
>
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 129e50888b90..24b163c2bd63 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -2842,7 +2842,7 @@ int bpf_add_kfunc_call(struct bpf_verifier_env *env, u32 func_id, u16 offset)
> return 0;
> }
>
> -static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
> +static int add_subprogs(struct bpf_verifier_env *env)
> {
> struct bpf_subprog_info *subprog = env->subprog_info;
> int i, ret, insn_cnt = env->prog->len, ex_cb_insn;
> @@ -2854,8 +2854,7 @@ static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
> return ret;
>
> for (i = 0; i < insn_cnt; i++, insn++) {
> - if (!bpf_pseudo_func(insn) && !bpf_pseudo_call(insn) &&
> - !bpf_pseudo_kfunc_call(insn))
> + if (!bpf_pseudo_func(insn) && !bpf_pseudo_call(insn))
> continue;
>
> if (!env->bpf_capable) {
> @@ -2863,11 +2862,7 @@ static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
> return -EPERM;
> }
>
> - if (bpf_pseudo_func(insn) || bpf_pseudo_call(insn))
> - ret = add_subprog(env, i + insn->imm + 1);
> - else
> - ret = bpf_add_kfunc_call(env, insn->imm, insn->off);
> -
> + ret = add_subprog(env, i + insn->imm + 1);
> if (ret < 0)
> return ret;
> }
> @@ -2905,6 +2900,28 @@ static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
> return 0;
> }
>
> +static int add_kfuncs(struct bpf_verifier_env *env)
> +{
> + struct bpf_insn *insn = env->prog->insnsi;
> + int i, ret, insn_cnt = env->prog->len;
> +
> + for (i = 0; i < insn_cnt; i++, insn++) {
> + if (!bpf_pseudo_kfunc_call(insn))
> + continue;
> +
> + if (!env->bpf_capable) {
> + verbose(env, "loading/calling other bpf or kernel functions are allowed for CAP_BPF and CAP_SYS_ADMIN\n");
nit: Should this and the error message in add_subprogs() be updated to
be more specific?
> + return -EPERM;
> + }
> +
> + ret = bpf_add_kfunc_call(env, insn->imm, insn->off);
> + if (ret < 0)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> static int check_subprogs(struct bpf_verifier_env *env)
> {
> int i, subprog_start, subprog_end, off, cur_subprog = 0;
> @@ -20322,7 +20339,13 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
> if (ret < 0)
> goto skip_full_check;
>
> - ret = add_subprog_and_kfunc(env);
> + /* Discover all subprograms before validating their layout and BTF. */
> + ret = add_subprogs(env);
> + if (ret < 0)
> + goto skip_full_check;
> +
> + /* Collect the kfunc descriptors used during verification. */
> + ret = add_kfuncs(env);
> if (ret < 0)
> goto skip_full_check;
>
> --
> 2.53.0
>
>
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH bpf-next v4 03/13] bpf: Collect kfuncs after resolving program resources
2026-08-05 21:04 ` [PATCH bpf-next v4 03/13] bpf: Collect kfuncs after resolving program resources Kumar Kartikeya Dwivedi
@ 2026-08-06 16:37 ` Amery Hung
0 siblings, 0 replies; 28+ messages in thread
From: Amery Hung @ 2026-08-06 16:37 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi
Cc: bpf, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, Emil Tsalapatis, Tejun Heo, kkd, kernel-team
On Wed, Aug 5, 2026 at 2:05 PM Kumar Kartikeya Dwivedi <memxor@gmail.com> wrote:
>
> The kfunc descriptors include argument prototypes generated while calls are
> collected. Some argument classifications need program auxiliary state derived
> from referenced maps, such as the arena associated with the program.
>
> This avoids a footgun in get_kfunc_arg_type() checks where we do
> validation on whether program has prog->aux->arena and it hasn't been
> resolved yet.
>
> check_and_resolve_insns() records used maps and populates that state. It must
> remain after bpf_check_btf_info(), which applies kernel-side CO-RE relocations,
> so that instruction validation and the program tag observe the relocated
> instruction stream.
>
> Move only add_kfuncs() after instruction and resource resolution. Subprogram
> discovery and validation remain before the full BTF phase because that phase
> needs the complete subprogram layout. Add a short comment describing the
> resource resolution phase at the call site.
>
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Reviewed-by: Amery Hung <ameryhung@gmail.com>
> ---
> kernel/bpf/verifier.c | 11 ++++++-----
> 1 file changed, 6 insertions(+), 5 deletions(-)
>
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 24b163c2bd63..b62e77949542 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -20344,11 +20344,6 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
> if (ret < 0)
> goto skip_full_check;
>
> - /* Collect the kfunc descriptors used during verification. */
> - ret = add_kfuncs(env);
> - if (ret < 0)
> - goto skip_full_check;
> -
> ret = check_subprogs(env);
> if (ret < 0)
> goto skip_full_check;
> @@ -20358,10 +20353,16 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
> if (ret < 0)
> goto skip_full_check;
>
> + /* Validate instructions and resolve the program's referenced resources. */
> ret = check_and_resolve_insns(env);
> if (ret < 0)
> goto skip_full_check;
>
> + /* Build kfunc prototypes after resolving program resources. */
> + ret = add_kfuncs(env);
> + if (ret < 0)
> + goto skip_full_check;
> +
> if (bpf_prog_is_offloaded(env->prog->aux)) {
> ret = bpf_prog_offload_verifier_prep(env->prog);
> if (ret)
> --
> 2.53.0
>
>
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH bpf-next v4 04/13] bpf: Support __arena and __arena__nullable kfunc argument suffixes
2026-08-05 21:04 ` [PATCH bpf-next v4 04/13] bpf: Support __arena and __arena__nullable kfunc argument suffixes Kumar Kartikeya Dwivedi
@ 2026-08-06 17:22 ` Amery Hung
2026-08-06 19:20 ` Kumar Kartikeya Dwivedi
2026-08-07 0:51 ` Eduard Zingerman
1 sibling, 1 reply; 28+ messages in thread
From: Amery Hung @ 2026-08-06 17:22 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi
Cc: bpf, Tejun Heo, Alexei Starovoitov, Andrii Nakryiko,
Daniel Borkmann, Eduard Zingerman, Emil Tsalapatis, kkd,
kernel-team
On Wed, Aug 5, 2026 at 2:05 PM Kumar Kartikeya Dwivedi <memxor@gmail.com> wrote:
>
> From: Tejun Heo <tj@kernel.org>
>
> Passing an arena pointer to a kfunc takes two steps today. There is no
> arena pointer argument type, so the pointer crosses the boundary as a
> bare scalar, and the kfunc then offsets it by the arena base and casts
> it before it can touch the memory. Every such kfunc open-codes the same
> translation.
>
> Add the __arena and __arena__nullable argument suffixes to make this more
> convenient. The kfunc declares the parameter by its real pointer type
> and dereferences it directly, with the JIT rebasing the value at the
> call site, rN = kern_vm_start + (u32)rN. No bounds check is needed: the
> u32 offset stays within the guard-padded arena kernel mapping, and a
> fault on an unpopulated page recovers through the per-arena scratch
> page. A suffixed argument accepts a PTR_TO_ARENA or scalar register,
> matching global subprog arena arguments.
>
> __arena rebases unconditionally, so the kfunc never sees NULL and a
> value with zero in the low 32 bits arrives as the arena base.
> __arena__nullable preserves NULL for optional arguments by skipping the
> rebase when the truncated value, arena offset 0, is zero. Keeping the
> plain form NULL-free saves the NULL test on every call.
>
> The double separator makes the annotations composable:
> __arena__nullable also ends in __nullable. Match the composite suffix
> first when classifying kfunc arguments and function-model flags so it
> retains arena semantics while carrying the nullable flag.
Since __arena__nullable will match is_kfunc_arg_arena() case and go
through JIT + regno check, and get its PTR_MAYBE_NULL anyway. How
about just keep it as __arena_nullable to simplify the patch?
1. No need to introudce is_kfunc_arg_arena_nullable() and changes in
is_kfunc_arg_nullable()
2. is_kfunc_arg_arena() returns btf_param_match_suffix(btf, arg,
"__arena_nullable") || btf_param_match_suffix(btf, arg, "__arena");
[...]
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index b62e77949542..2b7f6f6bbe76 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -10909,9 +10909,16 @@ static bool is_kfunc_arg_refcounted_kptr(const struct btf *btf, const struct btf
> return btf_param_match_suffix(btf, arg, "__refcounted_kptr");
> }
>
> +static bool is_kfunc_arg_arena_nullable(const struct btf *btf,
> + const struct btf_param *arg)
> +{
> + return btf_param_match_suffix(btf, arg, "__arena__nullable");
> +}
> +
> static bool is_kfunc_arg_nullable(const struct btf *btf, const struct btf_param *arg)
> {
> - return btf_param_match_suffix(btf, arg, "__nullable");
> + return !is_kfunc_arg_arena_nullable(btf, arg) &&
> + btf_param_match_suffix(btf, arg, "__nullable");
> }
No need for the changes above.
>
> static bool is_kfunc_arg_nonown_allowed(const struct btf *btf, const struct btf_param *arg)
> @@ -10929,6 +10936,12 @@ static bool is_kfunc_arg_irq_flag(const struct btf *btf, const struct btf_param
> return btf_param_match_suffix(btf, arg, "__irq_flag");
> }
>
> +static bool is_kfunc_arg_arena(const struct btf *btf, const struct btf_param *arg)
> +{
> + return is_kfunc_arg_arena_nullable(btf, arg) ||
> + btf_param_match_suffix(btf, arg, "__arena");
return btf_param_match_suffix(btf, arg, "__arena_nullable") ||
btf_param_match_suffix(btf, arg, "__arena");
[...]
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH bpf-next v4 04/13] bpf: Support __arena and __arena__nullable kfunc argument suffixes
2026-08-06 17:22 ` Amery Hung
@ 2026-08-06 19:20 ` Kumar Kartikeya Dwivedi
2026-08-06 19:23 ` Kumar Kartikeya Dwivedi
0 siblings, 1 reply; 28+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-08-06 19:20 UTC (permalink / raw)
To: Amery Hung
Cc: bpf, Tejun Heo, Alexei Starovoitov, Andrii Nakryiko,
Daniel Borkmann, Eduard Zingerman, Emil Tsalapatis, kkd,
kernel-team
On Thu Aug 6, 2026 at 7:22 PM CEST, Amery Hung wrote:
> On Wed, Aug 5, 2026 at 2:05 PM Kumar Kartikeya Dwivedi <memxor@gmail.com> wrote:
>>
>> From: Tejun Heo <tj@kernel.org>
>>
>> Passing an arena pointer to a kfunc takes two steps today. There is no
>> arena pointer argument type, so the pointer crosses the boundary as a
>> bare scalar, and the kfunc then offsets it by the arena base and casts
>> it before it can touch the memory. Every such kfunc open-codes the same
>> translation.
>>
>> Add the __arena and __arena__nullable argument suffixes to make this more
>> convenient. The kfunc declares the parameter by its real pointer type
>> and dereferences it directly, with the JIT rebasing the value at the
>> call site, rN = kern_vm_start + (u32)rN. No bounds check is needed: the
>> u32 offset stays within the guard-padded arena kernel mapping, and a
>> fault on an unpopulated page recovers through the per-arena scratch
>> page. A suffixed argument accepts a PTR_TO_ARENA or scalar register,
>> matching global subprog arena arguments.
>>
>> __arena rebases unconditionally, so the kfunc never sees NULL and a
>> value with zero in the low 32 bits arrives as the arena base.
>> __arena__nullable preserves NULL for optional arguments by skipping the
>> rebase when the truncated value, arena offset 0, is zero. Keeping the
>> plain form NULL-free saves the NULL test on every call.
>>
>> The double separator makes the annotations composable:
>> __arena__nullable also ends in __nullable. Match the composite suffix
>> first when classifying kfunc arguments and function-model flags so it
>> retains arena semantics while carrying the nullable flag.
>
> Since __arena__nullable will match is_kfunc_arg_arena() case and go
> through JIT + regno check, and get its PTR_MAYBE_NULL anyway. How
> about just keep it as __arena_nullable to simplify the patch?
>
> 1. No need to introudce is_kfunc_arg_arena_nullable() and changes in
> is_kfunc_arg_nullable()
For consistency, would you prefer that I don't manually set | PTR_MAYBE_NULL and
let is_kfunc_arg_nullable() handle that? That would be another way to address
this.
In some sense, __arena includes __nullable for the purposes of type checking, so
it might make sense to add it to the predicate that determines NULL-ness, then
it will acquire PTR_MAYBE_NULL automatically.
We will still drop is_kfunc_arg_arena_nullable() though.
Anyhow, I don't have any strong preference one way or the other, but thought I'd
float this as an alternative since it appears to fit better, and details are
hidden the predicates.
> 2. is_kfunc_arg_arena() returns btf_param_match_suffix(btf, arg,
> "__arena_nullable") || btf_param_match_suffix(btf, arg, "__arena");
>
Yeah, makes sense.
> [...]
>
>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>> index b62e77949542..2b7f6f6bbe76 100644
>> --- a/kernel/bpf/verifier.c
>> +++ b/kernel/bpf/verifier.c
>> @@ -10909,9 +10909,16 @@ static bool is_kfunc_arg_refcounted_kptr(const struct btf *btf, const struct btf
>> return btf_param_match_suffix(btf, arg, "__refcounted_kptr");
>> }
>>
>> +static bool is_kfunc_arg_arena_nullable(const struct btf *btf,
>> + const struct btf_param *arg)
>> +{
>> + return btf_param_match_suffix(btf, arg, "__arena__nullable");
>> +}
>> +
>> static bool is_kfunc_arg_nullable(const struct btf *btf, const struct btf_param *arg)
>> {
>> - return btf_param_match_suffix(btf, arg, "__nullable");
>> + return !is_kfunc_arg_arena_nullable(btf, arg) &&
>> + btf_param_match_suffix(btf, arg, "__nullable");
>> }
>
> No need for the changes above.
>
>>
>> static bool is_kfunc_arg_nonown_allowed(const struct btf *btf, const struct btf_param *arg)
>> @@ -10929,6 +10936,12 @@ static bool is_kfunc_arg_irq_flag(const struct btf *btf, const struct btf_param
>> return btf_param_match_suffix(btf, arg, "__irq_flag");
>> }
>>
>> +static bool is_kfunc_arg_arena(const struct btf *btf, const struct btf_param *arg)
>> +{
>> + return is_kfunc_arg_arena_nullable(btf, arg) ||
>> + btf_param_match_suffix(btf, arg, "__arena");
>
> return btf_param_match_suffix(btf, arg, "__arena_nullable") ||
> btf_param_match_suffix(btf, arg, "__arena");
>
Ack, I'll adjust this bit.
> [...]
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH bpf-next v4 04/13] bpf: Support __arena and __arena__nullable kfunc argument suffixes
2026-08-06 19:20 ` Kumar Kartikeya Dwivedi
@ 2026-08-06 19:23 ` Kumar Kartikeya Dwivedi
2026-08-06 19:31 ` Amery Hung
0 siblings, 1 reply; 28+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-08-06 19:23 UTC (permalink / raw)
To: Amery Hung
Cc: bpf, Tejun Heo, Alexei Starovoitov, Andrii Nakryiko,
Daniel Borkmann, Eduard Zingerman, Emil Tsalapatis, kkd,
kernel-team
On Thu Aug 6, 2026 at 9:20 PM CEST, Kumar Kartikeya Dwivedi wrote:
> On Thu Aug 6, 2026 at 7:22 PM CEST, Amery Hung wrote:
>> On Wed, Aug 5, 2026 at 2:05 PM Kumar Kartikeya Dwivedi <memxor@gmail.com> wrote:
>>>
>>> From: Tejun Heo <tj@kernel.org>
>>>
>>> Passing an arena pointer to a kfunc takes two steps today. There is no
>>> arena pointer argument type, so the pointer crosses the boundary as a
>>> bare scalar, and the kfunc then offsets it by the arena base and casts
>>> it before it can touch the memory. Every such kfunc open-codes the same
>>> translation.
>>>
>>> Add the __arena and __arena__nullable argument suffixes to make this more
>>> convenient. The kfunc declares the parameter by its real pointer type
>>> and dereferences it directly, with the JIT rebasing the value at the
>>> call site, rN = kern_vm_start + (u32)rN. No bounds check is needed: the
>>> u32 offset stays within the guard-padded arena kernel mapping, and a
>>> fault on an unpopulated page recovers through the per-arena scratch
>>> page. A suffixed argument accepts a PTR_TO_ARENA or scalar register,
>>> matching global subprog arena arguments.
>>>
>>> __arena rebases unconditionally, so the kfunc never sees NULL and a
>>> value with zero in the low 32 bits arrives as the arena base.
>>> __arena__nullable preserves NULL for optional arguments by skipping the
>>> rebase when the truncated value, arena offset 0, is zero. Keeping the
>>> plain form NULL-free saves the NULL test on every call.
>>>
>>> The double separator makes the annotations composable:
>>> __arena__nullable also ends in __nullable. Match the composite suffix
>>> first when classifying kfunc arguments and function-model flags so it
>>> retains arena semantics while carrying the nullable flag.
>>
>> Since __arena__nullable will match is_kfunc_arg_arena() case and go
>> through JIT + regno check, and get its PTR_MAYBE_NULL anyway. How
>> about just keep it as __arena_nullable to simplify the patch?
>>
>> 1. No need to introudce is_kfunc_arg_arena_nullable() and changes in
>> is_kfunc_arg_nullable()
>
> For consistency, would you prefer that I don't manually set | PTR_MAYBE_NULL and
> let is_kfunc_arg_nullable() handle that? That would be another way to address
> this.
>
> In some sense, __arena includes __nullable for the purposes of type checking, so
> it might make sense to add it to the predicate that determines NULL-ness, then
> it will acquire PTR_MAYBE_NULL automatically.
>
> We will still drop is_kfunc_arg_arena_nullable() though.
>
> Anyhow, I don't have any strong preference one way or the other, but thought I'd
> float this as an alternative since it appears to fit better, and details are
> hidden the predicates.
This will amount to adding extra OR for __arena prefix match inside
is_kfunc_arg_nullable(), since __nullable should already match on
__arena__nullable.
>
>> 2. is_kfunc_arg_arena() returns btf_param_match_suffix(btf, arg,
>> "__arena_nullable") || btf_param_match_suffix(btf, arg, "__arena");
>>
>
> Yeah, makes sense.
>
>> [...]
>>
>>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>>> index b62e77949542..2b7f6f6bbe76 100644
>>> --- a/kernel/bpf/verifier.c
>>> +++ b/kernel/bpf/verifier.c
>>> @@ -10909,9 +10909,16 @@ static bool is_kfunc_arg_refcounted_kptr(const struct btf *btf, const struct btf
>>> return btf_param_match_suffix(btf, arg, "__refcounted_kptr");
>>> }
>>>
>>> +static bool is_kfunc_arg_arena_nullable(const struct btf *btf,
>>> + const struct btf_param *arg)
>>> +{
>>> + return btf_param_match_suffix(btf, arg, "__arena__nullable");
>>> +}
>>> +
>>> static bool is_kfunc_arg_nullable(const struct btf *btf, const struct btf_param *arg)
>>> {
>>> - return btf_param_match_suffix(btf, arg, "__nullable");
>>> + return !is_kfunc_arg_arena_nullable(btf, arg) &&
>>> + btf_param_match_suffix(btf, arg, "__nullable");
>>> }
>>
>> No need for the changes above.
>>
>>>
>>> static bool is_kfunc_arg_nonown_allowed(const struct btf *btf, const struct btf_param *arg)
>>> @@ -10929,6 +10936,12 @@ static bool is_kfunc_arg_irq_flag(const struct btf *btf, const struct btf_param
>>> return btf_param_match_suffix(btf, arg, "__irq_flag");
>>> }
>>>
>>> +static bool is_kfunc_arg_arena(const struct btf *btf, const struct btf_param *arg)
>>> +{
>>> + return is_kfunc_arg_arena_nullable(btf, arg) ||
>>> + btf_param_match_suffix(btf, arg, "__arena");
>>
>> return btf_param_match_suffix(btf, arg, "__arena_nullable") ||
>> btf_param_match_suffix(btf, arg, "__arena");
>>
>
> Ack, I'll adjust this bit.
>
>> [...]
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH bpf-next v4 04/13] bpf: Support __arena and __arena__nullable kfunc argument suffixes
2026-08-06 19:23 ` Kumar Kartikeya Dwivedi
@ 2026-08-06 19:31 ` Amery Hung
0 siblings, 0 replies; 28+ messages in thread
From: Amery Hung @ 2026-08-06 19:31 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi
Cc: bpf, Tejun Heo, Alexei Starovoitov, Andrii Nakryiko,
Daniel Borkmann, Eduard Zingerman, Emil Tsalapatis, kkd,
kernel-team
On Thu, Aug 6, 2026 at 12:23 PM Kumar Kartikeya Dwivedi
<memxor@gmail.com> wrote:
>
> On Thu Aug 6, 2026 at 9:20 PM CEST, Kumar Kartikeya Dwivedi wrote:
> > On Thu Aug 6, 2026 at 7:22 PM CEST, Amery Hung wrote:
> >> On Wed, Aug 5, 2026 at 2:05 PM Kumar Kartikeya Dwivedi <memxor@gmail.com> wrote:
> >>>
> >>> From: Tejun Heo <tj@kernel.org>
> >>>
> >>> Passing an arena pointer to a kfunc takes two steps today. There is no
> >>> arena pointer argument type, so the pointer crosses the boundary as a
> >>> bare scalar, and the kfunc then offsets it by the arena base and casts
> >>> it before it can touch the memory. Every such kfunc open-codes the same
> >>> translation.
> >>>
> >>> Add the __arena and __arena__nullable argument suffixes to make this more
> >>> convenient. The kfunc declares the parameter by its real pointer type
> >>> and dereferences it directly, with the JIT rebasing the value at the
> >>> call site, rN = kern_vm_start + (u32)rN. No bounds check is needed: the
> >>> u32 offset stays within the guard-padded arena kernel mapping, and a
> >>> fault on an unpopulated page recovers through the per-arena scratch
> >>> page. A suffixed argument accepts a PTR_TO_ARENA or scalar register,
> >>> matching global subprog arena arguments.
> >>>
> >>> __arena rebases unconditionally, so the kfunc never sees NULL and a
> >>> value with zero in the low 32 bits arrives as the arena base.
> >>> __arena__nullable preserves NULL for optional arguments by skipping the
> >>> rebase when the truncated value, arena offset 0, is zero. Keeping the
> >>> plain form NULL-free saves the NULL test on every call.
> >>>
> >>> The double separator makes the annotations composable:
> >>> __arena__nullable also ends in __nullable. Match the composite suffix
> >>> first when classifying kfunc arguments and function-model flags so it
> >>> retains arena semantics while carrying the nullable flag.
> >>
> >> Since __arena__nullable will match is_kfunc_arg_arena() case and go
> >> through JIT + regno check, and get its PTR_MAYBE_NULL anyway. How
> >> about just keep it as __arena_nullable to simplify the patch?
> >>
> >> 1. No need to introudce is_kfunc_arg_arena_nullable() and changes in
> >> is_kfunc_arg_nullable()
> >
> > For consistency, would you prefer that I don't manually set | PTR_MAYBE_NULL and
> > let is_kfunc_arg_nullable() handle that? That would be another way to address
> > this.
Yeah. This also makes sense. No strong preference here.
> >
> > In some sense, __arena includes __nullable for the purposes of type checking, so
> > it might make sense to add it to the predicate that determines NULL-ness, then
> > it will acquire PTR_MAYBE_NULL automatically.
> >
> > We will still drop is_kfunc_arg_arena_nullable() though.
> >
> > Anyhow, I don't have any strong preference one way or the other, but thought I'd
> > float this as an alternative since it appears to fit better, and details are
> > hidden the predicates.
>
> This will amount to adding extra OR for __arena prefix match inside
> is_kfunc_arg_nullable(), since __nullable should already match on
> __arena__nullable.
>
> >
> >> 2. is_kfunc_arg_arena() returns btf_param_match_suffix(btf, arg,
> >> "__arena_nullable") || btf_param_match_suffix(btf, arg, "__arena");
> >>
> >
> > Yeah, makes sense.
> >
> >> [...]
> >>
> >>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> >>> index b62e77949542..2b7f6f6bbe76 100644
> >>> --- a/kernel/bpf/verifier.c
> >>> +++ b/kernel/bpf/verifier.c
> >>> @@ -10909,9 +10909,16 @@ static bool is_kfunc_arg_refcounted_kptr(const struct btf *btf, const struct btf
> >>> return btf_param_match_suffix(btf, arg, "__refcounted_kptr");
> >>> }
> >>>
> >>> +static bool is_kfunc_arg_arena_nullable(const struct btf *btf,
> >>> + const struct btf_param *arg)
> >>> +{
> >>> + return btf_param_match_suffix(btf, arg, "__arena__nullable");
> >>> +}
> >>> +
> >>> static bool is_kfunc_arg_nullable(const struct btf *btf, const struct btf_param *arg)
> >>> {
> >>> - return btf_param_match_suffix(btf, arg, "__nullable");
> >>> + return !is_kfunc_arg_arena_nullable(btf, arg) &&
> >>> + btf_param_match_suffix(btf, arg, "__nullable");
> >>> }
> >>
> >> No need for the changes above.
> >>
> >>>
> >>> static bool is_kfunc_arg_nonown_allowed(const struct btf *btf, const struct btf_param *arg)
> >>> @@ -10929,6 +10936,12 @@ static bool is_kfunc_arg_irq_flag(const struct btf *btf, const struct btf_param
> >>> return btf_param_match_suffix(btf, arg, "__irq_flag");
> >>> }
> >>>
> >>> +static bool is_kfunc_arg_arena(const struct btf *btf, const struct btf_param *arg)
> >>> +{
> >>> + return is_kfunc_arg_arena_nullable(btf, arg) ||
> >>> + btf_param_match_suffix(btf, arg, "__arena");
> >>
> >> return btf_param_match_suffix(btf, arg, "__arena_nullable") ||
> >> btf_param_match_suffix(btf, arg, "__arena");
> >>
> >
> > Ack, I'll adjust this bit.
> >
> >> [...]
>
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH bpf-next v4 05/13] bpf: Support __arena and __arena__nullable on struct_ops arguments
2026-08-05 21:04 ` [PATCH bpf-next v4 05/13] bpf: Support __arena and __arena__nullable on struct_ops arguments Kumar Kartikeya Dwivedi
2026-08-05 21:18 ` sashiko-bot
@ 2026-08-07 0:51 ` Eduard Zingerman
1 sibling, 0 replies; 28+ messages in thread
From: Eduard Zingerman @ 2026-08-07 0:51 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi, bpf
Cc: Tejun Heo, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Emil Tsalapatis, kkd, kernel-team
On Wed, 2026-08-05 at 23:04 +0200, Kumar Kartikeya Dwivedi wrote:
Lgtm, but we botched communication regarding the squash.
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index f47556b56a48..0d1773af8373 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -1289,6 +1289,20 @@ struct bpf_tramp_nodes {
> int nr_nodes;
> };
>
> +/*
> + * Which 8-byte ctx slots of a struct_ops trampoline hold arena kernel
> + * pointers that save_args() converts to the arena pointer form,
> + * ctx[slot] = (u32)(kaddr - kern_vm_start).
> + */
> +struct bpf_tramp_arena_args {
> + u32 slots;
> + u32 nullable_slots; /* subset of @slots where NULL is preserved */
> + u64 kern_vm_start;
> +};
> +
> +bool bpf_tramp_collect_arena_args(struct bpf_tramp_nodes *tnodes, u32 flags,
> + struct bpf_tramp_arena_args *aargs);
> +
"bpf, x86: Convert struct_ops arena arguments in the trampoline"
removes the type and rewrites bpf_tramp_collect_arena_args()
as bpf_tramp_arena_base(). Please move these definitions here
to avoid unnecessary churn. Same for 'arena_nullable' field below.
> struct bpf_tramp_run_ctx;
>
> /* Different use cases for BPF trampoline:
> @@ -1690,6 +1704,11 @@ struct bpf_ctx_arg_aux {
> u32 btf_id;
> u32 ref_id;
> bool refcounted;
> + /*
> + * We don't encode NULL-ness in the type for the program, but still need
> + * to distinguish it for the purposes of telling JITs what sequence to emit.
> + */
> + bool arena_nullable;
> };
>
> struct btf_mod_pair {
> diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
...
> @@ -225,27 +229,35 @@ static int prepare_arg_info(struct btf *btf,
> /* Prepare info for every nullable argument */
> info = info_buf;
> for (arg_no = 0; arg_no < nargs; arg_no++) {
> - /* Skip arguments that is not suffixed with
> - * "__nullable or __ref".
> + /*
> + * Skip arguments that are not suffixed with "__arena__nullable",
> + * "__arena", "__nullable", or "__ref".
> */
> - is_nullable = btf_param_match_suffix(btf, &stub_args[arg_no],
> - MAYBE_NULL_SUFFIX);
> + is_arena_nullable = btf_param_match_suffix(btf, &stub_args[arg_no],
> + ARENA_MAYBE_NULL_SUFFIX);
> + is_arena = btf_param_match_suffix(btf, &stub_args[arg_no], ARENA_SUFFIX);
> + is_nullable = !is_arena_nullable &&
> + btf_param_match_suffix(btf, &stub_args[arg_no], MAYBE_NULL_SUFFIX);
> is_refcounted = btf_param_match_suffix(btf, &stub_args[arg_no],
> REFCOUNTED_SUFFIX);
>
> - if (is_nullable)
> + if (is_arena_nullable)
> + suffix = ARENA_MAYBE_NULL_SUFFIX;
> + else if (is_arena)
> + suffix = ARENA_SUFFIX;
> + else if (is_nullable)
> suffix = MAYBE_NULL_SUFFIX;
> else if (is_refcounted)
> suffix = REFCOUNTED_SUFFIX;
> else
> continue;
>
> - /* Should be a pointer to struct */
> - pointed_type = btf_type_resolve_ptr(btf,
> - args[arg_no].type,
> - &arg_btf_id);
> - if (!pointed_type ||
> - !btf_type_is_struct(pointed_type)) {
> + /*
> + * Should be a pointer to struct, or any pointer for __arena or
> + * __arena__nullable.
> + */
> + pointed_type = btf_type_resolve_ptr(btf, args[arg_no].type, &arg_btf_id);
> + if (!pointed_type || (!is_arena && !is_arena_nullable && !btf_type_is_struct(pointed_type))) {
Nit: it is so painful to read conditions like this, adding two variables 'ptr_to_struct'
and 'ptr_to_arena' would make it much nicer. (since respin is needed anyway).
...
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH bpf-next v4 04/13] bpf: Support __arena and __arena__nullable kfunc argument suffixes
2026-08-05 21:04 ` [PATCH bpf-next v4 04/13] bpf: Support __arena and __arena__nullable kfunc argument suffixes Kumar Kartikeya Dwivedi
2026-08-06 17:22 ` Amery Hung
@ 2026-08-07 0:51 ` Eduard Zingerman
1 sibling, 0 replies; 28+ messages in thread
From: Eduard Zingerman @ 2026-08-07 0:51 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi, bpf
Cc: Tejun Heo, Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Emil Tsalapatis, kkd, kernel-team
On Wed, 2026-08-05 at 23:04 +0200, Kumar Kartikeya Dwivedi wrote:
> From: Tejun Heo <tj@kernel.org>
>
> Passing an arena pointer to a kfunc takes two steps today. There is no
> arena pointer argument type, so the pointer crosses the boundary as a
> bare scalar, and the kfunc then offsets it by the arena base and casts
> it before it can touch the memory. Every such kfunc open-codes the same
> translation.
>
> Add the __arena and __arena__nullable argument suffixes to make this more
> convenient. The kfunc declares the parameter by its real pointer type
> and dereferences it directly, with the JIT rebasing the value at the
> call site, rN = kern_vm_start + (u32)rN. No bounds check is needed: the
> u32 offset stays within the guard-padded arena kernel mapping, and a
> fault on an unpopulated page recovers through the per-arena scratch
> page. A suffixed argument accepts a PTR_TO_ARENA or scalar register,
> matching global subprog arena arguments.
>
> __arena rebases unconditionally, so the kfunc never sees NULL and a
> value with zero in the low 32 bits arrives as the arena base.
> __arena__nullable preserves NULL for optional arguments by skipping the
> rebase when the truncated value, arena offset 0, is zero. Keeping the
> plain form NULL-free saves the NULL test on every call.
>
> The double separator makes the annotations composable:
> __arena__nullable also ends in __nullable. Match the composite suffix
> first when classifying kfunc arguments and function-model flags so it
> retains arena semantics while carrying the nullable flag.
>
> This patch adds the verifier side: the suffixes are recognized in
> check_kfunc_args() and distilled into argument flags in the function
> model stored in the kfunc descriptor. JITs retrieve the model while
> emitting the call, avoiding per-call state in insn_aux_data.
>
> JITs declare support with bpf_jit_supports_arena_args() and verification
> fails with -ENOTSUPP elsewhere.
>
> Signed-off-by: Tejun Heo <tj@kernel.org>
> Co-developed-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> ---
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
^ permalink raw reply [flat|nested] 28+ messages in thread
end of thread, other threads:[~2026-08-07 0:51 UTC | newest]
Thread overview: 28+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-05 21:04 [PATCH bpf-next v4 00/13] Add arena argument support to kfuncs and struct_ops Kumar Kartikeya Dwivedi
2026-08-05 21:04 ` [PATCH bpf-next v4 01/13] bpf: Rename 'early' BTF checking as a preparation phase Kumar Kartikeya Dwivedi
2026-08-06 16:29 ` Amery Hung
2026-08-05 21:04 ` [PATCH bpf-next v4 02/13] bpf: Split subprogram and kfunc collection Kumar Kartikeya Dwivedi
2026-08-05 21:49 ` bot+bpf-ci
2026-08-06 16:31 ` Amery Hung
2026-08-05 21:04 ` [PATCH bpf-next v4 03/13] bpf: Collect kfuncs after resolving program resources Kumar Kartikeya Dwivedi
2026-08-06 16:37 ` Amery Hung
2026-08-05 21:04 ` [PATCH bpf-next v4 04/13] bpf: Support __arena and __arena__nullable kfunc argument suffixes Kumar Kartikeya Dwivedi
2026-08-06 17:22 ` Amery Hung
2026-08-06 19:20 ` Kumar Kartikeya Dwivedi
2026-08-06 19:23 ` Kumar Kartikeya Dwivedi
2026-08-06 19:31 ` Amery Hung
2026-08-07 0:51 ` Eduard Zingerman
2026-08-05 21:04 ` [PATCH bpf-next v4 05/13] bpf: Support __arena and __arena__nullable on struct_ops arguments Kumar Kartikeya Dwivedi
2026-08-05 21:18 ` sashiko-bot
2026-08-07 0:51 ` Eduard Zingerman
2026-08-05 21:04 ` [PATCH bpf-next v4 06/13] bpf, x86: JIT __arena kfunc argument rebasing Kumar Kartikeya Dwivedi
2026-08-05 21:04 ` [PATCH bpf-next v4 07/13] bpf, x86: Convert struct_ops arena arguments in the trampoline Kumar Kartikeya Dwivedi
2026-08-05 21:04 ` [PATCH bpf-next v4 08/13] selftests/bpf: Add kfunc __arena and __arena__nullable argument tests Kumar Kartikeya Dwivedi
2026-08-05 21:04 ` [PATCH bpf-next v4 09/13] selftests/bpf: Add JIT-sequence tests for __arena kfunc arguments Kumar Kartikeya Dwivedi
2026-08-05 21:04 ` [PATCH bpf-next v4 10/13] selftests/bpf: Add struct_ops __arena and __arena__nullable argument tests Kumar Kartikeya Dwivedi
2026-08-05 21:15 ` sashiko-bot
2026-08-05 21:04 ` [PATCH bpf-next v4 11/13] bpf, x86: Fix stack-passed arguments for indirect trampolines Kumar Kartikeya Dwivedi
2026-08-05 21:15 ` sashiko-bot
2026-08-05 21:04 ` [PATCH bpf-next v4 12/13] selftests/bpf: Test stack-passed struct_ops arena arguments Kumar Kartikeya Dwivedi
2026-08-05 21:04 ` [PATCH bpf-next v4 13/13] bpf: Reject tracing progs for struct_ops with arena args Kumar Kartikeya Dwivedi
2026-08-05 22:02 ` bot+bpf-ci
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox