* [PATCH bpf-next 00/14] bpf: Support programs compiled by rust-bpf
@ 2026-09-30 19:31 Alexei Starovoitov
2026-09-30 19:31 ` [PATCH bpf-next 01/14] bpf: Allow bitwise ops, shifts and mul/div on pointers with CAP_PERFMON Alexei Starovoitov
` (13 more replies)
0 siblings, 14 replies; 31+ messages in thread
From: Alexei Starovoitov @ 2026-09-30 19:31 UTC (permalink / raw)
To: bpf; +Cc: daniel, andrii, eddyz87, memxor
From: Alexei Starovoitov <ast@kernel.org>
Rust programs compiled by rust-bpf keep all memory in arena and access
it through plain numbers, since there are no address spaces in Rust.
core tags pointers in the low bit. BTF of such program has names like
'Option<alloc::collections::btree::map::BTreeMap<u32, u32>>'.
Patches 1-2 Allow ALU on pointers with CAP_PERFMON. The result is
a number.
Patches 3-4 Treat load and store through a number as arena access
in programs loaded with BPF_F_ARENA_SCALAR.
Patches 5-10 Accept BTF of Rust programs in the kernel: names of types
and functions that are not C identifiers, arguments of
static functions without names, a variable in DATASEC
that is smaller than its type.
Patches 11-13 libbpf: Keep .data, .bss and .rodata in arena when
the object has .arena.data section and load its programs
with BPF_F_ARENA_SCALAR. Format strings of bpf_printk() go
to .rodata.str, which stays a map.
Patch 14 A program in Rust, built by upstream rustc, that shows why
the data has to be in arena.
No changes for unprivileged programs. Without the flag a number is not
an address, whether the program has an arena or not.
Arena access through a number is for x86 and arm64: other JITs have to
say that they take BPF_REG_AX as the address, the flag is rejected with
-EOPNOTSUPP there. arm64 is compile tested only.
With the series the kernel loads BTF of scx_simple and scx_cosmos
schedulers written in Rust, of the unwind and of the immediate-abort
builds. The objects call bpf_alloc(), bpf_free(), bpf_arena_memcpy() and
bpf_arena_memcmp(), which are not in the series, so they don't load yet.
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Alexei Starovoitov (14):
bpf: Allow bitwise ops, shifts and mul/div on pointers with
CAP_PERFMON
selftests/bpf: Add tests for ALU on pointers with CAP_PERFMON
bpf: Treat load and store through a number as arena access
selftests/bpf: Add tests for arena access through numbers
bpf: Allow names of Rust types and functions in BTF
selftests/bpf: Add tests for names of Rust types and functions in BTF
bpf: Allow arguments without names in static functions in BTF
selftests/bpf: Add test for arguments without names in static
functions
bpf: Allow a variable in DATASEC that is smaller than its type
selftests/bpf: Add tests for a variable that is smaller than its type
libbpf: Keep global data in arena when the object has .arena.data
libbpf: Keep format strings of bpf_printk() in .rodata.str
selftests/bpf: Add test for global data in arena
selftests/bpf: Add test for global data of a program in Rust
arch/arm64/net/bpf_jit_comp.c | 5 +
arch/x86/net/bpf_jit_comp.c | 6 +
include/linux/bpf_verifier.h | 2 +
include/linux/filter.h | 1 +
include/uapi/linux/bpf.h | 6 +
kernel/bpf/btf.c | 50 +-
kernel/bpf/core.c | 11 +
kernel/bpf/fixups.c | 39 +
kernel/bpf/syscall.c | 4 +
kernel/bpf/verifier.c | 74 +-
tools/include/uapi/linux/bpf.h | 6 +
tools/lib/bpf/bpf_helpers.h | 18 +-
tools/lib/bpf/libbpf.c | 415 +++++++-
tools/testing/selftests/bpf/Makefile | 12 +-
.../testing/selftests/bpf/Makefile.buildvars | 19 +
tools/testing/selftests/bpf/Makefile.skel | 17 +-
.../bpf/prog_tests/arena_scalar_blinded.c | 21 +
tools/testing/selftests/bpf/prog_tests/btf.c | 105 +-
.../selftests/bpf/prog_tests/btf_rust.c | 147 +++
.../selftests/bpf/prog_tests/data_in_arena.c | 171 ++++
.../selftests/bpf/prog_tests/verifier.c | 2 +
.../selftests/bpf/progs/data_in_arena.c | 107 ++
.../selftests/bpf/progs/data_in_arena_decl.c | 37 +
.../bpf/progs/data_in_arena_extern.c | 20 +
.../selftests/bpf/progs/data_in_arena_fail.c | 20 +
.../selftests/bpf/progs/data_in_arena_rust.rs | 70 ++
.../bpf/progs/verifier_arena_scalar.c | 912 ++++++++++++++++++
.../bpf/progs/verifier_value_illegal_alu.c | 173 +++-
tools/testing/selftests/bpf/test_loader.c | 2 +
29 files changed, 2373 insertions(+), 99 deletions(-)
create mode 100644 tools/testing/selftests/bpf/prog_tests/arena_scalar_blinded.c
create mode 100644 tools/testing/selftests/bpf/prog_tests/btf_rust.c
create mode 100644 tools/testing/selftests/bpf/prog_tests/data_in_arena.c
create mode 100644 tools/testing/selftests/bpf/progs/data_in_arena.c
create mode 100644 tools/testing/selftests/bpf/progs/data_in_arena_decl.c
create mode 100644 tools/testing/selftests/bpf/progs/data_in_arena_extern.c
create mode 100644 tools/testing/selftests/bpf/progs/data_in_arena_fail.c
create mode 100644 tools/testing/selftests/bpf/progs/data_in_arena_rust.rs
create mode 100644 tools/testing/selftests/bpf/progs/verifier_arena_scalar.c
base-commit: acff58e305175df35985082b0e79103a2497f702
--
2.55.0
^ permalink raw reply [flat|nested] 31+ messages in thread
* [PATCH bpf-next 01/14] bpf: Allow bitwise ops, shifts and mul/div on pointers with CAP_PERFMON
2026-09-30 19:31 [PATCH bpf-next 00/14] bpf: Support programs compiled by rust-bpf Alexei Starovoitov
@ 2026-09-30 19:31 ` Alexei Starovoitov
2026-09-30 19:31 ` [PATCH bpf-next 02/14] selftests/bpf: Add tests for ALU " Alexei Starovoitov
` (12 subsequent siblings)
13 siblings, 0 replies; 31+ messages in thread
From: Alexei Starovoitov @ 2026-09-30 19:31 UTC (permalink / raw)
To: bpf; +Cc: daniel, andrii, eddyz87, memxor
From: Alexei Starovoitov <ast@kernel.org>
Rust's core::fmt keeps a flag in the low bit of a pointer:
r2 = *(u64 *)(r1 + 0)
r3 = r2
r3 &= 1
r2 >>= 1
The verifier rejects it:
r0 &= 8
R0 bitwise operator &= on pointer prohibited
Negation and byte swap of a pointer already produce a number when
allow_ptr_leaks is set. Do the same for bitwise ops, shifts, *=, /= and
%=, 64-bit and 32-bit. The result is an unknown number. With CAP_PERFMON
the program can store the pointer and load it back as a number already.
+= and -= are not changed: they keep the pointer, 32-bit += is rejected.
Pointers that allow no arithmetic and pointers that may be NULL are
still rejected.
Two tests in verifier_value_illegal_alu store through the result of
&= and /=. They are rejected at the store now. Update expected messages.
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
---
kernel/bpf/verifier.c | 13 ++++++++++++-
.../bpf/progs/verifier_value_illegal_alu.c | 8 ++++----
2 files changed, 16 insertions(+), 5 deletions(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 1599bac1bac9..4bd14f4bcf40 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -15543,8 +15543,14 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, struct bpf_insn
u32 dst = insn->dst_reg;
const char *reason;
int ret, bounds_ret;
+ bool to_scalar;
dst_reg = ®s[dst];
+ /*
+ * Only += and -= keep a pointer. Any other op makes a number of it,
+ * which is fine when the program may see values of pointers anyway.
+ */
+ to_scalar = opcode != BPF_ADD && opcode != BPF_SUB && env->allow_ptr_leaks;
if ((known && (smin_val != smax_val || umin_val != umax_val)) ||
smin_val > smax_val || umin_val > umax_val) {
@@ -15555,7 +15561,7 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, struct bpf_insn
return 0;
}
- if (BPF_CLASS(insn->code) != BPF_ALU64) {
+ if (BPF_CLASS(insn->code) != BPF_ALU64 && !to_scalar) {
/* 32-bit ALU ops on pointers produce (meaningless) scalars */
if (opcode == BPF_SUB && env->allow_ptr_leaks) {
__mark_reg_unknown(env, dst_reg);
@@ -15621,6 +15627,11 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, struct bpf_insn
return -EACCES;
}
+ if (to_scalar) {
+ __mark_reg_unknown(env, dst_reg);
+ return 0;
+ }
+
/* For 'scalar += pointer', dst_reg inherits the complete pointer
* register state. Individual fields may be adjusted later by pointer
* arithmetic. Callers guarantee that below does not overwrite off_reg.
diff --git a/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c b/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c
index 4d8273c258d5..9f669cf85c59 100644
--- a/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c
+++ b/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c
@@ -22,8 +22,8 @@ struct {
SEC("socket")
__description("map element value illegal alu op, 1")
-__failure __msg("R0 bitwise operator &= on pointer")
-__failure_unpriv
+__failure __msg("R0 invalid mem access 'scalar'")
+__failure_unpriv __msg_unpriv("R0 bitwise operator &= on pointer")
__naked void value_illegal_alu_op_1(void)
{
asm volatile (" \
@@ -70,8 +70,8 @@ l0_%=: exit; \
SEC("socket")
__description("map element value illegal alu op, 3")
-__failure __msg("R0 pointer arithmetic with /= operator")
-__failure_unpriv
+__failure __msg("R0 invalid mem access 'scalar'")
+__failure_unpriv __msg_unpriv("R0 pointer arithmetic with /= operator")
__naked void value_illegal_alu_op_3(void)
{
asm volatile (" \
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 02/14] selftests/bpf: Add tests for ALU on pointers with CAP_PERFMON
2026-09-30 19:31 [PATCH bpf-next 00/14] bpf: Support programs compiled by rust-bpf Alexei Starovoitov
2026-09-30 19:31 ` [PATCH bpf-next 01/14] bpf: Allow bitwise ops, shifts and mul/div on pointers with CAP_PERFMON Alexei Starovoitov
@ 2026-09-30 19:31 ` Alexei Starovoitov
2026-09-30 19:31 ` [PATCH bpf-next 03/14] bpf: Treat load and store through a number as arena access Alexei Starovoitov
` (11 subsequent siblings)
13 siblings, 0 replies; 31+ messages in thread
From: Alexei Starovoitov @ 2026-09-30 19:31 UTC (permalink / raw)
To: bpf; +Cc: daniel, andrii, eddyz87, memxor
From: Alexei Starovoitov <ast@kernel.org>
Check that &=, |=, ^=, >>=, *= and 32-bit &= on a pointer to map value
produce a number for a program with CAP_PERFMON and that the number
can't be dereferenced. They are rejected without CAP_PERFMON, with
CAP_BPF or without it. Pointer that may be NULL and pointer to map are
rejected as before.
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
---
.../bpf/progs/verifier_value_illegal_alu.c | 165 ++++++++++++++++++
1 file changed, 165 insertions(+)
diff --git a/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c b/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c
index 9f669cf85c59..31663338866d 100644
--- a/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c
+++ b/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c
@@ -165,6 +165,171 @@ __naked void map_ptr_illegal_alu_op(void)
: __clobber_all);
}
+SEC("socket")
+__description("tag in the low bit of a pointer, and, shift")
+__success __retval(0)
+__failure_unpriv __msg_unpriv("R1 bitwise operator &= on pointer")
+__naked void ptr_tag_and_shift(void)
+{
+ asm volatile (" \
+ r2 = r10; \
+ r2 += -8; \
+ r1 = 0; \
+ *(u64*)(r2 + 0) = r1; \
+ r1 = %[map_hash_48b] ll; \
+ call %[bpf_map_lookup_elem]; \
+ if r0 == 0 goto l0_%=; \
+ r1 = r0; \
+ r1 &= 1; \
+ r2 = r0; \
+ r2 >>= 1; \
+ r3 = r0; \
+ r3 |= 1; \
+ r3 ^= 1; \
+ r0 = *(u32*)(r0 + 0); \
+ r0 = 0; \
+l0_%=: exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_48b)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("tag in the low bit of a pointer, CAP_BPF without CAP_PERFMON")
+__success __retval(0)
+__failure_unpriv __msg_unpriv("R1 bitwise operator &= on pointer")
+__caps_unpriv(CAP_BPF)
+__naked void ptr_tag_cap_bpf(void)
+{
+ asm volatile (" \
+ r2 = r10; \
+ r2 += -8; \
+ r1 = 0; \
+ *(u64*)(r2 + 0) = r1; \
+ r1 = %[map_hash_48b] ll; \
+ call %[bpf_map_lookup_elem]; \
+ if r0 == 0 goto l0_%=; \
+ r1 = r0; \
+ r1 &= 1; \
+ r0 = 0; \
+l0_%=: exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_48b)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("number op= pointer")
+__success __retval(0)
+__failure_unpriv __msg_unpriv("R1 pointer arithmetic with *= operator")
+__naked void number_mul_ptr(void)
+{
+ asm volatile (" \
+ r2 = r10; \
+ r2 += -8; \
+ r1 = 0; \
+ *(u64*)(r2 + 0) = r1; \
+ r1 = %[map_hash_48b] ll; \
+ call %[bpf_map_lookup_elem]; \
+ if r0 == 0 goto l0_%=; \
+ r1 = 7; \
+ r1 *= r0; \
+ r0 = 0; \
+l0_%=: exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_48b)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("pointer with the tag cleared is a number")
+__failure __msg("R0 invalid mem access 'scalar'")
+__failure_unpriv __msg_unpriv("R0 bitwise operator |= on pointer")
+__naked void ptr_tag_cleared_deref(void)
+{
+ asm volatile (" \
+ r2 = r10; \
+ r2 += -8; \
+ r1 = 0; \
+ *(u64*)(r2 + 0) = r1; \
+ r1 = %[map_hash_48b] ll; \
+ call %[bpf_map_lookup_elem]; \
+ if r0 == 0 goto l0_%=; \
+ r0 |= 1; \
+ r0 ^= 1; \
+ r0 = *(u32*)(r0 + 0); \
+l0_%=: r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_48b)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("shift of a pointer that may be NULL")
+__failure __msg("R0 pointer arithmetic on map_value_or_null prohibited, null-check it first")
+__failure_unpriv
+__naked void ptr_or_null_shift(void)
+{
+ asm volatile (" \
+ r2 = r10; \
+ r2 += -8; \
+ r1 = 0; \
+ *(u64*)(r2 + 0) = r1; \
+ r1 = %[map_hash_48b] ll; \
+ call %[bpf_map_lookup_elem]; \
+ r0 >>= 1; \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_48b)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("and of a pointer to map")
+__failure __msg("R0 pointer arithmetic on map_ptr prohibited")
+__failure_unpriv
+__naked void map_ptr_and(void)
+{
+ asm volatile (" \
+ r0 = %[map_hash_48b] ll; \
+ r0 &= 1; \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm_addr(map_hash_48b)
+ : __clobber_all);
+}
+
+SEC("socket")
+__description("32-bit and of a pointer")
+__success __retval(0)
+__failure_unpriv __msg_unpriv("R0 32-bit pointer arithmetic prohibited")
+__naked void ptr_and32(void)
+{
+ asm volatile (" \
+ r2 = r10; \
+ r2 += -8; \
+ r1 = 0; \
+ *(u64*)(r2 + 0) = r1; \
+ r1 = %[map_hash_48b] ll; \
+ call %[bpf_map_lookup_elem]; \
+ if r0 == 0 goto l0_%=; \
+ w0 &= 1; \
+l0_%=: r0 = 0; \
+ exit; \
+" :
+ : __imm(bpf_map_lookup_elem),
+ __imm_addr(map_hash_48b)
+ : __clobber_all);
+}
+
SEC("flow_dissector")
__description("flow_keys illegal alu op with variable offset")
__failure __msg("R7 pointer arithmetic on flow_keys prohibited")
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 03/14] bpf: Treat load and store through a number as arena access
2026-09-30 19:31 [PATCH bpf-next 00/14] bpf: Support programs compiled by rust-bpf Alexei Starovoitov
2026-09-30 19:31 ` [PATCH bpf-next 01/14] bpf: Allow bitwise ops, shifts and mul/div on pointers with CAP_PERFMON Alexei Starovoitov
2026-09-30 19:31 ` [PATCH bpf-next 02/14] selftests/bpf: Add tests for ALU " Alexei Starovoitov
@ 2026-09-30 19:31 ` Alexei Starovoitov
2026-09-30 19:31 ` [PATCH bpf-next 04/14] selftests/bpf: Add tests for arena access through numbers Alexei Starovoitov
` (10 subsequent siblings)
13 siblings, 0 replies; 31+ messages in thread
From: Alexei Starovoitov @ 2026-09-30 19:31 UTC (permalink / raw)
To: bpf; +Cc: daniel, andrii, eddyz87, memxor
From: Alexei Starovoitov <ast@kernel.org>
There are no address spaces in Rust. LLVM emits plain loads and stores
for arena memory, without cast_kern:
r3 = 0x20 ll // R_BPF_64_64 .bss, libbpf puts it into arena
r2 = *(u64 *)(r3 + 0) // R3 invalid mem access 'scalar'
lock *(u64 *)(r2 + 8) += r1
Add BPF_F_ARENA_SCALAR flag of BPF_PROG_LOAD. When the program is loaded
with it and has an arena treat ldx, stx, st and atomics through a number
as arena access. It's as safe as access through PTR_TO_ARENA: JIT adds
the base of arena to the low 32 bits of the address. The program has
an arena only with CAP_BPF and CAP_PERFMON.
Registers of the program are not changed, since Rust compares and
stores the address after the access. bpf_do_misc_fixups() copies
the low 32 bits into BPF_REG_AX and the access goes through it:
r2 = *(u64 *)(r3 + 0) -> w12 = w3
r2 = *(u64 *)(r12 + 0)
Constant blinding needs BPF_REG_AX for immediates and leaves insns that
use BPF_REG_AX alone. So the immediate of st through a number is not
blinded, the rest of the program is:
*(u64 *)(r3 + 0) = 1 -> w12 = w3
*(u64 *)(r12 + 0) = 1
The same insn may see PTR_TO_ARENA on another path. It works for both.
It's a flag, since a number is also what the verifier makes of a pointer
that went away when the program has CAP_PERFMON: a ringbuf record after
bpf_ringbuf_submit(), a pointer to the packet after bpf_skb_pull_data().
Programs in C that have an arena keep "invalid mem access 'scalar'" for
such bugs.
JIT tells with bpf_jit_supports_arena_scalar() that it takes BPF_REG_AX
as the address of arena access. With other JITs the flag is rejected
with -EOPNOTSUPP. x86 and arm64 do:
- x86: the fault handler finds the register that holds the address
through reg2pt_regs[]. Add BPF_REG_AX, r10 of x86, there.
- arm64: nothing else is needed. The JIT takes any register as the
address and the fault handler reads it by its number. Compile tested
only.
Any number is an address of arena, NULL and small numbers included, like
it is for PTR_TO_ARENA: arena code in C dereferences NULL and relies on
the fault being handled.
Still rejected:
- insn that sees a number on one path and a pointer that is not
PTR_TO_ARENA on another.
- numbers passed to helpers and kfuncs, except __arena arguments.
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
---
arch/arm64/net/bpf_jit_comp.c | 5 +++
arch/x86/net/bpf_jit_comp.c | 6 ++++
include/linux/bpf_verifier.h | 2 ++
include/linux/filter.h | 1 +
include/uapi/linux/bpf.h | 6 ++++
kernel/bpf/core.c | 6 ++++
kernel/bpf/fixups.c | 39 ++++++++++++++++++++++
kernel/bpf/syscall.c | 4 +++
kernel/bpf/verifier.c | 61 +++++++++++++++++++++++++++-------
tools/include/uapi/linux/bpf.h | 6 ++++
10 files changed, 124 insertions(+), 12 deletions(-)
diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c
index 475e70653454..6979c8ead0e8 100644
--- a/arch/arm64/net/bpf_jit_comp.c
+++ b/arch/arm64/net/bpf_jit_comp.c
@@ -3413,6 +3413,11 @@ bool bpf_jit_supports_arena(void)
return true;
}
+bool bpf_jit_supports_arena_scalar(void)
+{
+ return true;
+}
+
bool bpf_jit_supports_insn(struct bpf_insn *insn, bool in_arena)
{
if (!in_arena)
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 6c7a0578760e..8199d28e2a12 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -236,6 +236,7 @@ static const int reg2pt_regs[] = {
[BPF_REG_7] = offsetof(struct pt_regs, r13),
[BPF_REG_8] = offsetof(struct pt_regs, r14),
[BPF_REG_9] = offsetof(struct pt_regs, r15),
+ [BPF_REG_AX] = offsetof(struct pt_regs, r10),
};
/*
@@ -4667,6 +4668,11 @@ bool bpf_jit_supports_arena(void)
return true;
}
+bool bpf_jit_supports_arena_scalar(void)
+{
+ return true;
+}
+
bool bpf_jit_supports_insn(struct bpf_insn *insn, bool in_arena)
{
if (!in_arena)
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 811342e3c041..0074f1356c58 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -672,6 +672,7 @@ struct bpf_insn_aux_data {
bool non_sleepable; /* helper/kfunc may be called from non-sleepable context */
bool is_iter_next; /* bpf_iter_<type>_next() kfunc call */
bool call_with_percpu_alloc_ptr; /* {this,per}_cpu_ptr() with prog percpu alloc */
+ bool arena_scalar; /* ldx/stx/st/atomic through a number, it's an address in arena */
u8 alu_state; /* used in combination with alu_limit */
/* true if STX or LDX instruction is a part of a spill/fill
* pattern for a bpf_fastcall call.
@@ -943,6 +944,7 @@ struct bpf_verifier_env {
bool strict_alignment; /* perform strict pointer alignment checks */
bool test_state_freq; /* test verifier with different pruning frequency */
bool test_reg_invariants; /* fail verification on register invariants violations */
+ bool arena_scalar; /* load and store through a number is arena access */
struct bpf_verifier_state *cur_state; /* current verifier state */
/* Search pruning optimization, array of list_heads for
* lists of struct bpf_verifier_state_list.
diff --git a/include/linux/filter.h b/include/linux/filter.h
index e42eccb0990e..9339c6131f8f 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -1250,6 +1250,7 @@ bool bpf_jit_supports_far_kfunc_call(void);
bool bpf_jit_supports_exceptions(void);
bool bpf_jit_supports_ptr_xchg(void);
bool bpf_jit_supports_arena(void);
+bool bpf_jit_supports_arena_scalar(void);
bool bpf_jit_supports_insn(struct bpf_insn *insn, bool in_arena);
bool bpf_jit_supports_private_stack(void);
bool bpf_jit_supports_large_stack(void);
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index 4687c3310996..e0ed44b1bbcb 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -1344,6 +1344,12 @@ enum bpf_perf_event_type {
/* The verifier internal test flag. Behavior is undefined */
#define BPF_F_TEST_REG_INVARIANTS (1U << 7)
+/*
+ * Load and store through a number is an access to the arena of the program
+ * at the low 32 bits of the number. It's for programs written in Rust.
+ */
+#define BPF_F_ARENA_SCALAR (1U << 8)
+
/* link_create.kprobe_multi.flags used in LINK_CREATE command for
* BPF_TRACE_KPROBE_MULTI attach type to create return probe.
*/
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index d3b8b626ec0f..a1721f9c0f52 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -3429,6 +3429,12 @@ bool __weak bpf_jit_supports_arena(void)
return false;
}
+/* Whether JIT takes BPF_REG_AX as the address of arena access */
+bool __weak bpf_jit_supports_arena_scalar(void)
+{
+ return false;
+}
+
bool __weak bpf_jit_supports_insn(struct bpf_insn *insn, bool in_arena)
{
return false;
diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
index 37cf130ebb57..b4bfe5522003 100644
--- a/kernel/bpf/fixups.c
+++ b/kernel/bpf/fixups.c
@@ -46,6 +46,31 @@ static bool is_addr_space_cast32(struct bpf_prog *prog, const struct bpf_insn *i
return false;
}
+/*
+ * The insn accesses arena through a number. JITs add the base of arena
+ * to the register as it is, so the access goes through the low 32 bits of
+ * the number in BPF_REG_AX. Registers of the program are not changed: the
+ * number may be compared or stored later, and on another path the register
+ * may be PTR_TO_ARENA.
+ *
+ * Constant blinding leaves insns that use BPF_REG_AX alone, so the immediate
+ * of st through a number is not blinded.
+ */
+static int arena_scalar_access(const struct bpf_insn *insn, struct bpf_insn *buf)
+{
+ bool load = BPF_CLASS(insn->code) == BPF_LDX || bpf_atomic_is_load_acq(insn);
+ struct bpf_insn *patch = buf;
+
+ *patch++ = BPF_MOV32_REG(BPF_REG_AX, load ? insn->src_reg : insn->dst_reg);
+ *patch = *insn;
+ if (load)
+ patch->src_reg = BPF_REG_AX;
+ else
+ patch->dst_reg = BPF_REG_AX;
+ patch++;
+ return patch - buf;
+}
+
/* Return the regno defined by the insn, or -1. */
static int insn_def_regno(const struct bpf_insn *insn)
{
@@ -1785,6 +1810,20 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
/* Convert BPF_CLASS(insn->code) == BPF_ALU64 to 32-bit ALU */
insn->code = BPF_ALU | BPF_OP(insn->code) | BPF_SRC(insn->code);
+ if (env->insn_aux_data[i + delta].arena_scalar) {
+ cnt = arena_scalar_access(insn, insn_buf);
+
+ new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
+ if (!new_prog)
+ return -ENOMEM;
+
+ delta += cnt - 1;
+ prog = new_prog;
+ env->prog = prog;
+ insn = prog->insnsi + i + delta;
+ goto next_insn;
+ }
+
/* Make sdiv/smod divide-by-minus-one exceptions impossible. */
if ((insn->code == (BPF_ALU64 | BPF_MOD | BPF_K) ||
insn->code == (BPF_ALU64 | BPF_DIV | BPF_K) ||
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index ac52f4ae414c..778bca2abc04 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -2948,9 +2948,13 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, struct bpf_log_at
BPF_F_XDP_HAS_FRAGS |
BPF_F_XDP_DEV_BOUND_ONLY |
BPF_F_TEST_REG_INVARIANTS |
+ BPF_F_ARENA_SCALAR |
BPF_F_TOKEN_FD))
return -EINVAL;
+ if ((attr->prog_flags & BPF_F_ARENA_SCALAR) && !bpf_jit_supports_arena_scalar())
+ return -EOPNOTSUPP;
+
bpf_prog_load_fixup_attach_type(attr);
if (attr->prog_flags & BPF_F_TOKEN_FD) {
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 4bd14f4bcf40..4468740d4ed5 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -5232,6 +5232,19 @@ static bool is_arena_reg(struct bpf_verifier_env *env, int regno)
return reg->type == PTR_TO_ARENA;
}
+/*
+ * There are no address spaces in Rust, addresses of arena are plain numbers.
+ * When the program is loaded with BPF_F_ARENA_SCALAR and has an arena a load or
+ * a store through a number is an access to arena at the low 32 bits of it,
+ * like the access through PTR_TO_ARENA is. The register stays a number.
+ */
+static bool is_arena_scalar(struct bpf_verifier_env *env, int regno)
+{
+ const struct bpf_reg_state *reg = reg_state(env, regno);
+
+ return reg->type == SCALAR_VALUE && env->arena_scalar && env->prog->aux->arena;
+}
+
static bool is_load_acq_unsafe(struct bpf_verifier_env *env, int regno,
struct bpf_insn *insn)
{
@@ -5262,7 +5275,7 @@ static bool atomic_ptr_type_ok(struct bpf_verifier_env *env, int regno,
return false;
if (is_sk_reg(env, regno))
return false;
- if (is_arena_reg(env, regno))
+ if (is_arena_reg(env, regno) || is_arena_scalar(env, regno))
return bpf_jit_supports_insn(insn, true);
if (is_load_acq_unsafe(env, regno, insn))
return false;
@@ -7143,6 +7156,22 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
static int save_aux_ptr_type(struct bpf_verifier_env *env, enum bpf_reg_type type,
bool allow_trust_mismatch);
+/*
+ * Returns the register to check the access of the current insn with.
+ * For a number in a program with an arena that is 'arena'.
+ */
+static struct bpf_reg_state *mem_access_reg(struct bpf_verifier_env *env, int regno,
+ struct bpf_reg_state *arena)
+{
+ if (!is_arena_scalar(env, regno))
+ return cur_regs(env) + regno;
+
+ memset(arena, 0, sizeof(*arena));
+ arena->type = PTR_TO_ARENA;
+ env->insn_aux_data[env->insn_idx].arena_scalar = true;
+ return arena;
+}
+
static int check_load_mem(struct bpf_verifier_env *env, struct bpf_insn *insn,
bool strict_alignment_once, bool is_ldsx,
bool allow_trust_mismatch, const char *ctx)
@@ -7150,6 +7179,7 @@ static int check_load_mem(struct bpf_verifier_env *env, struct bpf_insn *insn,
struct bpf_verifier_state *vstate = env->cur_state;
struct bpf_func_state *state = vstate->frame[vstate->curframe];
struct bpf_reg_state *regs = cur_regs(env);
+ struct bpf_reg_state arena, *src_reg;
enum bpf_reg_type src_reg_type;
int err;
@@ -7171,15 +7201,16 @@ static int check_load_mem(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (err)
return err;
- src_reg_type = regs[insn->src_reg].type;
+ src_reg = mem_access_reg(env, insn->src_reg, &arena);
+ src_reg_type = src_reg->type;
/*
* check_stack_read_fixed_off() may refine the modification's origin to
* the source stack slot.
*/
bpf_diag_mod_begin(env, ®s[insn->dst_reg], NULL, BPF_DIAG_MOD_WRITE);
- err = check_mem_access(env, env->insn_idx, regs + insn->src_reg, argno_from_reg(insn->src_reg), insn->off,
- BPF_SIZE(insn->code), BPF_READ, insn->dst_reg,
+ err = check_mem_access(env, env->insn_idx, src_reg, argno_from_reg(insn->src_reg),
+ insn->off, BPF_SIZE(insn->code), BPF_READ, insn->dst_reg,
strict_alignment_once, is_ldsx);
err = err ?: save_aux_ptr_type(env, src_reg_type,
allow_trust_mismatch);
@@ -7196,6 +7227,7 @@ static int check_store_reg(struct bpf_verifier_env *env, struct bpf_insn *insn,
struct bpf_verifier_state *vstate = env->cur_state;
struct bpf_func_state *state = vstate->frame[vstate->curframe];
struct bpf_reg_state *regs = cur_regs(env);
+ struct bpf_reg_state arena, *dst_reg;
enum bpf_reg_type dst_reg_type;
int err;
@@ -7217,11 +7249,12 @@ static int check_store_reg(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (err)
return err;
- dst_reg_type = regs[insn->dst_reg].type;
+ dst_reg = mem_access_reg(env, insn->dst_reg, &arena);
+ dst_reg_type = dst_reg->type;
/* Check if (dst_reg + off) is writeable. */
- err = check_mem_access(env, env->insn_idx, regs + insn->dst_reg, argno_from_reg(insn->dst_reg), insn->off,
- BPF_SIZE(insn->code), BPF_WRITE, insn->src_reg,
+ err = check_mem_access(env, env->insn_idx, dst_reg, argno_from_reg(insn->dst_reg),
+ insn->off, BPF_SIZE(insn->code), BPF_WRITE, insn->src_reg,
strict_alignment_once, false);
err = err ?: save_aux_ptr_type(env, dst_reg_type, false);
@@ -7231,7 +7264,7 @@ static int check_store_reg(struct bpf_verifier_env *env, struct bpf_insn *insn,
static int check_atomic_rmw(struct bpf_verifier_env *env,
struct bpf_insn *insn)
{
- struct bpf_reg_state *dst_reg;
+ struct bpf_reg_state arena, *dst_reg;
int load_reg;
int err;
@@ -7276,6 +7309,9 @@ static int check_atomic_rmw(struct bpf_verifier_env *env,
return -EACCES;
}
+ /* load_reg may be dst_reg. Look at dst_reg before it's marked as unknown. */
+ dst_reg = mem_access_reg(env, insn->dst_reg, &arena);
+
load_reg = bpf_atomic_load_reg(insn);
if (load_reg >= 0) {
/* check and record load of old value */
@@ -7284,8 +7320,6 @@ static int check_atomic_rmw(struct bpf_verifier_env *env,
return err;
}
- dst_reg = cur_regs(env) + insn->dst_reg;
-
/* Check whether we can read the memory, with second call for fetch
* case to simulate the register fill.
*/
@@ -19333,15 +19367,17 @@ static int do_check_insn(struct bpf_verifier_env *env, bool *do_print_state)
return check_stack_arg_write(env, state, insn->off, NULL);
}
+ struct bpf_reg_state arena, *dst_reg;
enum bpf_reg_type dst_reg_type;
err = check_reg_arg(env, insn->dst_reg, SRC_OP);
if (err)
return err;
- dst_reg_type = cur_regs(env)[insn->dst_reg].type;
+ dst_reg = mem_access_reg(env, insn->dst_reg, &arena);
+ dst_reg_type = dst_reg->type;
- err = check_mem_access(env, env->insn_idx, cur_regs(env) + insn->dst_reg, argno_from_reg(insn->dst_reg),
+ err = check_mem_access(env, env->insn_idx, dst_reg, argno_from_reg(insn->dst_reg),
insn->off, BPF_SIZE(insn->code),
BPF_WRITE, -1, false, false);
if (err)
@@ -22500,6 +22536,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
if (is_priv)
env->test_state_freq = attr->prog_flags & BPF_F_TEST_STATE_FREQ;
env->test_reg_invariants = attr->prog_flags & BPF_F_TEST_REG_INVARIANTS;
+ env->arena_scalar = attr->prog_flags & BPF_F_ARENA_SCALAR;
env->explored_states = kvzalloc_objs(struct list_head,
state_htab_size(env),
diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
index 4687c3310996..e0ed44b1bbcb 100644
--- a/tools/include/uapi/linux/bpf.h
+++ b/tools/include/uapi/linux/bpf.h
@@ -1344,6 +1344,12 @@ enum bpf_perf_event_type {
/* The verifier internal test flag. Behavior is undefined */
#define BPF_F_TEST_REG_INVARIANTS (1U << 7)
+/*
+ * Load and store through a number is an access to the arena of the program
+ * at the low 32 bits of the number. It's for programs written in Rust.
+ */
+#define BPF_F_ARENA_SCALAR (1U << 8)
+
/* link_create.kprobe_multi.flags used in LINK_CREATE command for
* BPF_TRACE_KPROBE_MULTI attach type to create return probe.
*/
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 04/14] selftests/bpf: Add tests for arena access through numbers
2026-09-30 19:31 [PATCH bpf-next 00/14] bpf: Support programs compiled by rust-bpf Alexei Starovoitov
` (2 preceding siblings ...)
2026-09-30 19:31 ` [PATCH bpf-next 03/14] bpf: Treat load and store through a number as arena access Alexei Starovoitov
@ 2026-09-30 19:31 ` Alexei Starovoitov
2026-09-30 19:48 ` sashiko-bot
2026-09-30 20:22 ` bot+bpf-ci
2026-09-30 19:31 ` [PATCH bpf-next 05/14] bpf: Allow names of Rust types and functions in BTF Alexei Starovoitov
` (9 subsequent siblings)
13 siblings, 2 replies; 31+ messages in thread
From: Alexei Starovoitov @ 2026-09-30 19:31 UTC (permalink / raw)
To: bpf; +Cc: daniel, andrii, eddyz87, memxor
From: Alexei Starovoitov <ast@kernel.org>
Add tests for load and store through the address that
bpf_arena_alloc_pages() returned, without cast_kern:
- all sizes, sign extending load, atomics, load-acquire, store-release.
- no register is changed by the access, whatever the upper half of
the address is, and 64-bit math on the address stays 64-bit.
- a number that is not an address of arena, address of the stack
included, reads zeroes and writes nowhere at both ends of the range
of insn offset.
- insn that sees a number on one path and PTR_TO_ARENA on another,
followed by access through PTR_TO_ARENA.
- store in a callback of bpf_loop().
- NULL that a helper returned and a number that is less than a page
are addresses of arena too.
- xchg that loads into the register that holds the address.
- rejected: prog without arena, prog without BPF_F_ARENA_SCALAR, pointer
to stack at the same insn, xchg that loads into the register that
holds a pointer to stack, numbers passed to helpers.
The tests are for x86 and arm64, other JITs don't take the flag.
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
---
.../bpf/prog_tests/arena_scalar_blinded.c | 21 +
.../selftests/bpf/prog_tests/verifier.c | 2 +
.../bpf/progs/verifier_arena_scalar.c | 912 ++++++++++++++++++
tools/testing/selftests/bpf/test_loader.c | 2 +
4 files changed, 937 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/arena_scalar_blinded.c
create mode 100644 tools/testing/selftests/bpf/progs/verifier_arena_scalar.c
diff --git a/tools/testing/selftests/bpf/prog_tests/arena_scalar_blinded.c b/tools/testing/selftests/bpf/prog_tests/arena_scalar_blinded.c
new file mode 100644
index 000000000000..2ac2e9a652fe
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/arena_scalar_blinded.c
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include "sysctl_helpers.h"
+#include "verifier_arena_scalar.skel.h"
+
+/* The same tests with constants of the programs blinded */
+void serial_test_arena_scalar_blinded(void)
+{
+ const char *harden = "/proc/sys/net/core/bpf_jit_harden";
+ char old[16] = {};
+
+ if (!is_jit_enabled()) {
+ test__skip();
+ return;
+ }
+ if (sysctl_set_or_fail(harden, old, "2"))
+ return;
+ RUN_TESTS(verifier_arena_scalar);
+ sysctl_set_or_fail(harden, NULL, old);
+}
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index 8a6d341b754a..460ad10ddc02 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -12,6 +12,7 @@
#include "verifier_and.skel.h"
#include "verifier_arena.skel.h"
#include "verifier_arena_large.skel.h"
+#include "verifier_arena_scalar.skel.h"
#include "verifier_arena_globals1.skel.h"
#include "verifier_arena_globals2.skel.h"
#include "verifier_array_access.skel.h"
@@ -198,6 +199,7 @@ void test_verifier_align(void) { RUN(verifier_align); }
void test_verifier_and(void) { RUN(verifier_and); }
void test_verifier_arena(void) { RUN(verifier_arena); }
void test_verifier_arena_large(void) { RUN(verifier_arena_large); }
+void test_verifier_arena_scalar(void) { RUN(verifier_arena_scalar); }
void test_verifier_arena_globals1(void) { RUN(verifier_arena_globals1); }
void test_verifier_arena_globals2(void) { RUN(verifier_arena_globals2); }
void test_verifier_basic_stack(void) { RUN(verifier_basic_stack); }
diff --git a/tools/testing/selftests/bpf/progs/verifier_arena_scalar.c b/tools/testing/selftests/bpf/progs/verifier_arena_scalar.c
new file mode 100644
index 000000000000..bebc37f501b7
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_arena_scalar.c
@@ -0,0 +1,912 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "../../../include/linux/filter.h"
+#include "bpf_misc.h"
+
+void *bpf_arena_alloc_pages(void *map, void *addr, __u32 page_cnt, int node_id,
+ __u64 flags) __ksym;
+
+#ifdef __TARGET_ARCH_arm64
+#define ARENA_VM_START (1ull << 32)
+#else
+#define ARENA_VM_START (1ull << 44)
+#endif
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARENA);
+ __uint(map_flags, BPF_F_MMAPABLE);
+ __uint(max_entries, 4);
+ __ulong(map_extra, ARENA_VM_START);
+} arena SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __uint(max_entries, 1);
+ __type(key, int);
+ __type(value, long long);
+} hash SEC(".maps");
+
+/* JITs that take BPF_F_ARENA_SCALAR */
+#define __arena_scalar __flag(BPF_F_ARENA_SCALAR) __arch_x86_64 __arch_arm64
+
+/* BTF FUNC records are not generated for kfuncs referenced from inline assembly */
+void __kfunc_btf_root(void)
+{
+ bpf_arena_alloc_pages(0, 0, 0, 0, 0);
+}
+
+/* Tests start with r6 = address of a new page as the user space sees it, a number */
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: load and store of every size")
+__success __retval(0)
+__load_if_JITed()
+__naked void ld_st_sizes(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ r7 = r6; \
+ r1 = 0x1122334455667788 ll; \
+ *(u64 *)(r6 + 0) = r1; \
+ *(u32 *)(r6 + 8) = r1; \
+ *(u16 *)(r6 + 12) = r1; \
+ *(u8 *)(r6 + 14) = r1; \
+ *(u64 *)(r6 + 16) = 0x1234; \
+ *(u32 *)(r6 + 24) = 0x5678; \
+ *(u16 *)(r6 + 28) = 0x9a; \
+ *(u8 *)(r6 + 30) = 0xbc; \
+ r0 = 1; \
+ r2 = *(u64 *)(r6 + 0); \
+ if r2 != r1 goto 9f; \
+ r0 = 2; \
+ r2 = *(u32 *)(r6 + 8); \
+ if r2 != 0x55667788 goto 9f; \
+ r0 = 3; \
+ r2 = *(u16 *)(r6 + 12); \
+ if r2 != 0x7788 goto 9f; \
+ r0 = 4; \
+ r2 = *(u8 *)(r6 + 14); \
+ if r2 != 0x88 goto 9f; \
+ r0 = 5; \
+ r2 = *(u64 *)(r6 + 16); \
+ if r2 != 0x1234 goto 9f; \
+ r0 = 6; \
+ r2 = *(u32 *)(r6 + 24); \
+ if r2 != 0x5678 goto 9f; \
+ r0 = 7; \
+ r2 = *(u16 *)(r6 + 28); \
+ if r2 != 0x9a goto 9f; \
+ r0 = 8; \
+ r2 = *(u8 *)(r6 + 30); \
+ if r2 != 0xbc goto 9f; \
+ /* the address is what it was */ \
+ r0 = 9; \
+ if r6 != r7 goto 9f; \
+ r0 = 10; \
+ r7 >>= 32; \
+ if r7 == 0 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: load into the register that holds the address")
+__success __retval(0)
+__load_if_JITed()
+__naked void ld_into_base(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ r1 = 77; \
+ *(u64 *)(r6 + 0) = r1; \
+ r6 = *(u64 *)(r6 + 0); \
+ r0 = 1; \
+ if r6 != 77 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: sign extending load")
+__success __retval(0)
+__load_if_JITed()
+__naked void ldsx(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ r7 = r6; \
+ *(u64 *)(r6 + 0) = 0x80; \
+ .8byte %[ldsx_insn]; /* r2 = *(s8 *)(r6 + 0) */ \
+ r0 = 1; \
+ if r2 != -128 goto 9f; \
+ r0 = 2; \
+ if r6 != r7 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages),
+ __imm_insn(ldsx_insn, BPF_RAW_INSN(BPF_LDX | BPF_MEMSX | BPF_B,
+ BPF_REG_2, BPF_REG_6, 0, 0))
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: address loaded from arena")
+__success __retval(0)
+__load_if_JITed()
+__naked void ptr_chase(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ /* page[0] = &page[64]; page[64] = 5; */ \
+ r1 = r6; \
+ r1 += 64; \
+ *(u64 *)(r6 + 0) = r1; \
+ *(u64 *)(r1 + 0) = 5; \
+ r2 = *(u64 *)(r6 + 0); \
+ r0 = 1; \
+ if r2 != r1 goto 9f; \
+ r3 = *(u64 *)(r2 + 0); \
+ r0 = 2; \
+ if r3 != 5 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: atomics")
+__success __retval(0)
+__load_if_JITed()
+__naked void atomics(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ r7 = r6; \
+ *(u64 *)(r6 + 8) = 1; \
+ r1 = 2; \
+ lock *(u64 *)(r6 + 8) += r1; \
+ r1 = 4; \
+ .8byte %[fetch_add_insn]; /* r1 = atomic_fetch_add((u64 *)(r6 + 8), r1) */ \
+ r0 = 1; \
+ if r1 != 3 goto 9f; \
+ r1 = 8; \
+ .8byte %[xchg_insn]; /* r1 = xchg_64(r6 + 8, r1) */ \
+ r0 = 2; \
+ if r1 != 7 goto 9f; \
+ r0 = 8; \
+ r1 = 16; \
+ .8byte %[cmpxchg_insn]; /* r0 = cmpxchg_64(r6 + 8, r0, r1) */ \
+ r2 = r0; \
+ r0 = 3; \
+ if r2 != 8 goto 9f; \
+ r2 = *(u64 *)(r6 + 8); \
+ r0 = 4; \
+ if r2 != 16 goto 9f; \
+ r0 = 5; \
+ if r6 != r7 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages),
+ __imm_insn(fetch_add_insn, BPF_ATOMIC_OP(BPF_DW, BPF_ADD | BPF_FETCH,
+ BPF_REG_6, BPF_REG_1, 8)),
+ __imm_insn(xchg_insn, BPF_ATOMIC_OP(BPF_DW, BPF_XCHG, BPF_REG_6, BPF_REG_1, 8)),
+ __imm_insn(cmpxchg_insn, BPF_ATOMIC_OP(BPF_DW, BPF_CMPXCHG, BPF_REG_6, BPF_REG_1, 8))
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: cmpxchg through r0")
+__success __retval(0)
+__load_if_JITed()
+__naked void cmpxchg_r0(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ /* The address is in r0. The value at the address is not equal to it. */ \
+ *(u64 *)(r6 + 0) = 3; \
+ r0 = r6; \
+ r1 = 5; \
+ .8byte %[cmpxchg_insn]; /* r0 = cmpxchg_64(r0 + 0, r0, r1) */ \
+ r2 = r0; \
+ r0 = 1; \
+ if r2 != 3 goto 9f; \
+ r2 = *(u64 *)(r6 + 0); \
+ r0 = 2; \
+ if r2 != 3 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages),
+ __imm_insn(cmpxchg_insn, BPF_ATOMIC_OP(BPF_DW, BPF_CMPXCHG, BPF_REG_0, BPF_REG_1, 0))
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: xchg into the register that holds the address")
+__success __retval(0)
+__load_if_JITed()
+__naked void xchg_into_base(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ *(u64 *)(r6 + 0) = 3; \
+ r1 = r6; \
+ .8byte %[xchg_insn]; /* r1 = xchg_64(r1 + 0, r1) */ \
+ r0 = 1; \
+ if r1 != 3 goto 9f; \
+ r2 = *(u64 *)(r6 + 0); \
+ r0 = 2; \
+ if r2 != r6 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages),
+ __imm_insn(xchg_insn, BPF_ATOMIC_OP(BPF_DW, BPF_XCHG, BPF_REG_1, BPF_REG_1, 0))
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: xchg into the register that holds a pointer to stack")
+__failure __msg("misaligned access off (0x0; 0xffffffffffffffff)+0 size 8")
+__naked void xchg_into_stack_ptr(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r1 = 0; \
+ *(u64 *)(r10 - 8) = r1; \
+ r1 = r10; \
+ r1 += -8; \
+ .8byte %[xchg_insn]; /* r1 = xchg_64(r1 + 0, r1) */ \
+ r0 = 0; \
+ exit; \
+" :
+ : __imm_addr(arena),
+ __imm_insn(xchg_insn, BPF_ATOMIC_OP(BPF_DW, BPF_XCHG, BPF_REG_1, BPF_REG_1, 0))
+ : __clobber_all);
+}
+
+#ifdef CAN_USE_LOAD_ACQ_STORE_REL
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: load-acquire and store-release")
+__success __retval(0)
+__load_if_JITed()
+__naked void load_acq_store_rel(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ r7 = r6; \
+ r1 = 0x1234; \
+ .8byte %[store_release_insn]; /* store_release((u64 *)(r6 + 8), r1) */ \
+ .8byte %[load_acquire_insn]; /* r2 = load_acquire((u64 *)(r6 + 8)) */ \
+ r0 = 1; \
+ if r2 != 0x1234 goto 9f; \
+ .8byte %[load_acquire8_insn]; /* w2 = load_acquire((u8 *)(r6 + 8)) */ \
+ r0 = 2; \
+ if r2 != 0x34 goto 9f; \
+ r0 = 3; \
+ if r6 != r7 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages),
+ __imm_insn(store_release_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_STORE_REL, BPF_REG_6, BPF_REG_1, 8)),
+ __imm_insn(load_acquire_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_LOAD_ACQ, BPF_REG_2, BPF_REG_6, 8)),
+ __imm_insn(load_acquire8_insn,
+ BPF_ATOMIC_OP(BPF_B, BPF_LOAD_ACQ, BPF_REG_2, BPF_REG_6, 8))
+ : __clobber_all);
+}
+
+#endif /* CAN_USE_LOAD_ACQ_STORE_REL */
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: number that is not an address in arena")
+__success __retval(0)
+__load_if_JITed()
+__naked void not_in_arena(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ /* nothing is allocated: all loads read 0, stores are dropped */ \
+ r6 = 0xdeadbeef00000000 ll; \
+ r0 = 1; \
+ r2 = *(u64 *)(r6 + 0); \
+ if r2 != 0 goto 9f; \
+ r0 = 2; \
+ r2 = *(u8 *)(r6 - 32768); \
+ if r2 != 0 goto 9f; \
+ r6 = 0x12345678ffffffff ll; \
+ r0 = 3; \
+ r2 = *(u64 *)(r6 + 32760); \
+ if r2 != 0 goto 9f; \
+ *(u64 *)(r6 + 32760) = 1; \
+ *(u8 *)(r6 + 32767) = r2; \
+ r1 = 1; \
+ lock *(u64 *)(r6 + 32760) += r1; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: store through the address of the stack as a number")
+__success __retval(0)
+__load_if_JITed()
+__naked void stack_addr_as_number(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ *(u64 *)(r10 - 8) = 5; \
+ r6 = r10; \
+ r6 |= 0; \
+ /* r6 is a number now. The store goes to arena, not to the stack. */ \
+ *(u64 *)(r6 - 8) = 7; \
+ r1 = 9; \
+ *(u64 *)(r6 - 8) = r1; \
+ lock *(u64 *)(r6 - 8) += r1; \
+ r2 = *(u64 *)(r10 - 8); \
+ r0 = 1; \
+ if r2 != 5 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: 64-bit math on the address stays 64-bit")
+__success __retval(0)
+__load_if_JITed()
+__naked void alu64_after_access(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ r2 = *(u64 *)(r6 + 0); \
+ r7 = r6; \
+ r7 += 8; \
+ r7 -= r6; \
+ r0 = 1; \
+ if r7 != 8 goto 9f; \
+ r7 = r6; \
+ r7 >>= 32; \
+ r0 = 2; \
+ if r7 == 0 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: st of an immediate changes no register")
+__success __retval(0)
+__load_if_JITed()
+__naked void st_keeps_regs(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ r0 = r6; \
+ r1 = 0x1111; \
+ r2 = 0x2222; \
+ *(u64 *)(r0 + 0) = 5; \
+ r3 = r0; \
+ r0 = 1; \
+ if r3 != r6 goto 9f; \
+ r0 = 2; \
+ if r1 != 0x1111 goto 9f; \
+ r0 = 3; \
+ if r2 != 0x2222 goto 9f; \
+ r0 = 0x3333; \
+ r1 = r6; \
+ *(u32 *)(r1 + 8) = -7; \
+ r3 = r0; \
+ r0 = 4; \
+ if r3 != 0x3333 goto 9f; \
+ r0 = 5; \
+ if r1 != r6 goto 9f; \
+ r0 = 6; \
+ r3 = *(u64 *)(r6 + 0); \
+ if r3 != 5 goto 9f; \
+ r0 = 7; \
+ r3 = *(u64 *)(r6 + 8); \
+ r4 = 0xfffffff9 ll; \
+ if r3 != r4 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: access through a number with garbage in the upper half")
+__success __retval(0)
+__load_if_JITed()
+__naked void st_value_garbage(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ r1 = 0xdeadbeef00001000 ll; \
+ *(u64 *)(r6 + 2048) = r1; \
+ r7 = *(u64 *)(r6 + 2048); \
+ r8 = *(u64 *)(r6 + 2048); \
+ r1 = 3; \
+ *(u64 *)(r8 + 0) = 1; \
+ r0 = 1; \
+ if r8 != r7 goto 9f; \
+ *(u8 *)(r8 + 1) = 1; \
+ r0 = 2; \
+ if r8 != r7 goto 9f; \
+ *(u32 *)(r8 + 4) = r1; \
+ r0 = 3; \
+ if r8 != r7 goto 9f; \
+ r2 = *(u16 *)(r8 + 2); \
+ r0 = 4; \
+ if r8 != r7 goto 9f; \
+ r0 = 5; \
+ if r1 != 3 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: access through a number without the upper half")
+__success __retval(0)
+__load_if_JITed()
+__naked void st_value_small(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ r1 = 0x1000 ll; \
+ *(u64 *)(r6 + 2048) = r1; \
+ r7 = *(u64 *)(r6 + 2048); \
+ r8 = *(u64 *)(r6 + 2048); \
+ r1 = 3; \
+ *(u64 *)(r8 + 0) = 1; \
+ r0 = 1; \
+ if r8 != r7 goto 9f; \
+ *(u8 *)(r8 + 1) = 1; \
+ r0 = 2; \
+ if r8 != r7 goto 9f; \
+ *(u32 *)(r8 + 4) = r1; \
+ r0 = 3; \
+ if r8 != r7 goto 9f; \
+ r2 = *(u16 *)(r8 + 2); \
+ r0 = 4; \
+ if r8 != r7 goto 9f; \
+ r0 = 5; \
+ if r1 != 3 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: atomics through a number that is not an address in arena")
+__success __retval(0)
+__load_if_JITed()
+__naked void atomic_value(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ r1 = 0xdeadbeef00001000 ll; \
+ *(u64 *)(r6 + 2048) = r1; \
+ r7 = *(u64 *)(r6 + 2048); \
+ r8 = *(u64 *)(r6 + 2048); \
+ r1 = 3; \
+ lock *(u64 *)(r8 + 0) += r1; \
+ r0 = 1; \
+ if r8 != r7 goto 9f; \
+ .8byte %[fetch_add_insn]; \
+ r0 = 2; \
+ if r8 != r7 goto 9f; \
+ .8byte %[xchg_insn]; \
+ r0 = 3; \
+ if r8 != r7 goto 9f; \
+ r0 = 0; \
+ .8byte %[cmpxchg_insn]; \
+ r0 = 4; \
+ if r8 != r7 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages),
+ __imm_insn(fetch_add_insn, BPF_ATOMIC_OP(BPF_DW, BPF_ADD | BPF_FETCH,
+ BPF_REG_8, BPF_REG_1, 0)),
+ __imm_insn(xchg_insn, BPF_ATOMIC_OP(BPF_DW, BPF_XCHG, BPF_REG_8, BPF_REG_1, 0)),
+ __imm_insn(cmpxchg_insn, BPF_ATOMIC_OP(BPF_DW, BPF_CMPXCHG, BPF_REG_8, BPF_REG_1, 0))
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: number and pointer to arena at the same insn")
+__success __retval(0)
+__load_if_JITed()
+__naked void mixed_number_arena(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ *(u64 *)(r6 + 8) = 0x1234; \
+ /* a number that the verifier does not know, 0 at run time */ \
+ r7 = *(u64 *)(r6 + 16); \
+ r8 = r6; \
+ if r7 != 0 goto 1f; \
+ .8byte %[cast_kern_insn]; \
+1: *(u8 *)(r8 + 0) = 1; \
+ *(u8 *)(r8 + 1) = r7; \
+ r2 = *(u8 *)(r8 + 1); \
+ lock *(u64 *)(r8 + 24) += r7; \
+ r0 = 0; \
+ if r7 != 0 goto 9f; \
+ /* pointer to arena only: JIT adds all 64 bits of r8 to the base */ \
+ r0 = 2; \
+ r2 = *(u64 *)(r8 + 8); \
+ if r2 != 0x1234 goto 9f; \
+ r0 = 3; \
+ r2 = *(u8 *)(r6 + 0); \
+ if r2 != 1 goto 9f; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages),
+ __imm_insn(cast_kern_insn, BPF_RAW_INSN(BPF_ALU64 | BPF_MOV | BPF_X,
+ BPF_REG_8, BPF_REG_8, 1, 1))
+ : __clobber_all);
+}
+
+SEC("syscall")
+__description("arena_scalar: no flag, no access through a number")
+__failure __msg("R6 invalid mem access 'scalar'")
+__naked void no_flag(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r6 = 0x100000000000 ll; \
+ r0 = *(u64 *)(r6 + 0); \
+ exit; \
+" :
+ : __imm_addr(arena)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: no arena, no access through a number")
+__failure __msg("R6 invalid mem access 'scalar'")
+__naked void no_arena(void)
+{
+ asm volatile (" \
+ r6 = 0x100000000000 ll; \
+ r0 = *(u64 *)(r6 + 0); \
+ exit; \
+" ::: __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: pointer that is NULL is an address in arena")
+__success __retval(0)
+__load_if_JITed()
+__naked void null_ptr(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r1 = 0; \
+ *(u32 *)(r10 - 4) = r1; \
+ r2 = r10; \
+ r2 += -4; \
+ r1 = %[hash] ll; \
+ call %[bpf_map_lookup_elem]; \
+ r1 = r0; \
+ r0 = 1; \
+ if r1 != 0 goto 9f; \
+ /* nothing is allocated: the load reads 0 */ \
+ r0 = *(u64 *)(r1 + 0); \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm_addr(hash),
+ __imm(bpf_map_lookup_elem)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: pointer that is NULL with an offset is an address in arena")
+__success __retval(0)
+__load_if_JITed()
+__naked void null_ptr_off(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r1 = 0; \
+ *(u32 *)(r10 - 4) = r1; \
+ r2 = r10; \
+ r2 += -4; \
+ r1 = %[hash] ll; \
+ call %[bpf_map_lookup_elem]; \
+ r1 = r0; \
+ r0 = 1; \
+ if r1 != 0 goto 9f; \
+ r1 += 8; \
+ *(u64 *)(r1 + 0) = 5; \
+ r0 = *(u64 *)(r1 + 0); \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm_addr(hash),
+ __imm(bpf_map_lookup_elem)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: number that is less than a page is an address in arena")
+__success __retval(0)
+__load_if_JITed()
+__naked void small_number(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ call %[bpf_get_prandom_u32]; \
+ r1 = r0; \
+ r1 &= 0xfff; \
+ r0 = *(u64 *)(r1 + 0); \
+ exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: number is not a pointer for a helper")
+__failure __msg("R2 type=scalar expected=")
+__naked void helper_arg(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ r1 = %[hash] ll; \
+ r2 = r6; \
+ call %[bpf_map_lookup_elem]; \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm_addr(hash),
+ __imm(bpf_arena_alloc_pages),
+ __imm(bpf_map_lookup_elem)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: number and pointer to stack at the same insn")
+__failure __msg("same insn cannot be used with different pointers")
+__load_if_JITed()
+__naked void mixed_number_stack(void)
+{
+ asm volatile (" \
+ r1 = %[arena] ll; \
+ r2 = 0; \
+ r3 = 1; \
+ r4 = -1; \
+ r5 = 0; \
+ call %[bpf_arena_alloc_pages]; \
+ r6 = r0; \
+ r0 = 100; \
+ if r6 == 0 goto 9f; \
+ *(u64 *)(r10 - 8) = 0; \
+ call %[bpf_get_prandom_u32]; \
+ if w0 != 0 goto 1f; \
+ r6 = r10; \
+ r6 += -8; \
+1: r0 = *(u64 *)(r6 + 0); \
+ r0 = 0; \
+9: exit; \
+" :
+ : __imm_addr(arena),
+ __imm(bpf_arena_alloc_pages),
+ __imm(bpf_get_prandom_u32)
+ : __clobber_all);
+}
+
+static int st_cb(__u64 idx, void *ctx)
+{
+ volatile long *p = *(volatile long **)ctx;
+
+ p[idx] = 7;
+ return 0;
+}
+
+SEC("syscall")
+__arena_scalar
+__description("arena_scalar: store through a number in a callback")
+__success __retval(0)
+__load_if_JITed()
+int st_in_callback(void *unused)
+{
+ volatile long *p = bpf_arena_alloc_pages(&arena, NULL, 1, -1, 0);
+
+ if (!p)
+ return 100;
+ bpf_loop(4, st_cb, &p, 0);
+ return p[0] + p[3] + p[4] - 14;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/test_loader.c b/tools/testing/selftests/bpf/test_loader.c
index 25eeb1c1248b..a89890cd56d8 100644
--- a/tools/testing/selftests/bpf/test_loader.c
+++ b/tools/testing/selftests/bpf/test_loader.c
@@ -580,6 +580,8 @@ static int parse_test_spec(struct test_loader *tester,
update_flags(&spec->prog_flags, BPF_F_XDP_HAS_FRAGS, clear);
} else if (strcmp(val, "BPF_F_TEST_REG_INVARIANTS") == 0) {
update_flags(&spec->prog_flags, BPF_F_TEST_REG_INVARIANTS, clear);
+ } else if (strcmp(val, "BPF_F_ARENA_SCALAR") == 0) {
+ update_flags(&spec->prog_flags, BPF_F_ARENA_SCALAR, clear);
} else /* assume numeric value */ {
err = parse_int(val, &flags, "test prog flags");
if (err)
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 05/14] bpf: Allow names of Rust types and functions in BTF
2026-09-30 19:31 [PATCH bpf-next 00/14] bpf: Support programs compiled by rust-bpf Alexei Starovoitov
` (3 preceding siblings ...)
2026-09-30 19:31 ` [PATCH bpf-next 04/14] selftests/bpf: Add tests for arena access through numbers Alexei Starovoitov
@ 2026-09-30 19:31 ` Alexei Starovoitov
2026-09-30 20:22 ` bot+bpf-ci
2026-10-01 17:42 ` Alan Maguire
2026-09-30 19:31 ` [PATCH bpf-next 06/14] selftests/bpf: Add tests for " Alexei Starovoitov
` (8 subsequent siblings)
13 siblings, 2 replies; 31+ messages in thread
From: Alexei Starovoitov @ 2026-09-30 19:31 UTC (permalink / raw)
To: bpf; +Cc: daniel, andrii, eddyz87, memxor
From: Alexei Starovoitov <ast@kernel.org>
Names of types and functions in BTF that LLVM makes for a Rust program
are not C identifiers:
[69] STRUCT 'NonNull<str>' size=16 vlen=1
[147] FUNC 'write_fmt<scx_cosmos::BpfStream>' type_id=146
and the kernel rejects such BTF with "Invalid name". scx_simple and
scx_cosmos schedulers written in Rust have them in STRUCT, FWD and FUNC,
made of letters, digits and " #&()*,:;<>[]{}", 430 characters at most.
Allow any printable character in btf_name_valid_identifier(), like it's
done for DATASEC. It checks names of types, functions, members,
enumerators, variables and arguments, so all of them can have such
characters now. The limit of KSYM_NAME_LEN stays.
The name of FUNC is a part of the name of the program in kallsyms, where
a space would break the parsers. Replace what is not a character of
an identifier with '_' there.
Tests in prog_tests/btf.c expect "Invalid name" for names with '!' and
'*', which are valid now. Put a character that is not printable there.
The type name '?foo' is expected to load.
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
---
kernel/bpf/btf.c | 15 ++----
kernel/bpf/core.c | 5 ++
tools/testing/selftests/bpf/prog_tests/btf.c | 52 ++++++++++----------
3 files changed, 33 insertions(+), 39 deletions(-)
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 8cc17a1cd25c..d27af5d8e495 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -901,16 +901,6 @@ static bool btf_name_offset_valid(const struct btf *btf, u32 offset)
return offset < btf->hdr.str_len;
}
-static bool __btf_name_char_ok(char c, bool first)
-{
- if ((first ? !isalpha(c) :
- !isalnum(c)) &&
- c != '_' &&
- c != '.')
- return false;
- return true;
-}
-
const char *btf_str_by_offset(const struct btf *btf, u32 offset)
{
while (offset < btf->start_str_off)
@@ -923,20 +913,21 @@ const char *btf_str_by_offset(const struct btf *btf, u32 offset)
return NULL;
}
+/* Names in BTF of Rust are not C identifiers. Allow any printable character */
static bool btf_name_valid_identifier(const struct btf *btf, u32 offset)
{
/* offset must be valid */
const char *src = btf_str_by_offset(btf, offset);
const char *src_limit;
- if (!__btf_name_char_ok(*src, true))
+ if (!isprint(*src))
return false;
/* set a limit on identifier length */
src_limit = src + KSYM_NAME_LEN;
src++;
while (*src && src < src_limit) {
- if (!__btf_name_char_ok(*src, false))
+ if (!isprint(*src))
return false;
src++;
}
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index a1721f9c0f52..36900b02d668 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -18,6 +18,7 @@
*/
#include <uapi/linux/btf.h>
+#include <linux/ctype.h>
#include <linux/filter.h>
#include <linux/sched/signal.h>
#include <linux/skbuff.h>
@@ -589,6 +590,10 @@ bpf_prog_ksym_set_name(struct bpf_prog *prog)
prog->aux->func_info[prog->aux->func_idx].type_id);
func_name = btf_name_by_offset(prog->aux->btf, type->name_off);
snprintf(sym, (size_t)(end - sym), "_%s", func_name);
+ /* the name of a function of Rust is not an identifier */
+ for (; *sym; sym++)
+ if (!isalnum(*sym) && *sym != '_' && *sym != '.')
+ *sym = '_';
return;
}
diff --git a/tools/testing/selftests/bpf/prog_tests/btf.c b/tools/testing/selftests/bpf/prog_tests/btf.c
index df6ad38d287d..87b554067071 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf.c
@@ -1987,14 +1987,14 @@ static struct btf_raw_test raw_tests[] = {
},
{
- .descr = "typedef (invalid name, invalid identifier)",
+ .descr = "typedef (invalid name, not printable)",
.raw_types = {
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
BTF_TYPEDEF_ENC(NAME_TBD, 1), /* [2] */
BTF_END_RAW,
},
- .str_sec = "\0__!int",
- .str_sec_size = sizeof("\0__!int"),
+ .str_sec = "\0__\7int",
+ .str_sec_size = sizeof("\0__\7int"),
.map_type = BPF_MAP_TYPE_ARRAY,
.map_name = "typedef_check_btf",
.key_size = sizeof(int),
@@ -2112,15 +2112,15 @@ static struct btf_raw_test raw_tests[] = {
},
{
- .descr = "fwd type (invalid name, invalid identifier)",
+ .descr = "fwd type (invalid name, not printable)",
.raw_types = {
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
BTF_TYPE_ENC(NAME_TBD,
BTF_INFO_ENC(BTF_KIND_FWD, 0, 0), 0), /* [2] */
BTF_END_RAW,
},
- .str_sec = "\0__!skb",
- .str_sec_size = sizeof("\0__!skb"),
+ .str_sec = "\0__\7skb",
+ .str_sec_size = sizeof("\0__\7skb"),
.map_type = BPF_MAP_TYPE_ARRAY,
.map_name = "fwd_type_check_btf",
.key_size = sizeof(int),
@@ -2175,7 +2175,7 @@ static struct btf_raw_test raw_tests[] = {
},
{
- .descr = "struct type (invalid name, invalid identifier)",
+ .descr = "struct type (invalid name, not printable)",
.raw_types = {
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
BTF_TYPE_ENC(NAME_TBD,
@@ -2183,8 +2183,8 @@ static struct btf_raw_test raw_tests[] = {
BTF_MEMBER_ENC(NAME_TBD, 1, 0),
BTF_END_RAW,
},
- .str_sec = "\0A!\0B",
- .str_sec_size = sizeof("\0A!\0B"),
+ .str_sec = "\0A\7\0B",
+ .str_sec_size = sizeof("\0A\7\0B"),
.map_type = BPF_MAP_TYPE_ARRAY,
.map_name = "struct_type_check_btf",
.key_size = sizeof(int),
@@ -2217,7 +2217,7 @@ static struct btf_raw_test raw_tests[] = {
},
{
- .descr = "struct member (invalid name, invalid identifier)",
+ .descr = "struct member (invalid name, not printable)",
.raw_types = {
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
BTF_TYPE_ENC(NAME_TBD,
@@ -2225,8 +2225,8 @@ static struct btf_raw_test raw_tests[] = {
BTF_MEMBER_ENC(NAME_TBD, 1, 0),
BTF_END_RAW,
},
- .str_sec = "\0A\0B*",
- .str_sec_size = sizeof("\0A\0B*"),
+ .str_sec = "\0A\0B\7",
+ .str_sec_size = sizeof("\0A\0B\7"),
.map_type = BPF_MAP_TYPE_ARRAY,
.map_name = "struct_type_check_btf",
.key_size = sizeof(int),
@@ -2260,7 +2260,7 @@ static struct btf_raw_test raw_tests[] = {
},
{
- .descr = "enum type (invalid name, invalid identifier)",
+ .descr = "enum type (invalid name, not printable)",
.raw_types = {
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
BTF_TYPE_ENC(NAME_TBD,
@@ -2269,8 +2269,8 @@ static struct btf_raw_test raw_tests[] = {
BTF_ENUM_ENC(NAME_TBD, 0),
BTF_END_RAW,
},
- .str_sec = "\0A!\0B",
- .str_sec_size = sizeof("\0A!\0B"),
+ .str_sec = "\0A\7\0B",
+ .str_sec_size = sizeof("\0A\7\0B"),
.map_type = BPF_MAP_TYPE_ARRAY,
.map_name = "enum_type_check_btf",
.key_size = sizeof(int),
@@ -2306,7 +2306,7 @@ static struct btf_raw_test raw_tests[] = {
},
{
- .descr = "enum member (invalid name, invalid identifier)",
+ .descr = "enum member (invalid name, not printable)",
.raw_types = {
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
BTF_TYPE_ENC(0,
@@ -2315,8 +2315,8 @@ static struct btf_raw_test raw_tests[] = {
BTF_ENUM_ENC(NAME_TBD, 0),
BTF_END_RAW,
},
- .str_sec = "\0A!",
- .str_sec_size = sizeof("\0A!"),
+ .str_sec = "\0A\7",
+ .str_sec_size = sizeof("\0A\7"),
.map_type = BPF_MAP_TYPE_ARRAY,
.map_name = "enum_type_check_btf",
.key_size = sizeof(int),
@@ -2625,14 +2625,14 @@ static struct btf_raw_test raw_tests[] = {
.raw_types = {
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
BTF_TYPE_INT_ENC(0, 0, 0, 32, 4), /* [2] */
- /* void (*)(int a, unsigned int !!!) */
+ /* void (*)(int a, unsigned int \7) */
BTF_FUNC_PROTO_ENC(0, 2), /* [3] */
BTF_FUNC_PROTO_ARG_ENC(NAME_TBD, 1),
BTF_FUNC_PROTO_ARG_ENC(NAME_TBD, 2),
BTF_END_RAW,
},
- .str_sec = "\0a\0!!!",
- .str_sec_size = sizeof("\0a\0!!!"),
+ .str_sec = "\0a\0\7",
+ .str_sec_size = sizeof("\0a\0\7"),
.map_type = BPF_MAP_TYPE_ARRAY,
.map_name = "func_proto_type_check_btf",
.key_size = sizeof(int),
@@ -2775,12 +2775,12 @@ static struct btf_raw_test raw_tests[] = {
BTF_FUNC_PROTO_ENC(0, 2), /* [3] */
BTF_FUNC_PROTO_ARG_ENC(NAME_TBD, 1),
BTF_FUNC_PROTO_ARG_ENC(NAME_TBD, 2),
- /* void !!!(int a, unsigned int b) */
+ /* void \7(int a, unsigned int b) */
BTF_FUNC_ENC(NAME_TBD, 3), /* [4] */
BTF_END_RAW,
},
- .str_sec = "\0a\0b\0!!!",
- .str_sec_size = sizeof("\0a\0b\0!!!"),
+ .str_sec = "\0a\0b\0\7",
+ .str_sec_size = sizeof("\0a\0b\0\7"),
.map_type = BPF_MAP_TYPE_ARRAY,
.map_name = "func_type_check_btf",
.key_size = sizeof(int),
@@ -3585,15 +3585,13 @@ static struct btf_raw_test raw_tests[] = {
.btf_load_err = true,
},
{
- .descr = "type name '?foo' is not ok",
+ .descr = "type name '?foo' is ok",
.raw_types = {
/* union ?foo; */
BTF_TYPE_ENC(1, BTF_INFO_ENC(BTF_KIND_FWD, 1, 0), 0), /* [1] */
BTF_END_RAW,
},
BTF_STR_SEC("\0?foo"),
- .err_str = "Invalid name",
- .btf_load_err = true,
},
{
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 06/14] selftests/bpf: Add tests for names of Rust types and functions in BTF
2026-09-30 19:31 [PATCH bpf-next 00/14] bpf: Support programs compiled by rust-bpf Alexei Starovoitov
` (4 preceding siblings ...)
2026-09-30 19:31 ` [PATCH bpf-next 05/14] bpf: Allow names of Rust types and functions in BTF Alexei Starovoitov
@ 2026-09-30 19:31 ` Alexei Starovoitov
2026-09-30 20:22 ` bot+bpf-ci
2026-09-30 19:31 ` [PATCH bpf-next 07/14] bpf: Allow arguments without names in static " Alexei Starovoitov
` (7 subsequent siblings)
13 siblings, 1 reply; 31+ messages in thread
From: Alexei Starovoitov @ 2026-09-30 19:31 UTC (permalink / raw)
To: bpf; +Cc: daniel, andrii, eddyz87, memxor
From: Alexei Starovoitov <ast@kernel.org>
Check that BTF with names of STRUCT, FWD, TYPEDEF and FUNC that Rust
makes is loaded and that the name of FUNC is an identifier in kallsyms.
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
---
tools/testing/selftests/bpf/prog_tests/btf.c | 15 ++++
.../selftests/bpf/prog_tests/btf_rust.c | 82 +++++++++++++++++++
2 files changed, 97 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/btf_rust.c
diff --git a/tools/testing/selftests/bpf/prog_tests/btf.c b/tools/testing/selftests/bpf/prog_tests/btf.c
index 87b554067071..207341f4bd99 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf.c
@@ -3593,6 +3593,21 @@ static struct btf_raw_test raw_tests[] = {
},
BTF_STR_SEC("\0?foo"),
},
+{
+ .descr = "names of Rust types and functions are ok",
+ .raw_types = {
+ BTF_TYPE_INT_ENC(NAME_NTH(1), 0, 0, 32, 4), /* [1] */
+ BTF_STRUCT_ENC(NAME_NTH(2), 1, 4), /* [2] */
+ BTF_MEMBER_ENC(NAME_NTH(3), 1, 0),
+ BTF_FWD_ENC(NAME_NTH(4), 0), /* [3] */
+ BTF_TYPEDEF_ENC(NAME_NTH(5), 2), /* [4] */
+ BTF_FUNC_PROTO_ENC(0, 1), /* [5] */
+ BTF_FUNC_PROTO_ARG_ENC(NAME_NTH(6), 1),
+ BTF_FUNC_ENC(NAME_NTH(7), 5), /* [6] */
+ BTF_END_RAW,
+ },
+ BTF_STR_SEC("\0u32\0Option<&str>\0__0\0*const str\0{impl#9}<[u8; 4]>\0self\0fmt<str>"),
+},
{
.descr = "float test #1, well-formed",
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_rust.c b/tools/testing/selftests/bpf/prog_tests/btf_rust.c
new file mode 100644
index 000000000000..b0128daceaa1
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/btf_rust.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include <bpf/btf.h>
+
+#define FUNC_NAME "write_fmt<scx_simple::BpfStream>"
+#define KSYM_NAME "write_fmt_scx_simple__BpfStream_"
+
+static bool kallsyms_has(const char *sym)
+{
+ char line[1024], name[512];
+ bool found = false;
+ FILE *f;
+
+ f = fopen("/proc/kallsyms", "r");
+ if (!ASSERT_OK_PTR(f, "kallsyms"))
+ return false;
+ while (!found && fgets(line, sizeof(line), f))
+ found = sscanf(line, "%*s %*s %511s", name) == 1 && !strcmp(name, sym);
+ fclose(f);
+ return found;
+}
+
+/* The name of a function of Rust is a part of the name of the program in kallsyms */
+static void test_func_name(void)
+{
+ struct bpf_insn insns[] = {
+ BPF_MOV64_IMM(BPF_REG_0, 0),
+ BPF_EXIT_INSN(),
+ };
+ LIBBPF_OPTS(bpf_prog_load_opts, opts);
+ int int_id, proto_id, prog_fd = -1, i;
+ struct bpf_func_info func_info = {};
+ struct bpf_prog_info info = {};
+ __u32 len = sizeof(info);
+ char sym[128], *p = sym;
+ struct btf *btf;
+
+ btf = btf__new_empty();
+ if (!ASSERT_OK_PTR(btf, "btf"))
+ return;
+ int_id = btf__add_int(btf, "i32", 4, BTF_INT_SIGNED);
+ ASSERT_GT(int_id, 0, "int");
+ proto_id = btf__add_func_proto(btf, int_id);
+ ASSERT_GT(proto_id, 0, "proto");
+ ASSERT_OK(btf__add_func_param(btf, "ctx", int_id), "param");
+ func_info.type_id = btf__add_func(btf, FUNC_NAME, BTF_FUNC_STATIC, proto_id);
+ ASSERT_GT(func_info.type_id, 0, "func");
+ if (!ASSERT_OK(btf__load_into_kernel(btf), "btf load"))
+ goto out;
+
+ opts.prog_btf_fd = btf__fd(btf);
+ opts.func_info = &func_info;
+ opts.func_info_cnt = 1;
+ opts.func_info_rec_size = sizeof(func_info);
+ prog_fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, NULL, "GPL", insns,
+ ARRAY_SIZE(insns), &opts);
+ if (!ASSERT_GE(prog_fd, 0, "prog load"))
+ goto out;
+ if (!ASSERT_OK(bpf_prog_get_info_by_fd(prog_fd, &info, &len), "prog info"))
+ goto out;
+ if (!info.jited_prog_len) {
+ test__skip();
+ goto out;
+ }
+
+ p += sprintf(p, "bpf_prog_");
+ for (i = 0; i < BPF_TAG_SIZE; i++)
+ p += sprintf(p, "%02x", info.tag[i]);
+ sprintf(p, "_%s", KSYM_NAME);
+ ASSERT_TRUE(kallsyms_has(sym), sym);
+out:
+ if (prog_fd >= 0)
+ close(prog_fd);
+ btf__free(btf);
+}
+
+void test_btf_rust(void)
+{
+ if (test__start_subtest("func_name"))
+ test_func_name();
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 07/14] bpf: Allow arguments without names in static functions in BTF
2026-09-30 19:31 [PATCH bpf-next 00/14] bpf: Support programs compiled by rust-bpf Alexei Starovoitov
` (5 preceding siblings ...)
2026-09-30 19:31 ` [PATCH bpf-next 06/14] selftests/bpf: Add tests for " Alexei Starovoitov
@ 2026-09-30 19:31 ` Alexei Starovoitov
2026-10-01 21:14 ` Alan Maguire
2026-09-30 19:31 ` [PATCH bpf-next 08/14] selftests/bpf: Add test for arguments without names in static functions Alexei Starovoitov
` (6 subsequent siblings)
13 siblings, 1 reply; 31+ messages in thread
From: Alexei Starovoitov @ 2026-09-30 19:31 UTC (permalink / raw)
To: bpf; +Cc: daniel, andrii, eddyz87, memxor
From: Alexei Starovoitov <ast@kernel.org>
Some static functions in BTF of a Rust program have arguments without
names:
[71] FUNC_PROTO '(anon)' ret_type_id=0 vlen=1
'(anon)' type_id=72
[81] FUNC 'unwrap_failed' type_id=71 linkage=static
and the kernel rejects such BTF with "Invalid arg#1". It's 5 of 25
static functions in scx_cosmos and 6 of 24 in scx_simple.
The verifier looks at types of the arguments. The names are printed only,
as "(anon)" when there is none. Allow such static FUNC. Global functions
are checked as before.
The test "func (Some arg has no name)" in prog_tests/btf.c has a static
function. Make it global to keep the check.
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
---
kernel/bpf/btf.c | 4 ++++
tools/testing/selftests/bpf/prog_tests/btf.c | 5 +++--
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index d27af5d8e495..c9d4b709380c 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -5752,6 +5752,10 @@ static int btf_func_check(struct btf_verifier_env *env,
return -EINVAL;
}
+ /* Rust leaves out names of some arguments of static functions */
+ if (btf_func_linkage(t) == BTF_FUNC_STATIC)
+ return 0;
+
args = (const struct btf_param *)(proto_type + 1);
nr_args = btf_type_vlen(proto_type);
for (i = 0; i < nr_args; i++) {
diff --git a/tools/testing/selftests/bpf/prog_tests/btf.c b/tools/testing/selftests/bpf/prog_tests/btf.c
index 207341f4bd99..21fdeeb23405 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf.c
@@ -2793,7 +2793,7 @@ static struct btf_raw_test raw_tests[] = {
},
{
- .descr = "func (Some arg has no name)",
+ .descr = "func (Some arg of global func has no name)",
.raw_types = {
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
BTF_TYPE_INT_ENC(0, 0, 0, 32, 4), /* [2] */
@@ -2802,7 +2802,8 @@ static struct btf_raw_test raw_tests[] = {
BTF_FUNC_PROTO_ARG_ENC(NAME_TBD, 1),
BTF_FUNC_PROTO_ARG_ENC(0, 2),
/* void func(int a, unsigned int) */
- BTF_FUNC_ENC(NAME_TBD, 3), /* [4] */
+ BTF_TYPE_ENC(NAME_TBD, /* [4] */
+ BTF_INFO_ENC(BTF_KIND_FUNC, 0, BTF_FUNC_GLOBAL), 3),
BTF_END_RAW,
},
.str_sec = "\0a\0func",
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 08/14] selftests/bpf: Add test for arguments without names in static functions
2026-09-30 19:31 [PATCH bpf-next 00/14] bpf: Support programs compiled by rust-bpf Alexei Starovoitov
` (6 preceding siblings ...)
2026-09-30 19:31 ` [PATCH bpf-next 07/14] bpf: Allow arguments without names in static " Alexei Starovoitov
@ 2026-09-30 19:31 ` Alexei Starovoitov
2026-10-01 21:22 ` Alan Maguire
2026-09-30 19:31 ` [PATCH bpf-next 09/14] bpf: Allow a variable in DATASEC that is smaller than its type Alexei Starovoitov
` (5 subsequent siblings)
13 siblings, 1 reply; 31+ messages in thread
From: Alexei Starovoitov @ 2026-09-30 19:31 UTC (permalink / raw)
To: bpf; +Cc: daniel, andrii, eddyz87, memxor
From: Alexei Starovoitov <ast@kernel.org>
Check that BTF with a static function that has an argument without a name
is loaded.
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
---
tools/testing/selftests/bpf/prog_tests/btf.c | 24 ++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/btf.c b/tools/testing/selftests/bpf/prog_tests/btf.c
index 21fdeeb23405..751cb14c1949 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf.c
@@ -2819,6 +2819,30 @@ static struct btf_raw_test raw_tests[] = {
.err_str = "Invalid arg#2",
},
+{
+ .descr = "func (Some arg of static func has no name)",
+ .raw_types = {
+ BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
+ BTF_TYPE_INT_ENC(0, 0, 0, 32, 4), /* [2] */
+ /* void (*)(int a, unsigned int) */
+ BTF_FUNC_PROTO_ENC(0, 2), /* [3] */
+ BTF_FUNC_PROTO_ARG_ENC(NAME_TBD, 1),
+ BTF_FUNC_PROTO_ARG_ENC(0, 2),
+ /* static void func(int a, unsigned int) */
+ BTF_FUNC_ENC(NAME_TBD, 3), /* [4] */
+ BTF_END_RAW,
+ },
+ .str_sec = "\0a\0func",
+ .str_sec_size = sizeof("\0a\0func"),
+ .map_type = BPF_MAP_TYPE_ARRAY,
+ .map_name = "func_type_check_btf",
+ .key_size = sizeof(int),
+ .value_size = sizeof(int),
+ .key_type_id = 1,
+ .value_type_id = 1,
+ .max_entries = 4,
+},
+
{
.descr = "func (Non zero vlen)",
.raw_types = {
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 09/14] bpf: Allow a variable in DATASEC that is smaller than its type
2026-09-30 19:31 [PATCH bpf-next 00/14] bpf: Support programs compiled by rust-bpf Alexei Starovoitov
` (7 preceding siblings ...)
2026-09-30 19:31 ` [PATCH bpf-next 08/14] selftests/bpf: Add test for arguments without names in static functions Alexei Starovoitov
@ 2026-09-30 19:31 ` Alexei Starovoitov
2026-09-30 19:47 ` sashiko-bot
2026-09-30 19:31 ` [PATCH bpf-next 10/14] selftests/bpf: Add tests for a variable " Alexei Starovoitov
` (4 subsequent siblings)
13 siblings, 1 reply; 31+ messages in thread
From: Alexei Starovoitov @ 2026-09-30 19:31 UTC (permalink / raw)
To: bpf; +Cc: daniel, andrii, eddyz87, memxor
From: Alexei Starovoitov <ast@kernel.org>
LLVM splits a static of a Rust program into pieces. Every piece is a VAR
with the type of the whole static:
[223] STRUCT 'BpfCell<core::option::Option<...>>' size=32 vlen=1
[236] VAR '..scx_cosmos9TASK_CTXS.0' type_id=223, linkage=static
[248] DATASEC '.bss' size=0 vlen=9
type_id=236 offset=24648 size=1 (VAR '..TASK_CTXS.0')
and the kernel rejects such BTF with "Invalid size".
Allow it. The size of a variable in DATASEC is used in two places:
- btf_find_datasec_var() looks for timers, spin locks, kptrs and other
special fields. btf_find_field_one() skips a variable when its size is
not the size of its type, so there are no special fields in a piece.
- btf_datasec_show() prints variables by their types. It would read
past the piece and past the end of the map value when the piece is the
last one. Don't print the pieces.
"global data test #8" and "#10" in prog_tests/btf.c expect "Invalid
size". BTF of both is loaded now. The map of #10 is not created, since
its value is larger than the section.
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
---
kernel/bpf/btf.c | 31 +++++++++++++++-----
tools/testing/selftests/bpf/prog_tests/btf.c | 9 ++----
2 files changed, 26 insertions(+), 14 deletions(-)
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index c9d4b709380c..0630675377aa 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -5397,7 +5397,7 @@ static int btf_datasec_resolve(struct btf_verifier_env *env,
env->resolve_mode = RESOLVE_TBD;
for_each_vsi_from(i, v->next_member, v->t, vsi) {
- u32 var_type_id = vsi->type, type_id, type_size = 0;
+ u32 var_type_id = vsi->type, type_id;
const struct btf_type *var_type = btf_type_by_id(env->btf,
var_type_id);
if (!var_type || !btf_type_is_var(var_type)) {
@@ -5412,16 +5412,16 @@ static int btf_datasec_resolve(struct btf_verifier_env *env,
return env_stack_push(env, var_type, var_type_id);
}
+ /*
+ * The variable can be smaller than its type. It's a piece of
+ * a variable that the compiler split then, with the type of
+ * the whole variable.
+ */
type_id = var_type->type;
- if (!btf_type_id_size(btf, &type_id, &type_size)) {
+ if (!btf_type_id_size(btf, &type_id, NULL)) {
btf_verifier_log_vsi(env, v->t, vsi, "Invalid type");
return -EINVAL;
}
-
- if (vsi->size < type_size) {
- btf_verifier_log_vsi(env, v->t, vsi, "Invalid size");
- return -EINVAL;
- }
}
env_stack_pop_resolved(env, 0, 0);
@@ -5434,6 +5434,16 @@ static void btf_datasec_log(struct btf_verifier_env *env,
btf_verifier_log(env, "size=%u vlen=%u", t->size, btf_type_vlen(t));
}
+/* A piece of a variable is smaller than its type, which is the one of the whole variable */
+static bool btf_var_is_piece(const struct btf *btf, const struct btf_type *var,
+ const struct btf_var_secinfo *vsi)
+{
+ u32 size;
+
+ return IS_ERR(btf_resolve_size(btf, btf_type_by_id(btf, var->type), &size)) ||
+ vsi->size < size;
+}
+
static void btf_datasec_show(const struct btf *btf,
const struct btf_type *t, u32 type_id,
void *data, u8 bits_offset,
@@ -5441,6 +5451,7 @@ static void btf_datasec_show(const struct btf *btf,
{
const struct btf_var_secinfo *vsi;
const struct btf_type *var;
+ bool comma = false;
u32 i;
if (!btf_show_start_type(show, t, type_id, data))
@@ -5450,8 +5461,12 @@ static void btf_datasec_show(const struct btf *btf,
__btf_name_by_offset(btf, t->name_off));
for_each_vsi(i, t, vsi) {
var = btf_type_by_id(btf, vsi->type);
- if (i)
+ /* there is less data than the type takes */
+ if (btf_var_is_piece(btf, var, vsi))
+ continue;
+ if (comma)
btf_show(show, ",");
+ comma = true;
btf_type_ops(var)->show(btf, var, vsi->type,
data + vsi->offset, bits_offset, show);
}
diff --git a/tools/testing/selftests/bpf/prog_tests/btf.c b/tools/testing/selftests/bpf/prog_tests/btf.c
index 751cb14c1949..2c27224a0bfc 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf.c
@@ -424,7 +424,7 @@ static struct btf_raw_test raw_tests[] = {
.err_str = "Invalid type",
},
{
- .descr = "global data test #8, invalid var size",
+ .descr = "global data test #8, var is smaller than its type",
.raw_types = {
/* int */
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
@@ -457,8 +457,6 @@ static struct btf_raw_test raw_tests[] = {
.key_type_id = 0,
.value_type_id = 7,
.max_entries = 1,
- .btf_load_err = true,
- .err_str = "Invalid size",
},
{
.descr = "global data test #9, invalid var size",
@@ -498,7 +496,7 @@ static struct btf_raw_test raw_tests[] = {
.err_str = "Invalid size",
},
{
- .descr = "global data test #10, invalid var size",
+ .descr = "global data test #10, section is smaller than map value",
.raw_types = {
/* int */
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
@@ -531,8 +529,7 @@ static struct btf_raw_test raw_tests[] = {
.key_type_id = 0,
.value_type_id = 7,
.max_entries = 1,
- .btf_load_err = true,
- .err_str = "Invalid size",
+ .map_create_err = true,
},
{
.descr = "global data test #11, multiple section members",
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 10/14] selftests/bpf: Add tests for a variable that is smaller than its type
2026-09-30 19:31 [PATCH bpf-next 00/14] bpf: Support programs compiled by rust-bpf Alexei Starovoitov
` (8 preceding siblings ...)
2026-09-30 19:31 ` [PATCH bpf-next 09/14] bpf: Allow a variable in DATASEC that is smaller than its type Alexei Starovoitov
@ 2026-09-30 19:31 ` Alexei Starovoitov
2026-09-30 19:31 ` [PATCH bpf-next 11/14] libbpf: Keep global data in arena when the object has .arena.data Alexei Starovoitov
` (3 subsequent siblings)
13 siblings, 0 replies; 31+ messages in thread
From: Alexei Starovoitov @ 2026-09-30 19:31 UTC (permalink / raw)
To: bpf; +Cc: daniel, andrii, eddyz87, memxor
From: Alexei Starovoitov <ast@kernel.org>
Check that a variable in DATASEC that is smaller than its type is not
printed when the map is read from bpffs.
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
---
.../selftests/bpf/prog_tests/btf_rust.c | 65 +++++++++++++++++++
1 file changed, 65 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/btf_rust.c b/tools/testing/selftests/bpf/prog_tests/btf_rust.c
index b0128daceaa1..9eed78d98265 100644
--- a/tools/testing/selftests/bpf/prog_tests/btf_rust.c
+++ b/tools/testing/selftests/bpf/prog_tests/btf_rust.c
@@ -75,8 +75,73 @@ static void test_func_name(void)
btf__free(btf);
}
+#define PIN_PATH "/sys/fs/bpf/btf_rust_piece"
+
+/*
+ * A piece of a static that LLVM split has the type of the whole static.
+ * It's the last variable in the section, so its type ends past the map value.
+ */
+static void test_piece(void)
+{
+ LIBBPF_OPTS(bpf_map_create_opts, opts);
+ int int_id, struct_id, var_id, piece_id, sec_id, map_fd = -1, key = 0;
+ __u32 value[2] = { 0x11111111, 0x22222222 };
+ char line[256] = {};
+ struct btf *btf;
+ FILE *f = NULL;
+
+ btf = btf__new_empty();
+ if (!ASSERT_OK_PTR(btf, "btf"))
+ return;
+ int_id = btf__add_int(btf, "u32", 4, 0);
+ ASSERT_GT(int_id, 0, "int");
+ struct_id = btf__add_struct(btf, "Whole", 8);
+ ASSERT_GT(struct_id, 0, "struct");
+ ASSERT_OK(btf__add_field(btf, "a", int_id, 0, 0), "field");
+ ASSERT_OK(btf__add_field(btf, "b", int_id, 32, 0), "field");
+ var_id = btf__add_var(btf, "CNT", BTF_VAR_STATIC, int_id);
+ ASSERT_GT(var_id, 0, "var");
+ piece_id = btf__add_var(btf, "WHOLE.1", BTF_VAR_STATIC, struct_id);
+ ASSERT_GT(piece_id, 0, "piece");
+ sec_id = btf__add_datasec(btf, ".bss", sizeof(value));
+ ASSERT_GT(sec_id, 0, "datasec");
+ ASSERT_OK(btf__add_datasec_var_info(btf, var_id, 0, 4), "var info");
+ ASSERT_OK(btf__add_datasec_var_info(btf, piece_id, 4, 4), "piece info");
+ if (!ASSERT_OK(btf__load_into_kernel(btf), "btf load"))
+ goto out;
+
+ opts.btf_fd = btf__fd(btf);
+ opts.btf_value_type_id = sec_id;
+ map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, ".bss", sizeof(key), sizeof(value), 1, &opts);
+ if (!ASSERT_GE(map_fd, 0, "map create"))
+ goto out;
+ if (!ASSERT_OK(bpf_map_update_elem(map_fd, &key, value, 0), "map update"))
+ goto out;
+
+ /* the variable is printed, the piece is not */
+ unlink(PIN_PATH);
+ if (!ASSERT_OK(bpf_obj_pin(map_fd, PIN_PATH), "pin"))
+ goto out;
+ f = fopen(PIN_PATH, "r");
+ if (!ASSERT_OK_PTR(f, "open"))
+ goto out;
+ while (fgets(line, sizeof(line), f) && line[0] == '#')
+ ;
+ ASSERT_HAS_SUBSTR(line, "286331153", "var");
+ ASSERT_NULL(strstr(line, "572662306"), "piece");
+out:
+ if (f)
+ fclose(f);
+ unlink(PIN_PATH);
+ if (map_fd >= 0)
+ close(map_fd);
+ btf__free(btf);
+}
+
void test_btf_rust(void)
{
if (test__start_subtest("func_name"))
test_func_name();
+ if (test__start_subtest("piece"))
+ test_piece();
}
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 11/14] libbpf: Keep global data in arena when the object has .arena.data
2026-09-30 19:31 [PATCH bpf-next 00/14] bpf: Support programs compiled by rust-bpf Alexei Starovoitov
` (9 preceding siblings ...)
2026-09-30 19:31 ` [PATCH bpf-next 10/14] selftests/bpf: Add tests for a variable " Alexei Starovoitov
@ 2026-09-30 19:31 ` Alexei Starovoitov
2026-09-30 19:46 ` sashiko-bot
2026-09-30 20:22 ` bot+bpf-ci
2026-09-30 19:31 ` [PATCH bpf-next 12/14] libbpf: Keep format strings of bpf_printk() in .rodata.str Alexei Starovoitov
` (2 subsequent siblings)
13 siblings, 2 replies; 31+ messages in thread
From: Alexei Starovoitov @ 2026-09-30 19:31 UTC (permalink / raw)
To: bpf; +Cc: daniel, andrii, eddyz87, memxor
From: Alexei Starovoitov <ast@kernel.org>
All memory of a Rust program is arena, global data included. There are
no address spaces in Rust to say so. LLVM also drops bounds checks of
indexes into constant tables of core that it proved, so the verifier
can't check the access as access to map value.
The object asks for it with a section named .arena.data. What is in
the section doesn't matter:
#[used]
#[link_section = ".arena.data"]
static DATA_IN_ARENA: u8 = 0;
or in C:
char data_in_arena SEC(".arena.data");
There is nothing to tell to libbpf, so bpftool, veristat and other
loaders work with such objects as they are. When the object has the
section:
- .data, .bss and .rodata sections are appended to arena data after
__arena variables, at the alignment of the section. Like __arena
variables the data is at the end of arena.
- relocations of insns against the sections become relocations against
the arena map.
- array maps of the sections are not created.
- when the object has no arena map libbpf creates one that is as large
as the data. bpf_object__find_map_by_name(obj, "arena") finds it and
bpf_map__set_max_entries() makes room for allocations.
Read-only sections that stay frozen array maps:
- .data.rel.ro and sections that have relocations in them. The kernel
recognizes pointers to functions in them by value for callx.
- .rodata.str*. They hold const strings for __str arguments of kfuncs.
A copy of them is in arena too.
The program dereferences pointers that it loads from data, so pointers
to data that are stored in data become addresses of arena, wherever the
pointer is. The arena map is created ahead of the other maps then, since
its address goes into their data. A pointer to .rodata.str* points to
the copy. A pointer to a section that is not in arena fails the load:
sec '.data': pointer to 'tbl' at offset 8 can't be resolved:
sec '.data.rel.ro' is not in arena
Programs of the object are loaded with BPF_F_ARENA_SCALAR, since they
access the data through plain numbers.
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
---
tools/lib/bpf/libbpf.c | 415 +++++++++++++++++++++++++++++++++++++++--
1 file changed, 395 insertions(+), 20 deletions(-)
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index fdd69aac39bd..335cdafeb06c 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -552,6 +552,25 @@ struct bpf_struct_ops {
#define STRUCT_OPS_SEC ".struct_ops"
#define STRUCT_OPS_LINK_SEC ".struct_ops.link"
#define ARENA_SEC ".addr_space.1"
+/*
+ * An object with this section keeps its global data in arena instead of
+ * array maps. What is in the section doesn't matter. .data, .bss and .rodata
+ * sections become a part of the arena map of the object, like __arena global
+ * variables are. If the object doesn't declare an arena map libbpf creates
+ * one that is just large enough for the data. bpf_object__find_map_by_name()
+ * finds it by the name "arena" and bpf_map__set_max_entries() changes its
+ * size before the object is loaded.
+ * Read-only sections with pointers to functions and sections with constant
+ * strings stay frozen array maps. A constant that a helper or a kfunc takes
+ * by pointer has to be in such section.
+ * Pointers to data that are stored in data become addresses of arena.
+ * The load fails if such pointer points to a section that is not in arena.
+ *
+ * It's for programs written in Rust. There are no address spaces in Rust,
+ * the program accesses arena through plain numbers, so programs of the object
+ * are loaded with BPF_F_ARENA_SCALAR.
+ */
+#define ARENA_DATA_SEC ".arena.data"
enum libbpf_map_type {
LIBBPF_MAP_UNSPEC,
@@ -607,6 +626,13 @@ struct bpf_map {
/* pointers to functions in the data of an internal map, see obj->func_ptrs */
struct func_ptr *func_ptrs;
size_t func_ptr_cnt;
+ /*
+ * Data of the internal map is a part of arena data at arena_off.
+ * The map is not created, unless it's a copy that is in arena.
+ */
+ bool in_arena;
+ bool arena_copy;
+ size_t arena_off;
};
enum extern_type {
@@ -775,6 +801,17 @@ struct bpf_object {
void *arena_data;
size_t arena_data_sz;
size_t arena_data_off;
+ bool data_in_arena;
+ bool arena_mapped;
+ /* pointers to data in the data of internal maps, when data is in arena */
+ struct data_ptr {
+ int sec_idx;
+ size_t sec_off;
+ int targ_sec_idx;
+ size_t targ_off;
+ char *sym_name;
+ } *data_ptrs;
+ size_t data_ptr_cnt;
void *jumptables_data;
size_t jumptables_data_sz;
@@ -1600,6 +1637,7 @@ static struct bpf_object *bpf_object__new(const char *path,
obj->efile.obj_buf = obj_buf;
obj->efile.obj_buf_sz = obj_buf_sz;
obj->efile.btf_maps_shndx = -1;
+ obj->efile.arena_data_shndx = -1;
obj->kconfig_map_idx = -1;
obj->arena_map_idx = -1;
@@ -3081,6 +3119,129 @@ static int init_arena_map_data(struct bpf_object *obj, struct bpf_map *map,
return 0;
}
+static bool map_is_const_str(const struct bpf_map *map)
+{
+ return map->libbpf_type == LIBBPF_MAP_RODATA &&
+ str_has_pfx(map->real_name, RODATA_SEC ".str");
+}
+
+static bool map_data_goes_to_arena(const struct bpf_object *obj, const struct bpf_map *map)
+{
+ int i;
+
+ switch (map->libbpf_type) {
+ case LIBBPF_MAP_DATA:
+ case LIBBPF_MAP_BSS:
+ return true;
+ case LIBBPF_MAP_RODATA:
+ break;
+ default:
+ return false;
+ }
+
+ /*
+ * Const strings that kfuncs and helpers take have to be in a frozen
+ * map. Pointers to them that are stored in data point to a copy of
+ * the strings that is in arena.
+ */
+ if (map_is_const_str(map))
+ return true;
+ /*
+ * Read-only data with pointers stays a frozen map: the kernel
+ * recognizes pointers to functions in it.
+ */
+ if (str_has_pfx(map->real_name, DATA_REL_RO_SEC))
+ return false;
+ for (i = 0; i < obj->efile.sec_cnt; i++) {
+ const struct elf_sec_desc *sec = &obj->efile.secs[i];
+
+ if (sec->sec_type == SEC_RELO && sec->shdr->sh_info == map->sec_idx)
+ return false;
+ }
+ return true;
+}
+
+/* Returns the offset of the data of the map in arena data that is sz bytes so far */
+static size_t map_arena_off(const struct bpf_object *obj, const struct bpf_map *map, size_t sz)
+{
+ size_t align = obj->efile.secs[map->sec_idx].shdr->sh_addralign;
+
+ return roundup(sz, max(align, sizeof(__u64)));
+}
+
+/*
+ * Make .data, .bss and .rodata a part of arena data. They follow __arena
+ * variables of the object, if there are any.
+ */
+static int bpf_object__init_arena_data(struct bpf_object *obj)
+{
+ const size_t page_sz = sysconf(_SC_PAGE_SIZE);
+ size_t sz = obj->arena_data_sz;
+ struct bpf_map *map;
+ void *data;
+ int i;
+
+ if (!obj->data_in_arena)
+ return 0;
+
+ for (i = 0; i < obj->nr_maps; i++) {
+ map = &obj->maps[i];
+ if (map_data_goes_to_arena(obj, map))
+ sz = map_arena_off(obj, map, sz) + map->def.value_size;
+ }
+ if (sz == obj->arena_data_sz)
+ return 0;
+
+ if (obj->arena_map_idx < 0) {
+ map = bpf_object__add_map(obj);
+ if (IS_ERR(map))
+ return PTR_ERR(map);
+
+ map->real_name = strdup("arena");
+ map->name = strdup("arena");
+ if (!map->real_name || !map->name) {
+ zfree(&map->real_name);
+ zfree(&map->name);
+ return -ENOMEM;
+ }
+ map->sec_idx = -1;
+ map->def.type = BPF_MAP_TYPE_ARENA;
+ map->def.max_entries = roundup(sz, page_sz) / page_sz;
+ map->def.map_flags = BPF_F_MMAPABLE;
+ obj->arena_map_idx = map - obj->maps;
+ }
+
+ data = realloc(obj->arena_data, sz);
+ if (!data)
+ return -ENOMEM;
+ memset(data + obj->arena_data_sz, 0, sz - obj->arena_data_sz);
+ sz = obj->arena_data_sz;
+ obj->arena_data = data;
+ obj->maps[obj->arena_map_idx].mmaped = data;
+
+ for (i = 0; i < obj->nr_maps; i++) {
+ map = &obj->maps[i];
+ if (!map_data_goes_to_arena(obj, map))
+ continue;
+ sz = map_arena_off(obj, map, sz);
+ map->arena_off = sz;
+ memcpy(data + sz, map->mmaped, map->def.value_size);
+ sz += map->def.value_size;
+ pr_debug("map '%s': data is in arena at offset %zu\n", map->name, map->arena_off);
+ if (map_is_const_str(map)) {
+ map->arena_copy = true;
+ continue;
+ }
+ munmap(map->mmaped, bpf_map_mmap_sz(map));
+ /* make bpf_map__initial_value() and bpf_map__set_initial_value() work */
+ map->mmaped = data + map->arena_off;
+ map->autocreate = false;
+ map->in_arena = true;
+ }
+ obj->arena_data_sz = sz;
+ return 0;
+}
+
static int bpf_object__init_user_btf_maps(struct bpf_object *obj, bool strict,
const char *pin_root_path)
{
@@ -3173,6 +3334,7 @@ static int bpf_object__init_maps(struct bpf_object *obj,
err = err ?: bpf_object__init_global_data_maps(obj);
err = err ?: bpf_object__init_kconfig_map(obj);
err = err ?: bpf_object_init_struct_ops(obj);
+ err = err ?: bpf_object__init_arena_data(obj);
return err;
}
@@ -3987,6 +4149,11 @@ static int bpf_object__elf_collect(struct bpf_object *obj)
if (!sh)
return -LIBBPF_ERRNO__FORMAT;
+ /* the second pass has to know it when it sees relocations of data */
+ name = elf_sec_str(obj, sh->sh_name);
+ if (name && strcmp(name, ARENA_DATA_SEC) == 0)
+ obj->data_in_arena = true;
+
if (sh->sh_type == SHT_SYMTAB) {
if (obj->efile.symbols) {
pr_warn("elf: multiple symbol tables in %s\n", obj->path);
@@ -4103,6 +4270,8 @@ static int bpf_object__elf_collect(struct bpf_object *obj)
} else if (strcmp(name, ARENA_SEC) == 0) {
obj->efile.arena_data = data;
obj->efile.arena_data_shndx = idx;
+ } else if (strcmp(name, ARENA_DATA_SEC) == 0) {
+ /* the marker, see the first pass */
} else if (strcmp(name, JUMPTABLES_SEC) == 0) {
obj->jumptables_data = malloc(data->d_size);
if (!obj->jumptables_data)
@@ -4127,6 +4296,9 @@ static int bpf_object__elf_collect(struct bpf_object *obj)
* have pointers to functions.
*/
if (!section_have_execinstr(obj, targ_sec_idx) &&
+ !(obj->data_in_arena &&
+ (!strcmp(name, ".rel" DATA_SEC) ||
+ str_has_pfx(name, ".rel" DATA_SEC "."))) &&
strcmp(name, ".rel" RODATA_SEC) &&
!str_has_pfx(name, ".rel" RODATA_SEC ".") &&
strcmp(name, ".rel" DATA_REL_RO_SEC) &&
@@ -4878,6 +5050,10 @@ static int bpf_program__record_reloc(struct bpf_program *prog,
reloc_desc->insn_idx = insn_idx;
reloc_desc->map_idx = map_idx;
reloc_desc->sym_off = sym->st_value;
+ if (map->in_arena) {
+ reloc_desc->map_idx = obj->arena_map_idx;
+ reloc_desc->sym_off += map->arena_off;
+ }
return 0;
}
@@ -5096,6 +5272,9 @@ int bpf_map__set_autocreate(struct bpf_map *map, bool autocreate)
if (map_is_created(map))
return libbpf_err(-EBUSY);
+ if (map->in_arena)
+ return libbpf_err(-EOPNOTSUPP);
+
map->autocreate = autocreate;
return 0;
}
@@ -5376,6 +5555,45 @@ bpf_object__reuse_map(struct bpf_map *map)
return 0;
}
+static struct bpf_map *bpf_object__sec_map(struct bpf_object *obj, int sec_idx)
+{
+ int i;
+
+ for (i = 0; i < obj->nr_maps; i++)
+ if (bpf_map__is_internal(&obj->maps[i]) && obj->maps[i].sec_idx == sec_idx)
+ return &obj->maps[i];
+ return NULL;
+}
+
+static int bpf_object__mmap_arena(struct bpf_object *obj, struct bpf_map *map)
+{
+ int i, err;
+
+ map->mmaped = mmap((void *)(long)map->map_extra,
+ bpf_map_mmap_sz(map), PROT_READ | PROT_WRITE,
+ map->map_extra ? MAP_SHARED | MAP_FIXED : MAP_SHARED,
+ map->fd, 0);
+ if (map->mmaped == MAP_FAILED) {
+ err = -errno;
+ map->mmaped = NULL;
+ pr_warn("map '%s': failed to mmap arena: %s\n",
+ map->name, errstr(err));
+ return err;
+ }
+ if (obj->arena_data) {
+ memcpy(map->mmaped + obj->arena_data_off, obj->arena_data,
+ obj->arena_data_sz);
+ zfree(&obj->arena_data);
+ }
+ for (i = 0; i < obj->nr_maps; i++) {
+ struct bpf_map *m = &obj->maps[i];
+
+ if (m->in_arena)
+ m->mmaped = map->mmaped + obj->arena_data_off + m->arena_off;
+ }
+ return 0;
+}
+
static int
bpf_object__populate_internal_map(struct bpf_object *obj, struct bpf_map *map)
{
@@ -5736,6 +5954,10 @@ bpf_object__create_maps(struct bpf_object *obj)
continue;
}
+ /* see bpf_object__relocate_data_ptrs() */
+ if (map->def.type == BPF_MAP_TYPE_ARENA && obj->arena_mapped)
+ continue;
+
err = map_set_def_max_entries(map);
if (err)
goto err_out;
@@ -5773,22 +5995,9 @@ bpf_object__create_maps(struct bpf_object *obj)
if (err < 0)
goto err_out;
} else if (map->def.type == BPF_MAP_TYPE_ARENA) {
- map->mmaped = mmap((void *)(long)map->map_extra,
- bpf_map_mmap_sz(map), PROT_READ | PROT_WRITE,
- map->map_extra ? MAP_SHARED | MAP_FIXED : MAP_SHARED,
- map->fd, 0);
- if (map->mmaped == MAP_FAILED) {
- err = -errno;
- map->mmaped = NULL;
- pr_warn("map '%s': failed to mmap arena: %s\n",
- map->name, errstr(err));
+ err = bpf_object__mmap_arena(obj, map);
+ if (err)
return err;
- }
- if (obj->arena_data) {
- memcpy(map->mmaped + obj->arena_data_off, obj->arena_data,
- obj->arena_data_sz);
- zfree(&obj->arena_data);
- }
}
if (map->init_slots_sz && map->def.type != BPF_MAP_TYPE_PROG_ARRAY) {
err = init_map_in_map_slots(obj, map);
@@ -7803,6 +8012,72 @@ static int bpf_program_fixup_func_info(struct bpf_object *obj, struct bpf_progra
return err;
}
+/*
+ * Turn pointers to data that are stored in data into addresses of arena.
+ * The arena is created here, ahead of the other maps: the address has to be
+ * known before the maps that hold the pointers are created.
+ */
+static int bpf_object__relocate_data_ptrs(struct bpf_object *obj)
+{
+ struct bpf_map *arena, *map, *targ;
+ __u64 addr, val;
+ size_t i;
+ int err;
+
+ if (!obj->data_ptr_cnt)
+ return 0;
+
+ for (i = 0; i < obj->data_ptr_cnt; i++) {
+ struct data_ptr *p = &obj->data_ptrs[i];
+
+ if (p->targ_sec_idx < 0)
+ continue;
+ map = bpf_object__sec_map(obj, p->sec_idx);
+ targ = bpf_object__sec_map(obj, p->targ_sec_idx);
+ if (map && (map->in_arena || map->autocreate) &&
+ (!targ || (!targ->in_arena && !targ->arena_copy))) {
+ pr_warn("sec '%s': pointer to '%s' at offset %zu can't be resolved: sec '%s' is not in arena\n",
+ map->real_name, p->sym_name, p->sec_off,
+ targ ? targ->real_name : "<?>");
+ return -LIBBPF_ERRNO__RELOC;
+ }
+ }
+
+ if (obj->gen_loader) {
+ pr_warn("pointers to data in data are not supported by light skeleton\n");
+ return -ENOTSUP;
+ }
+
+ if (obj->arena_map_idx < 0)
+ return 0;
+
+ arena = &obj->maps[obj->arena_map_idx];
+ err = bpf_object__create_map(obj, arena, false);
+ err = err ?: bpf_object__mmap_arena(obj, arena);
+ if (err) {
+ pr_warn("map '%s': failed to create: %s\n", arena->name, errstr(err));
+ return err;
+ }
+ obj->arena_mapped = true;
+
+ for (i = 0; i < obj->data_ptr_cnt; i++) {
+ struct data_ptr *p = &obj->data_ptrs[i];
+
+ map = bpf_object__sec_map(obj, p->sec_idx);
+ if (!map || (!map->in_arena && !map->autocreate))
+ continue;
+
+ addr = (__u64)(unsigned long)arena->mmaped + obj->arena_data_off + p->targ_off;
+ if (p->targ_sec_idx >= 0)
+ addr += bpf_object__sec_map(obj, p->targ_sec_idx)->arena_off;
+
+ memcpy(&val, map->mmaped + p->sec_off, sizeof(val));
+ val += addr;
+ memcpy(map->mmaped + p->sec_off, &val, sizeof(val));
+ }
+ return 0;
+}
+
static int bpf_object__relocate(struct bpf_object *obj, const char *targ_btf_path)
{
struct bpf_program *prog;
@@ -7825,8 +8100,10 @@ static int bpf_object__relocate(struct bpf_object *obj, const char *targ_btf_pat
size_t mmap_sz = bpf_map_mmap_sz(arena_map);
if (data_sz > mmap_sz) {
- pr_warn("map '%s': declared ARENA map size (%zu) is too small to hold global __arena variables of size %zu\n",
- arena_map->name, mmap_sz, obj->arena_data_sz);
+ pr_warn("map '%s': declared ARENA map size (%zu) is too small to hold global %s of size %zu\n",
+ arena_map->name, mmap_sz,
+ obj->data_in_arena ? "data" : "__arena variables",
+ obj->arena_data_sz);
return -E2BIG;
}
@@ -7835,6 +8112,10 @@ static int bpf_object__relocate(struct bpf_object *obj, const char *targ_btf_pat
obj->arena_data_off = mmap_sz - data_sz;
}
+ err = bpf_object__relocate_data_ptrs(obj);
+ if (err)
+ return err;
+
/* Before relocating calls pre-process relocations and mark
* few ld_imm64 instructions that points to subprogs.
* Otherwise bpf_object__reloc_code() later would have to consider
@@ -8067,12 +8348,84 @@ static int bpf_object__collect_map_relos(struct bpf_object *obj,
return 0;
}
+/*
+ * A pointer to data that is stored in data. When data is in arena
+ * the program dereferences what it loads from data, so the pointer has
+ * to be the address of the target in arena.
+ */
+static int bpf_object__collect_data_ptr(struct bpf_object *obj, const char *relo_sec_name,
+ int relo_idx, size_t sec_idx, const Elf64_Rel *rel,
+ const Elf64_Sym *sym)
+{
+ Elf_Data *scn_data = obj->efile.secs[sec_idx].data;
+ const char *sym_name = elf_sym_str(obj, sym->st_name) ?: "<?>";
+ struct data_ptr *ptrs;
+
+ if (ELF64_ST_TYPE(sym->st_info) == STT_SECTION && sym->st_shndx < obj->efile.sec_cnt)
+ sym_name = elf_sec_name(obj, elf_sec_by_idx(obj, sym->st_shndx)) ?: "<?>";
+
+ if (ELF64_R_TYPE(rel->r_info) != R_BPF_64_ABS64 ||
+ sym->st_shndx >= obj->efile.sec_cnt ||
+ (sym->st_shndx != obj->efile.arena_data_shndx &&
+ !bpf_object__shndx_is_data(obj, sym->st_shndx)) ||
+ rel->r_offset + sizeof(__u64) > scn_data->d_size) {
+ pr_warn("sec '%s': relo #%d: can't resolve pointer to '%s' at offset %zu when data is in arena\n",
+ relo_sec_name, relo_idx, sym_name, (size_t)rel->r_offset);
+ return -LIBBPF_ERRNO__RELOC;
+ }
+
+ ptrs = libbpf_reallocarray(obj->data_ptrs, obj->data_ptr_cnt + 1, sizeof(*ptrs));
+ if (!ptrs)
+ return -ENOMEM;
+ obj->data_ptrs = ptrs;
+
+ ptrs[obj->data_ptr_cnt].sec_idx = sec_idx;
+ ptrs[obj->data_ptr_cnt].sec_off = rel->r_offset;
+ /* __arena variables are at the start of arena data */
+ ptrs[obj->data_ptr_cnt].targ_sec_idx =
+ sym->st_shndx == obj->efile.arena_data_shndx ? -1 : sym->st_shndx;
+ ptrs[obj->data_ptr_cnt].targ_off = sym->st_value;
+ ptrs[obj->data_ptr_cnt].sym_name = strdup(sym_name);
+ if (!ptrs[obj->data_ptr_cnt].sym_name)
+ return -ENOMEM;
+ obj->data_ptr_cnt++;
+
+ pr_debug("sec '%s': relo #%d: pointer at offset %zu to '%s'\n",
+ relo_sec_name, relo_idx, (size_t)rel->r_offset, sym_name);
+ return 0;
+}
+
+/* Collect pointers in a data section that went to arena */
+static int bpf_object__collect_data_relos(struct bpf_object *obj,
+ Elf64_Shdr *shdr, Elf_Data *data)
+{
+ int i, err, nrels = shdr->sh_size / shdr->sh_entsize;
+ const char *relo_sec_name;
+ Elf64_Sym *sym;
+ Elf64_Rel *rel;
+
+ relo_sec_name = elf_sec_str(obj, shdr->sh_name) ?: "<?>";
+ for (i = 0; i < nrels; i++) {
+ rel = elf_rel_by_idx(data, i);
+ sym = rel ? elf_sym_by_idx(obj, ELF64_R_SYM(rel->r_info)) : NULL;
+ if (!sym) {
+ pr_warn("sec '%s': failed to get relo #%d\n", relo_sec_name, i);
+ return -LIBBPF_ERRNO__FORMAT;
+ }
+ err = bpf_object__collect_data_ptr(obj, relo_sec_name, i, shdr->sh_info, rel, sym);
+ if (err)
+ return err;
+ }
+ return 0;
+}
+
/*
* Collect pointers to functions in a read-only data section. They are
* R_BPF_64_ABS64 relocations against .text section, where the offset of
* a static function in the section is stored in place. Relocations in data
* sections were ignored before pointers to functions were supported. Those
- * that are something else, e.g. pointers to data, still are.
+ * that are something else, e.g. pointers to data, still are, unless data
+ * is in arena.
*/
static int bpf_object__collect_rodata_relos(struct bpf_object *obj,
Elf64_Shdr *shdr, Elf_Data *data)
@@ -8108,6 +8461,15 @@ static int bpf_object__collect_rodata_relos(struct bpf_object *obj,
if (ELF64_R_TYPE(rel->r_info) != R_BPF_64_ABS64 ||
!sym_is_subprog(sym, obj->efile.text_shndx)) {
+ int err;
+
+ if (obj->data_in_arena) {
+ err = bpf_object__collect_data_ptr(obj, relo_sec_name, i,
+ sec_idx, rel, sym);
+ if (err)
+ return err;
+ continue;
+ }
pr_debug("sec '%s': relo #%d: not a pointer to a function, skipping...\n",
relo_sec_name, i);
continue;
@@ -8183,6 +8545,8 @@ static int bpf_object__collect_relos(struct bpf_object *obj)
if (obj->efile.secs[idx].sec_type == SEC_RODATA)
err = bpf_object__collect_rodata_relos(obj, shdr, data);
+ else if (obj->efile.secs[idx].sec_type == SEC_DATA)
+ err = bpf_object__collect_data_relos(obj, shdr, data);
else if (obj->efile.secs[idx].sec_type == SEC_ST_OPS)
err = bpf_object__collect_st_ops_relos(obj, shdr, data);
else if (idx == obj->efile.btf_maps_shndx)
@@ -8476,6 +8840,9 @@ static int bpf_object_load_prog(struct bpf_object *obj, struct bpf_program *prog
}
load_attr.log_level = log_level;
load_attr.prog_flags = prog->prog_flags;
+ /* the program accesses its data in arena through plain numbers */
+ if (obj->data_in_arena)
+ load_attr.prog_flags |= BPF_F_ARENA_SCALAR;
load_attr.fd_array = obj->fd_array;
load_attr.token_fd = obj->token_fd;
@@ -8547,7 +8914,7 @@ static int bpf_object_load_prog(struct bpf_object *obj, struct bpf_program *prog
for (i = 0; i < obj->nr_maps; i++) {
map = &prog->obj->maps[i];
- if (map->libbpf_type != LIBBPF_MAP_RODATA)
+ if (map->libbpf_type != LIBBPF_MAP_RODATA || map->in_arena)
continue;
if (bpf_prog_bind_map(ret, map->fd, NULL)) {
@@ -10111,7 +10478,7 @@ static void bpf_map__destroy(struct bpf_map *map)
zfree(&map->init_slots);
map->init_slots_sz = 0;
- if (map->mmaped && map->mmaped != map->obj->arena_data)
+ if (map->mmaped && map->mmaped != map->obj->arena_data && !map->in_arena)
munmap(map->mmaped, bpf_map_mmap_sz(map));
map->mmaped = NULL;
@@ -10196,6 +10563,9 @@ void bpf_object__close(struct bpf_object *obj)
close(obj->func_ptr_maps[i].fd);
zfree(&obj->func_ptr_maps);
zfree(&obj->func_ptrs);
+ for (i = 0; i < obj->data_ptr_cnt; i++)
+ zfree(&obj->data_ptrs[i].sym_name);
+ zfree(&obj->data_ptrs);
if (obj->btf_module_allowlist) {
for (i = 0; i < obj->btf_module_allowlist_cnt; i++)
@@ -10556,6 +10926,8 @@ int bpf_program__clone(struct bpf_program *prog, const struct bpf_prog_load_opts
attr.token_fd = OPTS_GET(opts, token_fd, 0) ?: obj->token_fd;
if (attr.token_fd)
attr.prog_flags |= BPF_F_TOKEN_FD;
+ if (obj->data_in_arena)
+ attr.prog_flags |= BPF_F_ARENA_SCALAR;
prog_btf_fd = OPTS_GET(opts, prog_btf_fd, 0);
if (!prog_btf_fd && obj->btf)
@@ -11562,6 +11934,9 @@ int bpf_map__set_value_size(struct bpf_map *map, __u32 size)
if (map_is_created(map))
return libbpf_err(-EBUSY);
+ if (map->in_arena)
+ return libbpf_err(-EOPNOTSUPP);
+
if (map->mmaped) {
size_t mmap_old_sz, mmap_new_sz;
int err;
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 12/14] libbpf: Keep format strings of bpf_printk() in .rodata.str
2026-09-30 19:31 [PATCH bpf-next 00/14] bpf: Support programs compiled by rust-bpf Alexei Starovoitov
` (10 preceding siblings ...)
2026-09-30 19:31 ` [PATCH bpf-next 11/14] libbpf: Keep global data in arena when the object has .arena.data Alexei Starovoitov
@ 2026-09-30 19:31 ` Alexei Starovoitov
2026-09-30 20:22 ` bot+bpf-ci
2026-09-30 19:31 ` [PATCH bpf-next 13/14] selftests/bpf: Add test for global data in arena Alexei Starovoitov
2026-09-30 19:31 ` [PATCH bpf-next 14/14] selftests/bpf: Add test for global data of a program in Rust Alexei Starovoitov
13 siblings, 1 reply; 31+ messages in thread
From: Alexei Starovoitov @ 2026-09-30 19:31 UTC (permalink / raw)
To: bpf; +Cc: daniel, andrii, eddyz87, memxor
From: Alexei Starovoitov <ast@kernel.org>
bpf_printk(), BPF_SNPRINTF(), BPF_SEQ_PRINTF() and bpf_stream_printk()
copy the format into a static const array, which the compiler puts into
.rodata. When global data of the object is in arena .rodata is there
too and the helper doesn't take the format:
R1 type=scalar expected=fp, pkt, pkt_meta, map_key, map_value, mem,
ringbuf_mem, buf, trusted_ptr_, ctx
String literals are in .rodata.str1.1, which libbpf keeps in a read-only
map. No compiler flag moves a named array there. Put the arrays into
.rodata.str with a section attribute.
An object that uses the macros gets one more map, .rodata.str.
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
---
tools/lib/bpf/bpf_helpers.h | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)
diff --git a/tools/lib/bpf/bpf_helpers.h b/tools/lib/bpf/bpf_helpers.h
index 9d160b5b9c0e..b37b727ef216 100644
--- a/tools/lib/bpf/bpf_helpers.h
+++ b/tools/lib/bpf/bpf_helpers.h
@@ -248,13 +248,21 @@ enum libbpf_tristate {
#define ___bpf_fill(arr, args...) \
___bpf_apply(___bpf_fill, ___bpf_narg(args))(arr, 0, args)
+/*
+ * Format strings are in a section of their own. When global data of the object
+ * is in arena (the object has .arena.data section) libbpf keeps .rodata.str*
+ * sections in read-only maps, which is where helpers and kfuncs take strings
+ * from.
+ */
+#define ___bpf_fmt_sec __attribute__((section(".rodata.str")))
+
/*
* BPF_SEQ_PRINTF to wrap bpf_seq_printf to-be-printed values
* in a structure.
*/
#define BPF_SEQ_PRINTF(seq, fmt, args...) \
({ \
- static const char ___fmt[] = fmt; \
+ static const char ___fmt[] ___bpf_fmt_sec = fmt; \
unsigned long long ___param[___bpf_narg(args)]; \
\
_Pragma("GCC diagnostic push") \
@@ -272,7 +280,7 @@ enum libbpf_tristate {
*/
#define BPF_SNPRINTF(out, out_size, fmt, args...) \
({ \
- static const char ___fmt[] = fmt; \
+ static const char ___fmt[] ___bpf_fmt_sec = fmt; \
unsigned long long ___param[___bpf_narg(args)]; \
\
_Pragma("GCC diagnostic push") \
@@ -287,7 +295,7 @@ enum libbpf_tristate {
#ifdef BPF_NO_GLOBAL_DATA
#define BPF_PRINTK_FMT_MOD
#else
-#define BPF_PRINTK_FMT_MOD static const
+#define BPF_PRINTK_FMT_MOD static const ___bpf_fmt_sec
#endif
#define __bpf_printk(fmt, ...) \
@@ -303,7 +311,7 @@ enum libbpf_tristate {
*/
#define __bpf_vprintk(fmt, args...) \
({ \
- static const char ___fmt[] = fmt; \
+ static const char ___fmt[] ___bpf_fmt_sec = fmt; \
unsigned long long ___param[___bpf_narg(args)]; \
\
_Pragma("GCC diagnostic push") \
@@ -317,7 +325,7 @@ enum libbpf_tristate {
#define bpf_stream_printk(stream_id, fmt, args...) \
({ \
- static const char ___fmt[] = fmt; \
+ static const char ___fmt[] ___bpf_fmt_sec = fmt; \
unsigned long long ___param[___bpf_narg(args)]; \
\
_Pragma("GCC diagnostic push") \
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 13/14] selftests/bpf: Add test for global data in arena
2026-09-30 19:31 [PATCH bpf-next 00/14] bpf: Support programs compiled by rust-bpf Alexei Starovoitov
` (11 preceding siblings ...)
2026-09-30 19:31 ` [PATCH bpf-next 12/14] libbpf: Keep format strings of bpf_printk() in .rodata.str Alexei Starovoitov
@ 2026-09-30 19:31 ` Alexei Starovoitov
2026-09-30 20:22 ` bot+bpf-ci
2026-09-30 19:31 ` [PATCH bpf-next 14/14] selftests/bpf: Add test for global data of a program in Rust Alexei Starovoitov
13 siblings, 1 reply; 31+ messages in thread
From: Alexei Starovoitov @ 2026-09-30 19:31 UTC (permalink / raw)
To: bpf; +Cc: daniel, andrii, eddyz87, memxor
From: Alexei Starovoitov <ast@kernel.org>
Load the program that reads and writes .data, .bss and .rodata from
the object with .arena.data section. Check that initial values set
through the skeleton reach the program and that the values written by
the program are seen through the skeleton.
Also check:
- alignment of a section.
- pointers to data and to a const string that are stored in data and
in read-only data, next to a pointer to a function that callx uses.
- bpf_strncmp() with a string literal, bpf_printk() and BPF_SNPRINTF().
- an object that has an arena map and __arena variables.
- the load fails when a pointer in data points to a section that is
not in arena or to a variable of the kernel.
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
---
tools/testing/selftests/bpf/Makefile.skel | 3 +-
.../selftests/bpf/prog_tests/data_in_arena.c | 146 ++++++++++++++++++
.../selftests/bpf/progs/data_in_arena.c | 107 +++++++++++++
.../selftests/bpf/progs/data_in_arena_decl.c | 37 +++++
.../bpf/progs/data_in_arena_extern.c | 20 +++
.../selftests/bpf/progs/data_in_arena_fail.c | 20 +++
6 files changed, 332 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/bpf/prog_tests/data_in_arena.c
create mode 100644 tools/testing/selftests/bpf/progs/data_in_arena.c
create mode 100644 tools/testing/selftests/bpf/progs/data_in_arena_decl.c
create mode 100644 tools/testing/selftests/bpf/progs/data_in_arena_extern.c
create mode 100644 tools/testing/selftests/bpf/progs/data_in_arena_fail.c
diff --git a/tools/testing/selftests/bpf/Makefile.skel b/tools/testing/selftests/bpf/Makefile.skel
index 2e22bb901bf3..d9ddd1d86f89 100644
--- a/tools/testing/selftests/bpf/Makefile.skel
+++ b/tools/testing/selftests/bpf/Makefile.skel
@@ -20,7 +20,8 @@ ifneq ($(BPF_CC),)
BPF_SRCS := $(notdir $(wildcard progs/*.c))
BPF_OBJS := $(patsubst %.c,$(RDIR)/%.bpf.o,$(BPF_SRCS))
-SKEL_BLACKLIST := btf__% test_pinning_invalid.c test_sk_assign.c
+SKEL_BLACKLIST := btf__% test_pinning_invalid.c test_sk_assign.c \
+ data_in_arena_extern.c
LINKED_SKELS := test_static_linked.skel.h linked_funcs.skel.h \
linked_vars.skel.h linked_maps.skel.h linked_arena.skel.h \
diff --git a/tools/testing/selftests/bpf/prog_tests/data_in_arena.c b/tools/testing/selftests/bpf/prog_tests/data_in_arena.c
new file mode 100644
index 000000000000..bccfb0110b94
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/data_in_arena.c
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include "data_in_arena.skel.h"
+#include "data_in_arena_decl.skel.h"
+#include "data_in_arena_fail.skel.h"
+
+static int run_prog(struct bpf_program *prog)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, topts);
+
+ if (!ASSERT_OK(bpf_prog_test_run_opts(bpf_program__fd(prog), &topts), "test_run"))
+ return -1;
+ return topts.retval;
+}
+
+static void run(struct data_in_arena *skel, int counter)
+{
+ int i;
+
+ ASSERT_EQ(run_prog(skel->progs.use_data), counter + 7 + 1 + 2, "retval");
+ ASSERT_EQ(skel->bss->sum, counter + 7 + 1 + 2, "sum");
+ ASSERT_EQ(skel->data->counter, counter + 1, "counter");
+ ASSERT_EQ(skel->data->pair[1], 5, "pair[1]");
+ for (i = 0; i < 4; i++)
+ ASSERT_EQ(skel->bss->table[i], 10 * (i + 1) + i, "table");
+}
+
+static void test_in_arena(void)
+{
+ struct bpf_map_info info = {};
+ struct data_in_arena *skel;
+ __u32 len = sizeof(info);
+ struct bpf_map *arena;
+ size_t sz;
+
+ skel = data_in_arena__open();
+ if (!ASSERT_OK_PTR(skel, "open"))
+ return;
+
+ arena = bpf_object__find_map_by_name(skel->obj, "arena");
+ if (!ASSERT_OK_PTR(arena, "arena"))
+ goto out;
+ ASSERT_EQ(bpf_map__type(arena), BPF_MAP_TYPE_ARENA, "arena type");
+ ASSERT_EQ(bpf_map__max_entries(arena), 1, "arena pages");
+ ASSERT_OK(bpf_map__set_max_entries(arena, 8), "arena resize");
+ ASSERT_FALSE(bpf_map__autocreate(skel->maps.data), "data autocreate");
+ ASSERT_FALSE(bpf_map__autocreate(skel->maps.bss), "bss autocreate");
+ ASSERT_FALSE(bpf_map__autocreate(skel->maps.rodata), "rodata autocreate");
+ ASSERT_EQ(bpf_map__set_autocreate(skel->maps.data, true), -EOPNOTSUPP, "set_autocreate");
+ ASSERT_EQ(bpf_map__set_value_size(skel->maps.bss, 4096), -EOPNOTSUPP, "set_value_size");
+ ASSERT_EQ(bpf_map__initial_value(skel->maps.data, &sz), skel->data, "initial_value");
+ ASSERT_EQ(sz, sizeof(*skel->data), "initial_value size");
+
+ /* initial values are set the usual way */
+ skel->data->counter = 100;
+
+ if (!ASSERT_OK(data_in_arena__load(skel), "load"))
+ goto out;
+ /* there are no maps behind the sections */
+ ASSERT_ERR(bpf_map_get_info_by_fd(bpf_map__fd(skel->maps.data), &info, &len), "data map");
+ ASSERT_ERR(bpf_map_get_info_by_fd(bpf_map__fd(skel->maps.bss), &info, &len), "bss map");
+ ASSERT_ERR(bpf_map_get_info_by_fd(bpf_map__fd(skel->maps.rodata), &info, &len),
+ "rodata map");
+ run(skel, 100);
+
+ /* pointers to data next to pointers to functions */
+ ASSERT_EQ(run_prog(skel->progs.use_ops), 42 + 1 + 'e', "use_ops");
+ ASSERT_EQ(skel->data->counter, 102, "counter");
+
+ /* alignment of sections and pointers to data in data */
+ ASSERT_EQ((unsigned long)&skel->bss->aligned64 % 64, 0, "alignment");
+ ASSERT_EQ(run_prog(skel->progs.use_ptrs), 0, "use_ptrs");
+ ASSERT_EQ(skel->data->x, 43, "x");
+ ASSERT_EQ(skel->bss->aligned64.v[7], 7, "aligned64");
+ ASSERT_EQ(skel->data->px, &skel->data->x, "px");
+
+ /* format strings of bpf_printk() and BPF_SNPRINTF() */
+ ASSERT_EQ(run_prog(skel->progs.use_printk), sizeof("43-7"), "use_printk");
+ ASSERT_STREQ(skel->bss->out, "43-7", "out");
+out:
+ data_in_arena__destroy(skel);
+}
+
+/* The object has an arena map and __arena variables */
+static void test_declared_arena(void)
+{
+ struct data_in_arena_decl *skel;
+ struct bpf_map *map;
+ int arenas = 0;
+
+ skel = data_in_arena_decl__open();
+ if (!ASSERT_OK_PTR(skel, "open"))
+ return;
+ bpf_object__for_each_map(map, skel->obj)
+ arenas += bpf_map__type(map) == BPF_MAP_TYPE_ARENA;
+ ASSERT_EQ(arenas, 1, "no second arena");
+ skel->data->counter = 6;
+ if (!ASSERT_OK(data_in_arena_decl__load(skel), "load"))
+ goto out;
+ ASSERT_EQ(run_prog(skel->progs.use_data), 6 + 7 + 11, "retval");
+ ASSERT_EQ(run_prog(skel->progs.use_data), 7 + 7 + 12, "retval");
+ ASSERT_EQ(skel->bss->sum, 7 + 7 + 12, "sum");
+ ASSERT_EQ(skel->data->counter, 8, "counter");
+out:
+ data_in_arena_decl__destroy(skel);
+}
+
+/* A pointer in data that can't be made an address of arena fails the load */
+static void test_ptr_to_map(void)
+{
+ struct data_in_arena_fail *skel;
+
+ skel = data_in_arena_fail__open();
+ if (!ASSERT_OK_PTR(skel, "open"))
+ return;
+ ASSERT_ERR(data_in_arena_fail__load(skel), "load");
+ data_in_arena_fail__destroy(skel);
+}
+
+/* So does a pointer to a variable of the kernel. There is no skeleton: the open fails. */
+static void test_ptr_to_extern(void)
+{
+ struct bpf_object *obj;
+
+ obj = bpf_object__open_file("./data_in_arena_extern.bpf.o", NULL);
+ if (!ASSERT_ERR_PTR(obj, "open"))
+ bpf_object__close(obj);
+}
+
+void test_data_in_arena(void)
+{
+#if !defined(__x86_64__) && !defined(__aarch64__)
+ /* other JITs don't take BPF_F_ARENA_SCALAR */
+ test__skip();
+ return;
+#endif
+ if (test__start_subtest("arena"))
+ test_in_arena();
+ if (test__start_subtest("declared_arena"))
+ test_declared_arena();
+ if (test__start_subtest("ptr_to_map"))
+ test_ptr_to_map();
+ if (test__start_subtest("ptr_to_extern"))
+ test_ptr_to_extern();
+}
diff --git a/tools/testing/selftests/bpf/progs/data_in_arena.c b/tools/testing/selftests/bpf/progs/data_in_arena.c
new file mode 100644
index 000000000000..ce3838327d6f
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/data_in_arena.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+/* global data of the object is in arena */
+char data_in_arena SEC(".arena.data");
+
+int counter = 5;
+long pair[2] = { 1, 2 };
+int x = 42;
+long sum;
+long table[4];
+struct {
+ long v[8];
+} aligned64 __attribute__((aligned(64)));
+const volatile int ro = 7;
+const volatile long ro_table[4] = { 10, 20, 30, 40 };
+
+/* const strings stay in a map for helpers and kfuncs, a copy of them is in arena */
+const char hello[] SEC(".rodata.str.hello") = "hello";
+
+/* pointers to data that are stored in data */
+int *px = &x;
+const char *str = hello;
+int *const volatile cpx SEC(".data.rel.ro") = &x;
+
+SEC("syscall")
+int use_data(void *ctx)
+{
+ int i;
+
+ for (i = 0; i < 4; i++)
+ table[i] = ro_table[i] + i;
+ sum = counter + ro + pair[0] + pair[1];
+ counter++;
+ __sync_fetch_and_add(&pair[1], 3);
+ return sum;
+}
+
+typedef int (*op_fn)(int);
+
+static __noinline int add1(int v)
+{
+ return v + 1;
+}
+
+/*
+ * Pointers to functions and to data in read-only data of a program with callx.
+ * Volatile, so that the compiler doesn't replace the pointers with what
+ * they point to.
+ */
+static const volatile struct {
+ op_fn fn;
+ int *data;
+ const char *name;
+} ops SEC(".data.rel.ro") = { add1, &x, hello };
+
+SEC("syscall")
+int use_ops(void *ctx)
+{
+ /* a program has the arena when its code refers to it */
+ counter++;
+ return ops.fn(*ops.data) + ops.name[1];
+}
+
+SEC("syscall")
+int use_ptrs(void *ctx)
+{
+ unsigned long addr = (unsigned long)&aligned64;
+ char local[4] = "abc";
+
+ /* hide the address from the compiler, it knows that '& 63' is 0 */
+ asm volatile ("" : "+r"(addr));
+ if (addr & 63)
+ return 1;
+ aligned64.v[7] = 7;
+ if (*px != 42)
+ return 2;
+ *px = 43;
+ if (x != 43 || *cpx != 43)
+ return 3;
+ if (str[0] != 'h' || str[4] != 'o' || str[5])
+ return 4;
+ /* the literal is in a map */
+ if (bpf_strncmp(local, sizeof(local), "abc"))
+ return 5;
+ return 0;
+}
+
+char out[16];
+
+/* format strings are in a map */
+SEC("syscall")
+int use_printk(void *ctx)
+{
+ char buf[sizeof(out)];
+ int i, n;
+
+ bpf_printk("counter %d", counter);
+ n = BPF_SNPRINTF(buf, sizeof(buf), "%d-%d", x, ro);
+ for (i = 0; i < sizeof(out); i++)
+ out[i] = buf[i];
+ return n;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/data_in_arena_decl.c b/tools/testing/selftests/bpf/progs/data_in_arena_decl.c
new file mode 100644
index 000000000000..01bc4fea8f0c
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/data_in_arena_decl.c
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define BPF_NO_KFUNC_PROTOTYPES
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_experimental.h"
+#include <bpf_arena_common.h>
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARENA);
+ __uint(map_flags, BPF_F_MMAPABLE);
+ __uint(max_entries, 4);
+} arena SEC(".maps");
+
+/* global data of the object is in arena */
+char data_in_arena SEC(".arena.data");
+
+int counter = 5;
+long sum;
+const volatile int ro = 7;
+
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+int __arena avar = 11;
+#else
+int avar = 11;
+#endif
+
+SEC("syscall")
+int use_data(void *ctx)
+{
+ sum = counter + ro + avar;
+ counter++;
+ avar++;
+ return sum;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/data_in_arena_extern.c b/tools/testing/selftests/bpf/progs/data_in_arena_extern.c
new file mode 100644
index 000000000000..dc1ad5ca3bdd
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/data_in_arena_extern.c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+/* global data of the object is in arena */
+char data_in_arena SEC(".arena.data");
+
+extern const int bpf_prog_active __ksym;
+
+/* the variable of the kernel is not in arena */
+const void *kp = &bpf_prog_active;
+
+SEC("syscall")
+int ptr_to_extern(void *ctx)
+{
+ return *(int *)kp;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/data_in_arena_fail.c b/tools/testing/selftests/bpf/progs/data_in_arena_fail.c
new file mode 100644
index 000000000000..ad8afe9afc96
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/data_in_arena_fail.c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+/* global data of the object is in arena */
+char data_in_arena SEC(".arena.data");
+
+int x = 42;
+/* the table is read-only data with pointers. It's not in arena. */
+int *const volatile tbl[1] SEC(".data.rel.ro") = { &x };
+int *const volatile *pp = tbl;
+
+SEC("syscall")
+int ptr_to_map(void *ctx)
+{
+ return **pp;
+}
+
+char _license[] SEC("license") = "GPL";
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 14/14] selftests/bpf: Add test for global data of a program in Rust
2026-09-30 19:31 [PATCH bpf-next 00/14] bpf: Support programs compiled by rust-bpf Alexei Starovoitov
` (12 preceding siblings ...)
2026-09-30 19:31 ` [PATCH bpf-next 13/14] selftests/bpf: Add test for global data in arena Alexei Starovoitov
@ 2026-09-30 19:31 ` Alexei Starovoitov
2026-09-30 19:53 ` sashiko-bot
2026-09-30 20:22 ` bot+bpf-ci
13 siblings, 2 replies; 31+ messages in thread
From: Alexei Starovoitov @ 2026-09-30 19:31 UTC (permalink / raw)
To: bpf; +Cc: daniel, andrii, eddyz87, memxor
From: Alexei Starovoitov <ast@kernel.org>
data_in_arena_rust.rs shows why libbpf keeps .data, .bss and .rodata of
a program in Rust in arena. A reference in Rust is an address that can
be stored in data. The list in the test has a node in each of the three
sections. IN_BSS.next is stored by the program, IN_DATA.next is
a relocation in .data against .rodata. sum() follows them and reads all
nodes with the same insns:
5: w0 = *(u32 *)(r1 + 0x8)
8: r1 = *(u64 *)(r1 + 0x0)
The same program without .arena.data section, when the sections are
array maps:
libbpf: elf: skipping relo section(9) .rel.data for section(8) .data
...
16: (79) r1 = *(u64 *)(r1 +0) ; frame1: R1=scalar()
...
13: (61) r0 = *(u32 *)(r1 +8)
R1 invalid mem access 'scalar'
The program is built by upstream rustc for its bpfel-unknown-none
target. Nothing is done to the output: rustc emits LLVM bitcode and
clang makes the object of it for the cpu version of the test_progs
flavor. rustc has no prebuilt core for the target, so core is built from
the source that comes with rustc, like the kernel does it. That takes
5 seconds.
The subtest is skipped when there is no rustc, no source of core
(rustup component add rust-src) or when clang is older than 23.
There are no kfuncs in the program. rustc doesn't emit debug info for
extern functions, so there would be no BTF for them.
panic=abort is a stop gap. Nothing in the program panics, so the panic
handler is not loaded. The series from Yonghong are about to add support
for panic=unwind.
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
---
tools/testing/selftests/bpf/Makefile | 12 +++-
.../testing/selftests/bpf/Makefile.buildvars | 19 +++++
tools/testing/selftests/bpf/Makefile.skel | 14 ++++
.../selftests/bpf/prog_tests/data_in_arena.c | 25 +++++++
.../selftests/bpf/progs/data_in_arena_rust.rs | 70 +++++++++++++++++++
5 files changed, 139 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/bpf/progs/data_in_arena_rust.rs
diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index afa589a27b15..a22be7efd1fa 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -422,6 +422,16 @@ $(LIBARENA_ASAN_SKEL): $(INCLUDE_DIR)/vmlinux.h $(BPFOBJ) $(LIBARENA_BPF_DEPS)
+$(MAKE) -C libarena libarena_asan.skel.h $(LIBARENA_MAKE_ARGS)
endif
+# #![no_std] looks for compiler_builtins too. Nothing of it is used.
+ifneq ($(RUST_CORE),)
+$(RUST_CORE): $(RUST_CORE_SRC)
+ $(call msg,RUSTC,,$@)
+ $(Q)mkdir -p $(@D)
+ +$(Q)$(RUSTC_BPF) -A warnings --edition 2024 --crate-name core --out-dir $(@D) $<
+ +$(Q)echo '#![feature(compiler_builtins)] #![compiler_builtins] #![no_std]' | \
+ $(RUSTC_BPF) -A warnings --crate-name compiler_builtins --out-dir $(@D) -
+endif
+
# Generated test list headers
define gen_tests_hdr
@@ -457,7 +467,7 @@ RUNNER_PREREQS := $(INCLUDE_DIR)/vmlinux.h $(BPFOBJ) $(BPFTOOL) \
$(VERIFY_SIG_HDR) $(PRIVATE_KEY) $(VERIFICATION_CERT) \
$(LIBARENA_SKEL) $(LIBARENA_ASAN_SKEL) \
prog_tests/tests.h map_tests/tests.h \
- $(RUNNER_OBJS)
+ $(RUNNER_OBJS) $(RUST_CORE)
# Runtime fixtures for each test_progs flavor.
RUNNER_EXTRA_FILES := $(OUTPUT)/urandom_read \
diff --git a/tools/testing/selftests/bpf/Makefile.buildvars b/tools/testing/selftests/bpf/Makefile.buildvars
index d2a0c0031b87..c1a255d4287e 100644
--- a/tools/testing/selftests/bpf/Makefile.buildvars
+++ b/tools/testing/selftests/bpf/Makefile.buildvars
@@ -109,6 +109,25 @@ HOST_INCLUDE_DIR := $(INCLUDE_DIR)
endif
RESOLVE_BTFIDS := $(HOST_BUILD_DIR)/resolve_btfids/resolve_btfids
+# Programs in Rust are built by upstream rustc. It has no prebuilt core for
+# the bpf target, so it has to come with the source of core:
+# rustup component add rust-src
+# rustc emits LLVM bitcode, clang 23 or newer makes the object of it.
+# Without any of them RUST_CORE is empty and the tests are skipped.
+RUSTC ?= rustc
+RUST_CORE_SRC := $(wildcard $(shell $(RUSTC) --print sysroot 2>/dev/null)$\
+ /lib/rustlib/src/rust/library/core/src/lib.rs)
+ifneq ($(RUST_CORE_SRC),)
+ifeq ($(shell [ 0$(shell echo __clang_major__ | $(CLANG) -E -P -x c - 2>/dev/null) -ge 23 ] && echo y),y)
+RUST_CORE := $(BUILD_DIR)/rust/libcore.rlib
+endif
+endif
+# RUSTC_BOOTSTRAP=1 is to build core with a stable rustc, like the kernel does.
+# panic=abort is a stop gap until panic=unwind is supported.
+RUSTC_BPF = RUSTC_BOOTSTRAP=1 $(RUSTC) -O -C panic=abort --crate-type rlib \
+ --target $(if $(IS_LITTLE_ENDIAN),bpfel,bpfeb)-unknown-none \
+ -L $(dir $(RUST_CORE))
+
DEFAULT_BPFTOOL := $(HOST_SCRATCH_DIR)/sbin/bpftool
ifneq ($(CROSS_COMPILE),)
CROSS_BPFTOOL := $(SCRATCH_DIR)/sbin/bpftool
diff --git a/tools/testing/selftests/bpf/Makefile.skel b/tools/testing/selftests/bpf/Makefile.skel
index d9ddd1d86f89..df6bbf644a43 100644
--- a/tools/testing/selftests/bpf/Makefile.skel
+++ b/tools/testing/selftests/bpf/Makefile.skel
@@ -138,4 +138,18 @@ $(LINKED_SKELS_H): $(RDIR)/%.skel.h: $$(addprefix $(RDIR)/,$$($$*.skel.h-deps))
$(BPFTOOL) $(GEN_SKEL) | $(RDIR)
$(Q)$(cmd_bpf_link_skel)
+# Programs in Rust, see Makefile.buildvars. No skeletons: the objects may be absent.
+ifneq ($(RUST_CORE),)
+ifeq ($(BPF_CC),$(CLANG))
+RUST_OBJS := $(patsubst progs/%.rs,$(RDIR)/%.bpf.o,$(wildcard progs/*.rs))
+BPF_OBJS += $(RUST_OBJS)
+
+$(RUST_OBJS): $(RDIR)/%.bpf.o: progs/%.rs $(RUST_CORE) | $(RDIR)
+ $(call msg,RUSTC,$(BINARY),$@)
+ +$(Q)$(RUSTC_BPF) --edition 2021 -C debuginfo=2 --emit=llvm-bc -o - \
+ $(patsubst -mcpu=%,-C target-cpu=%,$(filter -mcpu=%,$(BPF_CC_FLAGS))) $< | \
+ $(BPF_CC) $(BPF_CC_FLAGS) -x ir -c - -o $@
+endif
+endif
+
endif # BPF_CC
diff --git a/tools/testing/selftests/bpf/prog_tests/data_in_arena.c b/tools/testing/selftests/bpf/prog_tests/data_in_arena.c
index bccfb0110b94..d8bc489820b1 100644
--- a/tools/testing/selftests/bpf/prog_tests/data_in_arena.c
+++ b/tools/testing/selftests/bpf/prog_tests/data_in_arena.c
@@ -128,6 +128,29 @@ static void test_ptr_to_extern(void)
bpf_object__close(obj);
}
+/* The object is there if rustc and clang can build it, see Makefile.buildvars */
+static void test_rust(void)
+{
+ const char *file = "./data_in_arena_rust.bpf.o";
+ struct bpf_object *obj;
+
+ if (access(file, R_OK)) {
+ test__skip();
+ return;
+ }
+ obj = bpf_object__open_file(file, NULL);
+ if (!ASSERT_OK_PTR(obj, "open"))
+ return;
+ if (!ASSERT_OK(bpf_object__load(obj), "load"))
+ goto out;
+ /* libbpf goes on without BTF when the kernel doesn't take it */
+ ASSERT_GE(bpf_object__btf_fd(obj), 0, "btf_fd");
+ ASSERT_EQ(run_prog(bpf_object__find_program_by_name(obj, "list_in_data")),
+ 100 + 20 + 3, "retval");
+out:
+ bpf_object__close(obj);
+}
+
void test_data_in_arena(void)
{
#if !defined(__x86_64__) && !defined(__aarch64__)
@@ -143,4 +166,6 @@ void test_data_in_arena(void)
test_ptr_to_map();
if (test__start_subtest("ptr_to_extern"))
test_ptr_to_extern();
+ if (test__start_subtest("rust"))
+ test_rust();
}
diff --git a/tools/testing/selftests/bpf/progs/data_in_arena_rust.rs b/tools/testing/selftests/bpf/progs/data_in_arena_rust.rs
new file mode 100644
index 000000000000..b69128487a0f
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/data_in_arena_rust.rs
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Why .data, .bss and .rodata of a program in Rust are in arena.
+//
+// A reference in Rust is an address. It doesn't say what it points to and it
+// can be stored in data. The list below has a node in each of the sections.
+// The nodes are linked by references that are in the data:
+// IN_BSS.next is stored by the program,
+// IN_DATA.next is a relocation in .data against .rodata.
+// sum() loads the references back and reads the three nodes with the same insn.
+//
+// When the sections are array maps libbpf skips the relocation in .data, and
+// what sum() loads from a node is a number that can't be dereferenced:
+// R1 invalid mem access 'scalar'
+// In arena the address of a node is a number to begin with.
+
+#![no_std]
+#![no_main]
+
+// Tell libbpf to keep .data, .bss and .rodata in arena.
+#[used]
+#[link_section = ".arena.data"]
+static DATA_IN_ARENA: u8 = 0;
+
+#[used]
+#[link_section = "license"]
+static LICENSE: [u8; 4] = *b"GPL\0";
+
+// panic=abort is a stop gap until panic=unwind is supported.
+// Nothing here panics, so the handler is not a part of the program.
+#[panic_handler]
+fn panic(_info: &core::panic::PanicInfo) -> ! {
+ loop {}
+}
+
+pub struct Node {
+ val: u32,
+ next: Option<&'static Node>,
+}
+
+// no_mangle makes them visible outside, so LLVM can't fold the list into a constant.
+#[no_mangle]
+static IN_RODATA: Node = Node { val: 3, next: None };
+#[no_mangle]
+static mut IN_DATA: Node = Node { val: 20, next: Some(&IN_RODATA) };
+#[no_mangle]
+static mut IN_BSS: Node = Node { val: 0, next: None };
+
+#[inline(never)]
+fn sum(mut node: Option<&Node>) -> u32 {
+ let mut sum = 0;
+ // The verifier wants a bound.
+ for _ in 0..8 {
+ let Some(n) = node else { break };
+ sum += n.val;
+ node = n.next;
+ }
+ sum
+}
+
+#[no_mangle]
+#[link_section = "syscall"]
+pub extern "C" fn list_in_data(_ctx: *mut u8) -> u32 {
+ unsafe {
+ let head = &mut *&raw mut IN_BSS;
+ head.val = 100;
+ head.next = Some(&*&raw const IN_DATA);
+ sum(Some(head))
+ }
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 11/14] libbpf: Keep global data in arena when the object has .arena.data
2026-09-30 19:31 ` [PATCH bpf-next 11/14] libbpf: Keep global data in arena when the object has .arena.data Alexei Starovoitov
@ 2026-09-30 19:46 ` sashiko-bot
2026-09-30 20:22 ` bot+bpf-ci
1 sibling, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-09-30 19:46 UTC (permalink / raw)
To: Alexei Starovoitov; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] libbpf: integer overflow in bpf_object__collect_data_ptr() allows heap corruption
- [Medium] libbpf: ARENA map bypasses pinning and reuse if data pointers exist
--
commit d23345bdc249c218c8d1f72cbc12d47c5a72bd22
Author: Alexei Starovoitov <ast@kernel.org>
libbpf: Keep global data in arena when the object has .arena.data
This commit introduces support for placing global data within an arena for
objects containing a .arena.data section. It appends .data, .bss, and .rodata
sections after __arena variables, converting relocations against these
sections into relocations against the arena map.
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
[ ... ]
> @@ -5736,6 +5954,10 @@ bpf_object__create_maps(struct bpf_object *obj)
> continue;
> }
>
> + /* see bpf_object__relocate_data_ptrs() */
> + if (map->def.type == BPF_MAP_TYPE_ARENA && obj->arena_mapped)
> + continue;
> +
[Severity: Medium]
Since bpf_object__relocate_data_ptrs() can create the ARENA map early and
set obj->arena_mapped to true, will this skip bypass the pinning and reuse
logic in bpf_object__reuse_map()?
If an application uses both data pointers and pinned ARENA maps, it appears
the map sharing might break because the early creation unconditionally skips
pin_path and reuse evaluations.
> err = map_set_def_max_entries(map);
> if (err)
> goto err_out;
[ ... ]
> @@ -8067,12 +8348,84 @@ static int bpf_object__collect_map_relos(struct bpf_object *obj,
[ ... ]
> +static int bpf_object__collect_data_ptr(struct bpf_object *obj, const char *relo_sec_name,
> + int relo_idx, size_t sec_idx, const Elf64_Rel *rel,
> + const Elf64_Sym *sym)
> +{
> + Elf_Data *scn_data = obj->efile.secs[sec_idx].data;
> + const char *sym_name = elf_sym_str(obj, sym->st_name) ?: "<?>";
> + struct data_ptr *ptrs;
> +
> + if (ELF64_ST_TYPE(sym->st_info) == STT_SECTION && sym->st_shndx < obj->efile.sec_cnt)
> + sym_name = elf_sec_name(obj, elf_sec_by_idx(obj, sym->st_shndx)) ?: "<?>";
> +
> + if (ELF64_R_TYPE(rel->r_info) != R_BPF_64_ABS64 ||
> + sym->st_shndx >= obj->efile.sec_cnt ||
> + (sym->st_shndx != obj->efile.arena_data_shndx &&
> + !bpf_object__shndx_is_data(obj, sym->st_shndx)) ||
> + rel->r_offset + sizeof(__u64) > scn_data->d_size) {
[Severity: High]
Could this bounds check overflow if rel->r_offset is maliciously large
(e.g., UINT64_MAX - 7)?
Since both operands are unsigned 64-bit integers, the addition might wrap
around to 0, which would incorrectly pass the > scn_data->d_size check.
If this check is bypassed, the overflowed offset is later used in
bpf_object__relocate_data_ptrs():
memcpy(&val, map->mmaped + p->sec_off, sizeof(val));
val += addr;
memcpy(map->mmaped + p->sec_off, &val, sizeof(val));
Can this lead to an out-of-bounds read and write against map->mmaped,
potentially corrupting heap metadata in the process parsing the object?
> + pr_warn("sec '%s': relo #%d: can't resolve pointer to '%s' at offset %zu when data is in arena\n",
> + relo_sec_name, relo_idx, sym_name, (size_t)rel->r_offset);
> + return -LIBBPF_ERRNO__RELOC;
> + }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260930193127.2181684-1-alexei.starovoitov@gmail.com?part=11
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 09/14] bpf: Allow a variable in DATASEC that is smaller than its type
2026-09-30 19:31 ` [PATCH bpf-next 09/14] bpf: Allow a variable in DATASEC that is smaller than its type Alexei Starovoitov
@ 2026-09-30 19:47 ` sashiko-bot
0 siblings, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-09-30 19:47 UTC (permalink / raw)
To: Alexei Starovoitov; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] bpftool/perf: out-of-bounds heap read when dumping DATASEC variables
--
commit 1b453d08464afcede36a667c89c48e99ef1d651d
Author: Alexei Starovoitov <ast@kernel.org>
bpf: Allow a variable in DATASEC that is smaller than its type
LLVM splits a static of a Rust program into pieces. Every piece is a VAR
with the type of the whole static:
[223] STRUCT 'BpfCell<core::option::Option<...>>' size=32 vlen=1
[236] VAR '..scx_cosmos9TASK_CTXS.0' type_id=223, linkage=static
[248] DATASEC '.bss' size=0 vlen=9
type_id=236 offset=24648 size=1 (VAR '..TASK_CTXS.0')
and the kernel rejects such BTF with "Invalid size". Allow it.
> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index c9d4b709380c9..0630675377aaa 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c
[ ... ]
> @@ -5412,16 +5412,16 @@ static int btf_datasec_resolve(struct btf_verifier_env *env,
> return env_stack_push(env, var_type, var_type_id);
> }
>
> + /*
> + * The variable can be smaller than its type. It's a piece of
> + * a variable that the compiler split then, with the type of
> + * the whole variable.
> + */
> type_id = var_type->type;
> - if (!btf_type_id_size(btf, &type_id, &type_size)) {
> + if (!btf_type_id_size(btf, &type_id, NULL)) {
> btf_verifier_log_vsi(env, v->t, vsi, "Invalid type");
> return -EINVAL;
> }
> -
> - if (vsi->size < type_size) {
> - btf_verifier_log_vsi(env, v->t, vsi, "Invalid size");
> - return -EINVAL;
> - }
[Severity: Medium]
Does removing this validation expose userspace tools to out-of-bounds heap
reads?
By allowing vsi->size < type_size, a DATASEC variable piece's offset plus its
full type size can now exceed the DATASEC's total size.
When bpftool dumps a DATASEC:
tools/bpf/bpftool/btf_dumper.c:btf_dumper_datasec() {
...
ret = btf_dumper_do_type(d, vsi[i].type, 0, data + vsi[i].offset);
...
}
It unconditionally reads bytes based on the variable's full type size. Since
the piece is smaller than its type, this reads past the end of the allocated
map value buffer.
Similarly, perf passes the full type size as the bounding data_sz to libbpf:
tools/perf/util/bpf-event.c:format_btf_variable() {
...
btf_size = btf__resolve_size(btf, t->type);
btf_dump__dump_type_data(d, t->type, btf_data, btf_size, &opts);
...
}
This incorrectly bypasses libbpf's internal bounds checks, causing libbpf
to read out-of-bounds from map->rodata.
> }
>
> env_stack_pop_resolved(env, 0, 0);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260930193127.2181684-1-alexei.starovoitov@gmail.com?part=9
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 04/14] selftests/bpf: Add tests for arena access through numbers
2026-09-30 19:31 ` [PATCH bpf-next 04/14] selftests/bpf: Add tests for arena access through numbers Alexei Starovoitov
@ 2026-09-30 19:48 ` sashiko-bot
2026-09-30 20:22 ` bot+bpf-ci
1 sibling, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-09-30 19:48 UTC (permalink / raw)
To: Alexei Starovoitov; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] bpf: verifier rejects valid atomic RMW due to register aliasing in check_atomic_rmw
--
commit 3887e0dd2a26cbc2d80c4f160a7eb98315fcc18d
Author: Alexei Starovoitov <ast@kernel.org>
selftests/bpf: Add tests for arena access through numbers
This commit adds various tests for loading and storing through addresses
returned by bpf_arena_alloc_pages() without cast_kern. It includes tests
covering different sizes, atomic operations, and boundary conditions for
arena and non-arena pointers.
> diff --git a/tools/testing/selftests/bpf/progs/verifier_arena_scalar.c b/tools/testing/selftests/bpf/progs/verifier_arena_scalar.c
> new file mode 100644
> index 0000000000000..bebc37f501b7e
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/verifier_arena_scalar.c
[ ... ]
> +SEC("syscall")
> +__arena_scalar
> +__description("arena_scalar: xchg into the register that holds a pointer to stack")
> +__failure __msg("misaligned access off (0x0; 0xffffffffffffffff)+0 size 8")
> +__naked void xchg_into_stack_ptr(void)
> +{
> + asm volatile (" \
> + r1 = %[arena] ll; \
> + r1 = 0; \
> + *(u64 *)(r10 - 8) = r1; \
> + r1 = r10; \
> + r1 += -8; \
> + .8byte %[xchg_insn]; /* r1 = xchg_64(r1 + 0, r1) */ \
> + r0 = 0; \
> + exit; \
> +" :
> + : __imm_addr(arena),
> + __imm_insn(xchg_insn, BPF_ATOMIC_OP(BPF_DW, BPF_XCHG, BPF_REG_1, BPF_REG_1, 0))
> + : __clobber_all);
> +}
[Severity: Medium]
Does this test codify an incorrect verifier rejection?
It appears the BPF verifier incorrectly rejects valid atomic read-modify-write
operations (like xchg r1, [r1]) on non-arena pointers (like PTR_TO_STACK)
due to in-place register corruption.
Looking at kernel/bpf/verifier.c:check_atomic_rmw() at line 7312:
/* load_reg may be dst_reg. Look at dst_reg before it's marked as unknown. */
dst_reg = mem_access_reg(env, insn->dst_reg, &arena);
load_reg = bpf_atomic_load_reg(insn);
if (load_reg >= 0) {
/* check and record load of old value */
err = check_reg_arg(env, load_reg, DST_OP);
Since kernel/bpf/verifier.c:mem_access_reg() at line 7162 returns a live
reference to the register state for non-arena pointers:
if (!is_arena_scalar(env, regno))
return cur_regs(env) + regno;
Could this cause an aliasing bug when the live register is later mutated by
check_reg_arg(), leading to this test's misaligned access failure message?
This is where the verifier attempts to snapshot the destination register state
but fails to isolate it from the source register mutation.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260930193127.2181684-1-alexei.starovoitov@gmail.com?part=4
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 14/14] selftests/bpf: Add test for global data of a program in Rust
2026-09-30 19:31 ` [PATCH bpf-next 14/14] selftests/bpf: Add test for global data of a program in Rust Alexei Starovoitov
@ 2026-09-30 19:53 ` sashiko-bot
2026-09-30 20:22 ` bot+bpf-ci
1 sibling, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-09-30 19:53 UTC (permalink / raw)
To: Alexei Starovoitov; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] selftests/bpf: missing mandatory ASSERT_OK_PTR validation for manual prog lookup
--
commit 37e64ff11fb20edbe8afb99ff6fe32fab0d64ea7
Author: Alexei Starovoitov <ast@kernel.org>
selftests/bpf: Add test for global data of a program in Rust
This commit introduces a new test demonstrating how libbpf handles .data,
.bss, and .rodata for BPF programs written in Rust. It builds the program
using upstream rustc and evaluates it through the test_progs framework.
> diff --git a/tools/testing/selftests/bpf/prog_tests/data_in_arena.c b/tools/testing/selftests/bpf/prog_tests/data_in_arena.c
> index bccfb0110b94b..d8bc489820b1e 100644
> --- a/tools/testing/selftests/bpf/prog_tests/data_in_arena.c
> +++ b/tools/testing/selftests/bpf/prog_tests/data_in_arena.c
[ ... ]
> +static void test_rust(void)
> +{
> + const char *file = "./data_in_arena_rust.bpf.o";
> + struct bpf_object *obj;
[ ... ]
> + if (!ASSERT_OK(bpf_object__load(obj), "load"))
> + goto out;
> + /* libbpf goes on without BTF when the kernel doesn't take it */
> + ASSERT_GE(bpf_object__btf_fd(obj), 0, "btf_fd");
> + ASSERT_EQ(run_prog(bpf_object__find_program_by_name(obj, "list_in_data")),
> + 100 + 20 + 3, "retval");
[Severity: Low]
Is an ASSERT_OK_PTR() check missing for the program lookup here?
The BPF subsystem testing guidelines mandate that manual lookup APIs like
bpf_object__find_program_by_name() must be validated before use. If the
program named "list_in_data" is missing from the object, the lookup returns
NULL. Passing this unvalidated pointer directly to run_prog() will cause
bpf_program__fd(NULL) to fail the test run.
Validating the pointer first would ensure any failure explicitly indicates
the program was not found, rather than reporting an obscure return value
mismatch.
> +out:
> + bpf_object__close(obj);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260930193127.2181684-1-alexei.starovoitov@gmail.com?part=14
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 12/14] libbpf: Keep format strings of bpf_printk() in .rodata.str
2026-09-30 19:31 ` [PATCH bpf-next 12/14] libbpf: Keep format strings of bpf_printk() in .rodata.str Alexei Starovoitov
@ 2026-09-30 20:22 ` bot+bpf-ci
0 siblings, 0 replies; 31+ messages in thread
From: bot+bpf-ci @ 2026-09-30 20:22 UTC (permalink / raw)
To: alexei.starovoitov, bpf
Cc: daniel, andrii, eddyz87, memxor, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2338 bytes --]
> libbpf: Keep format strings of bpf_printk() in .rodata.str
TL;DR: Commit message only: it may be inaccurate for objects built with
BPF_NO_GLOBAL_DATA, where bpf_printk() keeps its format on the stack and adds
no .rodata.str map. Not a bug.
> libbpf: Keep format strings of bpf_printk() in .rodata.str
>
> bpf_printk(), BPF_SNPRINTF(), BPF_SEQ_PRINTF() and bpf_stream_printk()
> copy the format into a static const array, which the compiler puts into
> .rodata. When global data of the object is in arena .rodata is there
> too and the helper doesn't take the format:
>
> R1 type=scalar expected=fp, pkt, pkt_meta, map_key, map_value, mem,
> ringbuf_mem, buf, trusted_ptr_, ctx
>
> String literals are in .rodata.str1.1, which libbpf keeps in a read-only
> map. No compiler flag moves a named array there. Put the arrays into
> .rodata.str with a section attribute.
>
> An object that uses the macros gets one more map, .rodata.str.
>
> Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This isn't a bug, but is the changelog accurate for objects built with
BPF_NO_GLOBAL_DATA?
The commit message says that bpf_printk() copies the format into a static
const array, and that "An object that uses the macros gets one more map,
.rodata.str." With BPF_NO_GLOBAL_DATA defined, the patch leaves
BPF_PRINTK_FMT_MOD empty:
#ifdef BPF_NO_GLOBAL_DATA
#define BPF_PRINTK_FMT_MOD
#else
#define BPF_PRINTK_FMT_MOD static const ___bpf_fmt_sec
#endif
so in that configuration the format in bpf_printk() is an on-stack array. It
is not placed in .rodata.str and no extra map is created. This looks
intentional, since tools/testing/selftests/bpf/progs/test_legacy_printk.c
relies on it for kernels without global data.
The exception only applies to bpf_printk() calls with at most 3 arguments.
__bpf_vprintk(), BPF_SEQ_PRINTF(), BPF_SNPRINTF() and bpf_stream_printk()
always use the static array.
Could the changelog either mention the BPF_NO_GLOBAL_DATA exception or scope
the claim to objects built without BPF_NO_GLOBAL_DATA?
---
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/36768148430
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 06/14] selftests/bpf: Add tests for names of Rust types and functions in BTF
2026-09-30 19:31 ` [PATCH bpf-next 06/14] selftests/bpf: Add tests for " Alexei Starovoitov
@ 2026-09-30 20:22 ` bot+bpf-ci
0 siblings, 0 replies; 31+ messages in thread
From: bot+bpf-ci @ 2026-09-30 20:22 UTC (permalink / raw)
To: alexei.starovoitov, bpf
Cc: daniel, andrii, eddyz87, memxor, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 4614 bytes --]
> selftests/bpf: Add tests for names of Rust types and functions in BTF
TL;DR:
- Test issue: btf_rust func_name may fail spuriously if JIT hardening or
kallsyms settings hide BPF symbols
- Nit: open-coded kallsyms scan duplicates kallsyms_find()
> diff --git a/tools/testing/selftests/bpf/prog_tests/btf_rust.c b/tools/testing/selftests/bpf/prog_tests/btf_rust.c
> new file mode 100644
> index 000000000000..b0128daceaa1
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/btf_rust.c
> @@ -0,0 +1,82 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <test_progs.h>
> +#include <bpf/btf.h>
> +
> +#define FUNC_NAME "write_fmt<scx_simple::BpfStream>"
> +#define KSYM_NAME "write_fmt_scx_simple__BpfStream_"
> +
> +static bool kallsyms_has(const char *sym)
> +{
> + char line[1024], name[512];
> + bool found = false;
> + FILE *f;
> +
> + f = fopen("/proc/kallsyms", "r");
> + if (!ASSERT_OK_PTR(f, "kallsyms"))
> + return false;
> + while (!found && fgets(line, sizeof(line), f))
> + found = sscanf(line, "%*s %*s %511s", name) == 1 && !strcmp(name, sym);
> + fclose(f);
> + return found;
> +}
A subsystem pattern flags this as potentially concerning: kallsyms_has()
open-codes a scan of /proc/kallsyms for an exact symbol name, while the
selftests already have a helper for this in trace_helpers.c:
/* open kallsyms and find addresses on the fly, faster than load + search. */
int kallsyms_find(const char *sym, unsigned long long *addr);
trace_helpers.h is already pulled in by test_progs.h, so no new include
would be needed. kallsyms_find() returns 0 when the name matches, and its
.llvm. suffix stripping only applies to type 'd' symbols, so the BPF 't'
symbols compare exactly.
Could the check be written as:
unsigned long long addr;
ASSERT_OK(kallsyms_find(sym, &addr), sym);
and kallsyms_has() dropped, so the suite keeps a single implementation of
the /proc/kallsyms parsing?
The code as written looks functionally correct: the buffers are bounded
(%511s, fgets of 1024), and the file is closed on all paths. The concern is
only the duplication of an existing helper.
[ ... ]
> + if (!info.jited_prog_len) {
> + test__skip();
> + goto out;
> + }
> +
> + p += sprintf(p, "bpf_prog_");
> + for (i = 0; i < BPF_TAG_SIZE; i++)
> + p += sprintf(p, "%02x", info.tag[i]);
> + sprintf(p, "_%s", KSYM_NAME);
> + ASSERT_TRUE(kallsyms_has(sym), sym);
The func_name subtest skips when the program is not JITed, but fails
outright when the JITed program is simply not visible in /proc/kallsyms.
Can this ASSERT_TRUE() fail spuriously?
Visibility of BPF symbols is decided at read time, not load time.
bpf_prog_kallsyms_add() always inserts a JITed prog into bpf_kallsyms, but
bpf_get_kallsym() in kernel/bpf/core.c starts with:
if (!bpf_jit_kallsyms_enabled())
return ret; /* -ERANGE */
and bpf_jit_kallsyms_enabled() returns false whenever bpf_jit_harden != 0 or
bpf_jit_kallsyms == 0. get_ksymbol_bpf() then ends the whole BPF section of
/proc/kallsyms, so kallsyms_has() returns false and the assertion fails.
This looks reachable inside the selftest suite itself.
test_bpf_insn_array() in prog_tests/bpf_insn_array.c (built on
x86_64/powerpc/aarch64) is a non-serial test, and its "blindness" subtest
does:
set_bpf_jit_harden('2') ... set_bpf_jit_harden(old)
around a prog load. Both test_bpf_insn_array and test_btf_rust are
dispatched to parallel workers by test_progs -j (test_progs.c skips a test
only on 'if (!test->should_run || test->run_serial_test) continue;' and
otherwise sends MSG_DO_TEST), so if btf_rust scans /proc/kallsyms while the
blindness window is open, func_name fails intermittently.
Other tests that change bpf_jit_harden (serial_test_kasan,
serial_test_arena_scalar_blinded) are serial precisely to avoid disturbing
parallel tests.
Outside CI the subtest also fails deterministically on hosts running with
net.core.bpf_jit_harden >= 1 or net.core.bpf_jit_kallsyms = 0, which is an
environment difference rather than a kernel bug.
Would it make sense to either make this a serial_test_btf_rust(), and/or
read /proc/sys/net/core/bpf_jit_harden and bpf_jit_kallsyms and call
test__skip() (with a printf'd reason) when BPF ksyms are hidden, the same
way the no-JIT case is skipped?
> +out:
> + if (prog_fd >= 0)
> + close(prog_fd);
> + btf__free(btf);
> +}
[ ... ]
---
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/36768148430
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 05/14] bpf: Allow names of Rust types and functions in BTF
2026-09-30 19:31 ` [PATCH bpf-next 05/14] bpf: Allow names of Rust types and functions in BTF Alexei Starovoitov
@ 2026-09-30 20:22 ` bot+bpf-ci
2026-10-01 17:42 ` Alan Maguire
1 sibling, 0 replies; 31+ messages in thread
From: bot+bpf-ci @ 2026-09-30 20:22 UTC (permalink / raw)
To: alexei.starovoitov, bpf
Cc: daniel, andrii, eddyz87, memxor, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 1687 bytes --]
> bpf: Allow names of Rust types and functions in BTF
TL;DR: Nit only: btf_name_valid_identifier() now duplicates
btf_name_valid_section() and its name and comment are stale; the review
suggests merging the two helpers.
> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c
[ ... ]
> +/* Names in BTF of Rust are not C identifiers. Allow any printable character */
> static bool btf_name_valid_identifier(const struct btf *btf, u32 offset)
> {
> /* offset must be valid */
> const char *src = btf_str_by_offset(btf, offset);
> const char *src_limit;
>
> - if (!__btf_name_char_ok(*src, true))
> + if (!isprint(*src))
> return false;
>
> /* set a limit on identifier length */
> src_limit = src + KSYM_NAME_LEN;
> src++;
> while (*src && src < src_limit) {
> - if (!__btf_name_char_ok(*src, false))
> + if (!isprint(*src))
> return false;
> src++;
> }
This isn't a bug, but btf_name_valid_identifier() now performs the same
checks as the neighbouring btf_name_valid_section(): an empty string is
rejected, every character up to KSYM_NAME_LEN must satisfy isprint(), and
the result is !*src.
The remaining '/* set a limit on identifier length */' comment and the
'identifier' in the function name also describe a restriction that is no
longer applied.
Could the two helpers be merged into one (or one call the other) under a
name that no longer says 'identifier'?
---
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/36768148430
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 04/14] selftests/bpf: Add tests for arena access through numbers
2026-09-30 19:31 ` [PATCH bpf-next 04/14] selftests/bpf: Add tests for arena access through numbers Alexei Starovoitov
2026-09-30 19:48 ` sashiko-bot
@ 2026-09-30 20:22 ` bot+bpf-ci
1 sibling, 0 replies; 31+ messages in thread
From: bot+bpf-ci @ 2026-09-30 20:22 UTC (permalink / raw)
To: alexei.starovoitov, bpf
Cc: daniel, andrii, eddyz87, memxor, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 3029 bytes --]
> selftests/bpf: Add tests for arena access through numbers
TL;DR: Test-only nits in the new blinded arena test: it could live in
verifier.c next to the existing suite (separate file also seems fine), and the
JIT-disabled skip lacks a reason message.
> diff --git a/tools/testing/selftests/bpf/prog_tests/arena_scalar_blinded.c b/tools/testing/selftests/bpf/prog_tests/arena_scalar_blinded.c
> new file mode 100644
> index 000000000000..2ac2e9a652fe
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/arena_scalar_blinded.c
> @@ -0,0 +1,21 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <test_progs.h>
> +#include "sysctl_helpers.h"
> +#include "verifier_arena_scalar.skel.h"
> +
> +/* The same tests with constants of the programs blinded */
> +void serial_test_arena_scalar_blinded(void)
> +{
A subsystem guideline flags this as potentially concerning: a new, narrow
test file for a variant of something an existing test in the same directory
already covers.
prog_tests/verifier.c already includes verifier_arena_scalar.skel.h and runs
the same programs through test_verifier_arena_scalar(). Could this be a
serial_test_verifier_arena_scalar_blinded() placed next to it in verifier.c,
so the two variants of the same suite sit together? test_progs finds
serial_test_ functions in any prog_tests/*.c file, so verifier.c would work.
Keeping it separate also looks reasonable. The test has to be serial
because it changes a global sysctl, and verifier.c has no serial_test_
functions today. One difference is that RUN() in verifier.c drops
CAP_SYS_ADMIN while RUN_TESTS() here keeps it, but no expectation in
verifier_arena_scalar.c appears to depend on that.
> + const char *harden = "/proc/sys/net/core/bpf_jit_harden";
> + char old[16] = {};
> +
> + if (!is_jit_enabled()) {
> + test__skip();
A subsystem guideline also flags a skip that has no reason string as
potentially concerning.
When the JIT is disabled, test__skip() is called with no message, so the
log does not say that the prerequisite was bpf_jit_enable. test_loader
prints a reason for its own skips, for example in run_subtest():
printf("%s:SKIP: %s\n", __func__, spec->skip_reason);
Would a one-line printf() before test__skip() help, for example:
printf("%s:SKIP: blinding needs the JIT\n", __func__);
Skipping instead of failing looks right here, since constant blinding only
happens when the JIT runs, and a bare test__skip() is also used in
prog_tests/callx_func_ptr_map.c, prog_tests/callx_rodata_lskel.c and the
RUN_JITED() macro in prog_tests/verifier.c. The only gap is the missing
reason string.
> + return;
> + }
> + if (sysctl_set_or_fail(harden, old, "2"))
> + return;
> + RUN_TESTS(verifier_arena_scalar);
> + sysctl_set_or_fail(harden, NULL, old);
> +}
---
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/36768148430
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 14/14] selftests/bpf: Add test for global data of a program in Rust
2026-09-30 19:31 ` [PATCH bpf-next 14/14] selftests/bpf: Add test for global data of a program in Rust Alexei Starovoitov
2026-09-30 19:53 ` sashiko-bot
@ 2026-09-30 20:22 ` bot+bpf-ci
1 sibling, 0 replies; 31+ messages in thread
From: bot+bpf-ci @ 2026-09-30 20:22 UTC (permalink / raw)
To: alexei.starovoitov, bpf
Cc: daniel, andrii, eddyz87, memxor, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 6399 bytes --]
> selftests/bpf: Add test for global data of a program in Rust
TL;DR:
- Selftests build may fail on rustc older than 1.85 (fixed --edition 2024),
breaking all runners despite the skip promise
- Failed rustc in the pipe may yield an empty object, so the Rust subtest
fails confusingly at run time
> diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
> index afa589a27b15..a22be7efd1fa 100644
> --- a/tools/testing/selftests/bpf/Makefile
> +++ b/tools/testing/selftests/bpf/Makefile
[ ... ]
> @@ -422,6 +422,16 @@ $(LIBARENA_ASAN_SKEL): $(INCLUDE_DIR)/vmlinux.h $(BPFOBJ) $(LIBARENA_BPF_DEPS)
> +$(MAKE) -C libarena libarena_asan.skel.h $(LIBARENA_MAKE_ARGS)
> endif
>
> +# #![no_std] looks for compiler_builtins too. Nothing of it is used.
> +ifneq ($(RUST_CORE),)
> +$(RUST_CORE): $(RUST_CORE_SRC)
> + $(call msg,RUSTC,,$@)
> + $(Q)mkdir -p $(@D)
> + +$(Q)$(RUSTC_BPF) -A warnings --edition 2024 --crate-name core --out-dir $(@D) $<
^^^^^^^^^^^^^^
Can this rule fail on a rustc older than 1.85?
The core crate is always built with a fixed --edition 2024, but the
detection in Makefile.buildvars only checks that rust-src exists and that
clang is version 23 or newer. It never looks at the rustc version:
ifneq ($(RUST_CORE_SRC),)
ifeq ($(shell [ 0$(shell echo __clang_major__ | $(CLANG) ...) -ge 23 ] ...
RUST_CORE := $(BUILD_DIR)/rust/libcore.rlib
A rustc older than 1.85 rejects --edition 2024, either as an unknown edition
or as unstable without -Z unstable-options, which RUSTC_BOOTSTRAP=1 does not
add. For rustc 1.85 and 1.86 the core source that ships with rustc is still
edition 2021 code.
The kernel picks the edition to match the rustc version in rust/Makefile:
core-edition := $(if $(call rustc-min-version,108700),2024,2021)
That came from f4daa80d6be7 ("rust: compile libcore with edition 2024 for
1.87+"), which says the edition must match what libcore expects. So the
commit message statement "core is built from the source that comes with
rustc, like the kernel does it" is not quite accurate for the edition.
> + +$(Q)echo '#![feature(compiler_builtins)] #![compiler_builtins] #![no_std]' | \
> + $(RUSTC_BPF) -A warnings --crate-name compiler_builtins --out-dir $(@D) -
> +endif
> +
[ ... ]
> @@ -457,7 +467,7 @@ RUNNER_PREREQS := $(INCLUDE_DIR)/vmlinux.h $(BPFOBJ) $(BPFTOOL) \
> $(VERIFY_SIG_HDR) $(PRIVATE_KEY) $(VERIFICATION_CERT) \
> $(LIBARENA_SKEL) $(LIBARENA_ASAN_SKEL) \
> prog_tests/tests.h map_tests/tests.h \
> - $(RUNNER_OBJS)
> + $(RUNNER_OBJS) $(RUST_CORE)
^^^^^^^^^^^^
If the libcore rule does fail, doesn't this stop the whole selftests/bpf
build and not just the Rust subtest?
$(RUST_CORE) is a prerequisite of RUNNER_PREREQS, so test_progs,
test_progs-no_alu32, test_progs-cpuv4, test_progs-bpf_gcc and test_maps all
depend on it. The recipe has no $(call skip_on_fail,...), so the build
fails even with BPF_STRICT_BUILD=0.
One setup that hits this is a rustc older than 1.85 with rust-src installed
in the sysroot and clang 23 or newer, for example a pinned rustup toolchain
with "rustup component add rust-src", or distro rustc and rust-src packages
together with an apt.llvm.org or self-built clang. On such a system
selftests/bpf built before this patch and does not build with it, while the
commit message says:
The subtest is skipped when there is no rustc, no source of core
(rustup component add rust-src) or when clang is older than 23.
Would it make sense to gate RUST_CORE on a probe that rustc accepts the
chosen edition, or to pick the core edition by rustc version the way
rust/Makefile does? It may also be worth making a libcore build failure
produce a skip instead of stopping every runner.
[ ... ]
> diff --git a/tools/testing/selftests/bpf/Makefile.skel b/tools/testing/selftests/bpf/Makefile.skel
> index d9ddd1d86f89..df6bbf644a43 100644
> --- a/tools/testing/selftests/bpf/Makefile.skel
> +++ b/tools/testing/selftests/bpf/Makefile.skel
[ ... ]
> @@ -138,4 +138,18 @@ $(LINKED_SKELS_H): $(RDIR)/%.skel.h: $$(addprefix $(RDIR)/,$$($$*.skel.h-deps))
> $(BPFTOOL) $(GEN_SKEL) | $(RDIR)
> $(Q)$(cmd_bpf_link_skel)
>
> +# Programs in Rust, see Makefile.buildvars. No skeletons: the objects may be absent.
> +ifneq ($(RUST_CORE),)
> +ifeq ($(BPF_CC),$(CLANG))
> +RUST_OBJS := $(patsubst progs/%.rs,$(RDIR)/%.bpf.o,$(wildcard progs/*.rs))
> +BPF_OBJS += $(RUST_OBJS)
> +
> +$(RUST_OBJS): $(RDIR)/%.bpf.o: progs/%.rs $(RUST_CORE) | $(RDIR)
> + $(call msg,RUSTC,$(BINARY),$@)
> + +$(Q)$(RUSTC_BPF) --edition 2021 -C debuginfo=2 --emit=llvm-bc -o - \
> + $(patsubst -mcpu=%,-C target-cpu=%,$(filter -mcpu=%,$(BPF_CC_FLAGS))) $< | \
> + $(BPF_CC) $(BPF_CC_FLAGS) -x ir -c - -o $@
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If rustc fails here, does this recipe still succeed?
The object is built as "rustc ... -o - $< | clang ... -x ir -c - -o $@".
The selftests makefiles do not set SHELL or pipefail, so the recipe exit
status is clang's alone. When rustc fails, for example on a compile error
in a progs/*.rs file or with a libcore.rlib built by an incompatible rustc,
it writes nothing to stdout. clang then reads an empty stream, and with
"-x ir" empty input parses as a valid empty LLVM module, so clang writes an
empty BPF object and exits 0.
make then treats data_in_arena_rust.bpf.o as built, and .DELETE_ON_ERROR in
Makefile.runner never triggers. The object is newer than its source, so it
is not rebuilt until the source or libcore changes.
Because the file exists, test_rust() does not skip, and the failure shows up
at run time as btf_fd, test_run and retval assertion failures, far from the
real cause. bpf_program__fd(NULL) returns -EINVAL, so this does not crash
today.
This rule also ignores the BPF_STRICT_BUILD=0 convention that the other BPF
object rules follow through skip_on_fail.
Would writing the bitcode to a temporary file and running the two steps with
&& work better, so that a rustc failure either stops the build or goes
through skip_on_fail?
---
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/36768148430
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 13/14] selftests/bpf: Add test for global data in arena
2026-09-30 19:31 ` [PATCH bpf-next 13/14] selftests/bpf: Add test for global data in arena Alexei Starovoitov
@ 2026-09-30 20:22 ` bot+bpf-ci
0 siblings, 0 replies; 31+ messages in thread
From: bot+bpf-ci @ 2026-09-30 20:22 UTC (permalink / raw)
To: alexei.starovoitov, bpf
Cc: daniel, andrii, eddyz87, memxor, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 1476 bytes --]
> selftests/bpf: Add test for global data in arena
TL;DR: Nit only, in the new selftest: on architectures other than x86_64 and
arm64 it skips silently, without printing a reason. Suggests adding a printf()
before test__skip(), as stream.c does.
> diff --git a/tools/testing/selftests/bpf/prog_tests/data_in_arena.c b/tools/testing/selftests/bpf/prog_tests/data_in_arena.c
> new file mode 100644
> index 000000000000..bccfb0110b94
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/data_in_arena.c
[ ... ]
> +void test_data_in_arena(void)
> +{
> +#if !defined(__x86_64__) && !defined(__aarch64__)
> + /* other JITs don't take BPF_F_ARENA_SCALAR */
> + test__skip();
> + return;
> +#endif
This isn't a bug, but on architectures other than x86_64 and arm64 this
skips all four subtests without printing anything. test__skip() takes no
message, so test_progs only reports the test as skipped and the reason is
visible only in the source comment.
prog_tests/stream.c uses the same arch guard and prints the reason first:
printf("%s:SKIP: arena fault reporting not supported\n", __func__);
test__skip();
return;
Could a similar printf() be added here, for example
"%s:SKIP: JIT does not support BPF_F_ARENA_SCALAR"?
---
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/36768148430
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 11/14] libbpf: Keep global data in arena when the object has .arena.data
2026-09-30 19:31 ` [PATCH bpf-next 11/14] libbpf: Keep global data in arena when the object has .arena.data Alexei Starovoitov
2026-09-30 19:46 ` sashiko-bot
@ 2026-09-30 20:22 ` bot+bpf-ci
1 sibling, 0 replies; 31+ messages in thread
From: bot+bpf-ci @ 2026-09-30 20:22 UTC (permalink / raw)
To: alexei.starovoitov, bpf
Cc: daniel, andrii, eddyz87, memxor, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 4075 bytes --]
> libbpf: Keep global data in arena when the object has .arena.data
TL;DR: In libbpf, an object with __arena variables and .arena.data but no
.maps section may get its .data/.bss/.rodata copies placed over the __arena
variables at arena offset 0, silently aliasing them instead of failing to
load.
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index fdd69aac39bd..335cdafeb06c 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
[ ... ]
> @@ -3081,6 +3119,129 @@ static int init_arena_map_data(struct bpf_object *obj, struct bpf_map *map,
[ ... ]
> +static int bpf_object__init_arena_data(struct bpf_object *obj)
> +{
> + const size_t page_sz = sysconf(_SC_PAGE_SIZE);
> + size_t sz = obj->arena_data_sz;
> + struct bpf_map *map;
> + void *data;
> + int i;
> +
> + if (!obj->data_in_arena)
> + return 0;
> +
> + for (i = 0; i < obj->nr_maps; i++) {
> + map = &obj->maps[i];
> + if (map_data_goes_to_arena(obj, map))
> + sz = map_arena_off(obj, map, sz) + map->def.value_size;
> + }
> + if (sz == obj->arena_data_sz)
> + return 0;
> +
> + if (obj->arena_map_idx < 0) {
> + map = bpf_object__add_map(obj);
> + if (IS_ERR(map))
> + return PTR_ERR(map);
> +
> + map->real_name = strdup("arena");
> + map->name = strdup("arena");
> + if (!map->real_name || !map->name) {
> + zfree(&map->real_name);
> + zfree(&map->name);
> + return -ENOMEM;
> + }
> + map->sec_idx = -1;
> + map->def.type = BPF_MAP_TYPE_ARENA;
> + map->def.max_entries = roundup(sz, page_sz) / page_sz;
> + map->def.map_flags = BPF_F_MMAPABLE;
> + obj->arena_map_idx = map - obj->maps;
> + }
When the object has __arena variables (.addr_space.1) and .arena.data, but
no SEC(".maps") section, doesn't this place the .data/.bss/.rodata copies
at arena offset 0, on top of the __arena variables?
The __arena bookkeeping only happens in bpf_object__init_user_btf_maps(),
and that function returns early when there is no .maps section:
bpf_object__init_user_btf_maps() {
...
if (obj->efile.btf_maps_shndx < 0)
return 0;
...
if (obj->efile.arena_data && obj->arena_map_idx < 0) {
pr_warn("elf: sec '%s': to use global __arena variables the ARENA map should be explicitly declared in SEC(\".maps\")\n",
ARENA_SEC);
return -ENOENT;
}
...
}
So in that case neither init_arena_map_data() runs, which is what copies
.addr_space.1 into obj->arena_data and sets obj->arena_data_sz, nor does
the -ENOENT check above fire.
bpf_object__init_arena_data() then starts with sz = obj->arena_data_sz = 0,
creates the "arena" map, and gives the first data map arena_off 0.
Before this patch, bpf_program__record_reloc() failed such an object with
"no arena maps defined", because arena_map_idx was still -1:
bpf_program__record_reloc() {
...
if (shdr_idx == obj->efile.arena_data_shndx) {
if (obj->arena_map_idx < 0) {
...
return -LIBBPF_ERRNO__RELOC;
}
...
reloc_desc->sym_off = sym->st_value;
...
}
Now arena_map_idx points at the auto-created map, so that relocation
succeeds and resolves to arena_data_off + st_value, which is the same
address range the .data/.bss/.rodata copies now occupy. Data pointers to
__arena variables (targ_sec_idx == -1 in bpf_object__relocate_data_ptrs())
resolve the same way.
For example, an object with:
char d SEC(".arena.data");
int counter = 5;
int __arena avar = 11;
and no maps: the program sees avar and counter at the same arena address.
avar starts out as 5 rather than 11, and a write to one variable changes
the other. Previously this object was rejected at load.
Should bpf_object__init_arena_data() (or bpf_object__init_user_btf_maps()
without the early return) handle obj->efile.arena_data when there is no
.maps section? It could either reserve the __arena variables first or keep
returning the -ENOENT error.
---
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/36768148430
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 05/14] bpf: Allow names of Rust types and functions in BTF
2026-09-30 19:31 ` [PATCH bpf-next 05/14] bpf: Allow names of Rust types and functions in BTF Alexei Starovoitov
2026-09-30 20:22 ` bot+bpf-ci
@ 2026-10-01 17:42 ` Alan Maguire
2026-10-02 12:24 ` Alexei Starovoitov
1 sibling, 1 reply; 31+ messages in thread
From: Alan Maguire @ 2026-10-01 17:42 UTC (permalink / raw)
To: Alexei Starovoitov, bpf; +Cc: daniel, andrii, eddyz87, memxor
On 30/09/2026 20:31, Alexei Starovoitov wrote:
> From: Alexei Starovoitov <ast@kernel.org>
>
> Names of types and functions in BTF that LLVM makes for a Rust program
> are not C identifiers:
>
> [69] STRUCT 'NonNull<str>' size=16 vlen=1
> [147] FUNC 'write_fmt<scx_cosmos::BpfStream>' type_id=146
>
> and the kernel rejects such BTF with "Invalid name". scx_simple and
> scx_cosmos schedulers written in Rust have them in STRUCT, FWD and FUNC,
> made of letters, digits and " #&()*,:;<>[]{}", 430 characters at most.
>
> Allow any printable character in btf_name_valid_identifier(), like it's
> done for DATASEC. It checks names of types, functions, members,
> enumerators, variables and arguments, so all of them can have such
> characters now. The limit of KSYM_NAME_LEN stays.
>
> The name of FUNC is a part of the name of the program in kallsyms, where
> a space would break the parsers. Replace what is not a character of
> an identifier with '_' there.
One idea I've been experimenting with for pahole-generated BTF is to
retain a more friendly rust-centric name, while keeping the kallsyms name
(which is name-mangled) in a structured BTF decl tag pointing at it [1].
The idea is that for tracing rust functions you could specify the rust name
(from DWARF DW_AT_name) in the SEC(), but to map to the actual symbol
address in kallsyms the decl tag that referenced that function could use
the DW_AT_linkage name.
So the decl tag would have the form
<lang>:linkage:<linkage-symbol>
and it would reference the FUNC type id.
Could we do similar here for the name -> symbol mapping?
[1] https://github.com/acmel/dwarves/commit/a14b368960635b30b188a899a1245143df53e70b
>
> Tests in prog_tests/btf.c expect "Invalid name" for names with '!' and
> '*', which are valid now. Put a character that is not printable there.
> The type name '?foo' is expected to load.
>
> Signed-off-by: Alexei Starovoitov <ast@kernel.org>
> ---
> kernel/bpf/btf.c | 15 ++----
> kernel/bpf/core.c | 5 ++
> tools/testing/selftests/bpf/prog_tests/btf.c | 52 ++++++++++----------
> 3 files changed, 33 insertions(+), 39 deletions(-)
>
> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index 8cc17a1cd25c..d27af5d8e495 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c
> @@ -901,16 +901,6 @@ static bool btf_name_offset_valid(const struct btf *btf, u32 offset)
> return offset < btf->hdr.str_len;
> }
>
> -static bool __btf_name_char_ok(char c, bool first)
> -{
> - if ((first ? !isalpha(c) :
> - !isalnum(c)) &&
> - c != '_' &&
> - c != '.')
> - return false;
> - return true;
> -}
> -
> const char *btf_str_by_offset(const struct btf *btf, u32 offset)
> {
> while (offset < btf->start_str_off)
> @@ -923,20 +913,21 @@ const char *btf_str_by_offset(const struct btf *btf, u32 offset)
> return NULL;
> }
>
> +/* Names in BTF of Rust are not C identifiers. Allow any printable character */
> static bool btf_name_valid_identifier(const struct btf *btf, u32 offset)
> {
> /* offset must be valid */
> const char *src = btf_str_by_offset(btf, offset);
> const char *src_limit;
>
> - if (!__btf_name_char_ok(*src, true))
> + if (!isprint(*src))
> return false;
>
> /* set a limit on identifier length */
> src_limit = src + KSYM_NAME_LEN;
> src++;
> while (*src && src < src_limit) {
> - if (!__btf_name_char_ok(*src, false))
> + if (!isprint(*src))
> return false;
> src++;
> }
> diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
> index a1721f9c0f52..36900b02d668 100644
> --- a/kernel/bpf/core.c
> +++ b/kernel/bpf/core.c
> @@ -18,6 +18,7 @@
> */
>
> #include <uapi/linux/btf.h>
> +#include <linux/ctype.h>
> #include <linux/filter.h>
> #include <linux/sched/signal.h>
> #include <linux/skbuff.h>
> @@ -589,6 +590,10 @@ bpf_prog_ksym_set_name(struct bpf_prog *prog)
> prog->aux->func_info[prog->aux->func_idx].type_id);
> func_name = btf_name_by_offset(prog->aux->btf, type->name_off);
> snprintf(sym, (size_t)(end - sym), "_%s", func_name);
> + /* the name of a function of Rust is not an identifier */
> + for (; *sym; sym++)
> + if (!isalnum(*sym) && *sym != '_' && *sym != '.')
> + *sym = '_';
> return;
> }
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/btf.c b/tools/testing/selftests/bpf/prog_tests/btf.c
> index df6ad38d287d..87b554067071 100644
> --- a/tools/testing/selftests/bpf/prog_tests/btf.c
> +++ b/tools/testing/selftests/bpf/prog_tests/btf.c
> @@ -1987,14 +1987,14 @@ static struct btf_raw_test raw_tests[] = {
> },
>
> {
> - .descr = "typedef (invalid name, invalid identifier)",
> + .descr = "typedef (invalid name, not printable)",
> .raw_types = {
> BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
> BTF_TYPEDEF_ENC(NAME_TBD, 1), /* [2] */
> BTF_END_RAW,
> },
> - .str_sec = "\0__!int",
> - .str_sec_size = sizeof("\0__!int"),
> + .str_sec = "\0__\7int",
> + .str_sec_size = sizeof("\0__\7int"),
> .map_type = BPF_MAP_TYPE_ARRAY,
> .map_name = "typedef_check_btf",
> .key_size = sizeof(int),
> @@ -2112,15 +2112,15 @@ static struct btf_raw_test raw_tests[] = {
> },
>
> {
> - .descr = "fwd type (invalid name, invalid identifier)",
> + .descr = "fwd type (invalid name, not printable)",
> .raw_types = {
> BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
> BTF_TYPE_ENC(NAME_TBD,
> BTF_INFO_ENC(BTF_KIND_FWD, 0, 0), 0), /* [2] */
> BTF_END_RAW,
> },
> - .str_sec = "\0__!skb",
> - .str_sec_size = sizeof("\0__!skb"),
> + .str_sec = "\0__\7skb",
> + .str_sec_size = sizeof("\0__\7skb"),
> .map_type = BPF_MAP_TYPE_ARRAY,
> .map_name = "fwd_type_check_btf",
> .key_size = sizeof(int),
> @@ -2175,7 +2175,7 @@ static struct btf_raw_test raw_tests[] = {
> },
>
> {
> - .descr = "struct type (invalid name, invalid identifier)",
> + .descr = "struct type (invalid name, not printable)",
> .raw_types = {
> BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
> BTF_TYPE_ENC(NAME_TBD,
> @@ -2183,8 +2183,8 @@ static struct btf_raw_test raw_tests[] = {
> BTF_MEMBER_ENC(NAME_TBD, 1, 0),
> BTF_END_RAW,
> },
> - .str_sec = "\0A!\0B",
> - .str_sec_size = sizeof("\0A!\0B"),
> + .str_sec = "\0A\7\0B",
> + .str_sec_size = sizeof("\0A\7\0B"),
> .map_type = BPF_MAP_TYPE_ARRAY,
> .map_name = "struct_type_check_btf",
> .key_size = sizeof(int),
> @@ -2217,7 +2217,7 @@ static struct btf_raw_test raw_tests[] = {
> },
>
> {
> - .descr = "struct member (invalid name, invalid identifier)",
> + .descr = "struct member (invalid name, not printable)",
> .raw_types = {
> BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
> BTF_TYPE_ENC(NAME_TBD,
> @@ -2225,8 +2225,8 @@ static struct btf_raw_test raw_tests[] = {
> BTF_MEMBER_ENC(NAME_TBD, 1, 0),
> BTF_END_RAW,
> },
> - .str_sec = "\0A\0B*",
> - .str_sec_size = sizeof("\0A\0B*"),
> + .str_sec = "\0A\0B\7",
> + .str_sec_size = sizeof("\0A\0B\7"),
> .map_type = BPF_MAP_TYPE_ARRAY,
> .map_name = "struct_type_check_btf",
> .key_size = sizeof(int),
> @@ -2260,7 +2260,7 @@ static struct btf_raw_test raw_tests[] = {
> },
>
> {
> - .descr = "enum type (invalid name, invalid identifier)",
> + .descr = "enum type (invalid name, not printable)",
> .raw_types = {
> BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
> BTF_TYPE_ENC(NAME_TBD,
> @@ -2269,8 +2269,8 @@ static struct btf_raw_test raw_tests[] = {
> BTF_ENUM_ENC(NAME_TBD, 0),
> BTF_END_RAW,
> },
> - .str_sec = "\0A!\0B",
> - .str_sec_size = sizeof("\0A!\0B"),
> + .str_sec = "\0A\7\0B",
> + .str_sec_size = sizeof("\0A\7\0B"),
> .map_type = BPF_MAP_TYPE_ARRAY,
> .map_name = "enum_type_check_btf",
> .key_size = sizeof(int),
> @@ -2306,7 +2306,7 @@ static struct btf_raw_test raw_tests[] = {
> },
>
> {
> - .descr = "enum member (invalid name, invalid identifier)",
> + .descr = "enum member (invalid name, not printable)",
> .raw_types = {
> BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
> BTF_TYPE_ENC(0,
> @@ -2315,8 +2315,8 @@ static struct btf_raw_test raw_tests[] = {
> BTF_ENUM_ENC(NAME_TBD, 0),
> BTF_END_RAW,
> },
> - .str_sec = "\0A!",
> - .str_sec_size = sizeof("\0A!"),
> + .str_sec = "\0A\7",
> + .str_sec_size = sizeof("\0A\7"),
> .map_type = BPF_MAP_TYPE_ARRAY,
> .map_name = "enum_type_check_btf",
> .key_size = sizeof(int),
> @@ -2625,14 +2625,14 @@ static struct btf_raw_test raw_tests[] = {
> .raw_types = {
> BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
> BTF_TYPE_INT_ENC(0, 0, 0, 32, 4), /* [2] */
> - /* void (*)(int a, unsigned int !!!) */
> + /* void (*)(int a, unsigned int \7) */
> BTF_FUNC_PROTO_ENC(0, 2), /* [3] */
> BTF_FUNC_PROTO_ARG_ENC(NAME_TBD, 1),
> BTF_FUNC_PROTO_ARG_ENC(NAME_TBD, 2),
> BTF_END_RAW,
> },
> - .str_sec = "\0a\0!!!",
> - .str_sec_size = sizeof("\0a\0!!!"),
> + .str_sec = "\0a\0\7",
> + .str_sec_size = sizeof("\0a\0\7"),
> .map_type = BPF_MAP_TYPE_ARRAY,
> .map_name = "func_proto_type_check_btf",
> .key_size = sizeof(int),
> @@ -2775,12 +2775,12 @@ static struct btf_raw_test raw_tests[] = {
> BTF_FUNC_PROTO_ENC(0, 2), /* [3] */
> BTF_FUNC_PROTO_ARG_ENC(NAME_TBD, 1),
> BTF_FUNC_PROTO_ARG_ENC(NAME_TBD, 2),
> - /* void !!!(int a, unsigned int b) */
> + /* void \7(int a, unsigned int b) */
> BTF_FUNC_ENC(NAME_TBD, 3), /* [4] */
> BTF_END_RAW,
> },
> - .str_sec = "\0a\0b\0!!!",
> - .str_sec_size = sizeof("\0a\0b\0!!!"),
> + .str_sec = "\0a\0b\0\7",
> + .str_sec_size = sizeof("\0a\0b\0\7"),
> .map_type = BPF_MAP_TYPE_ARRAY,
> .map_name = "func_type_check_btf",
> .key_size = sizeof(int),
> @@ -3585,15 +3585,13 @@ static struct btf_raw_test raw_tests[] = {
> .btf_load_err = true,
> },
> {
> - .descr = "type name '?foo' is not ok",
> + .descr = "type name '?foo' is ok",
> .raw_types = {
> /* union ?foo; */
> BTF_TYPE_ENC(1, BTF_INFO_ENC(BTF_KIND_FWD, 1, 0), 0), /* [1] */
> BTF_END_RAW,
> },
> BTF_STR_SEC("\0?foo"),
> - .err_str = "Invalid name",
> - .btf_load_err = true,
> },
>
> {
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 07/14] bpf: Allow arguments without names in static functions in BTF
2026-09-30 19:31 ` [PATCH bpf-next 07/14] bpf: Allow arguments without names in static " Alexei Starovoitov
@ 2026-10-01 21:14 ` Alan Maguire
0 siblings, 0 replies; 31+ messages in thread
From: Alan Maguire @ 2026-10-01 21:14 UTC (permalink / raw)
To: Alexei Starovoitov, bpf; +Cc: daniel, andrii, eddyz87, memxor
On 30/09/2026 20:31, Alexei Starovoitov wrote:
> From: Alexei Starovoitov <ast@kernel.org>
>
> Some static functions in BTF of a Rust program have arguments without
> names:
>
> [71] FUNC_PROTO '(anon)' ret_type_id=0 vlen=1
> '(anon)' type_id=72
> [81] FUNC 'unwrap_failed' type_id=71 linkage=static
>
> and the kernel rejects such BTF with "Invalid arg#1". It's 5 of 25
> static functions in scx_cosmos and 6 of 24 in scx_simple.
>
> The verifier looks at types of the arguments. The names are printed only,
> as "(anon)" when there is none. Allow such static FUNC. Global functions
> are checked as before.
>
> The test "func (Some arg has no name)" in prog_tests/btf.c has a static
> function. Make it global to keep the check.
>
> Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Acked-by: Alan Maguire <alan.maguire@oracle.com>
> ---
> kernel/bpf/btf.c | 4 ++++
> tools/testing/selftests/bpf/prog_tests/btf.c | 5 +++--
> 2 files changed, 7 insertions(+), 2 deletions(-)
>
> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index d27af5d8e495..c9d4b709380c 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c
> @@ -5752,6 +5752,10 @@ static int btf_func_check(struct btf_verifier_env *env,
> return -EINVAL;
> }
>
> + /* Rust leaves out names of some arguments of static functions */
> + if (btf_func_linkage(t) == BTF_FUNC_STATIC)
> + return 0;
> +
> args = (const struct btf_param *)(proto_type + 1);
> nr_args = btf_type_vlen(proto_type);
> for (i = 0; i < nr_args; i++) {
> diff --git a/tools/testing/selftests/bpf/prog_tests/btf.c b/tools/testing/selftests/bpf/prog_tests/btf.c
> index 207341f4bd99..21fdeeb23405 100644
> --- a/tools/testing/selftests/bpf/prog_tests/btf.c
> +++ b/tools/testing/selftests/bpf/prog_tests/btf.c
> @@ -2793,7 +2793,7 @@ static struct btf_raw_test raw_tests[] = {
> },
>
> {
> - .descr = "func (Some arg has no name)",
> + .descr = "func (Some arg of global func has no name)",
> .raw_types = {
> BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
> BTF_TYPE_INT_ENC(0, 0, 0, 32, 4), /* [2] */
> @@ -2802,7 +2802,8 @@ static struct btf_raw_test raw_tests[] = {
> BTF_FUNC_PROTO_ARG_ENC(NAME_TBD, 1),
> BTF_FUNC_PROTO_ARG_ENC(0, 2),
> /* void func(int a, unsigned int) */
> - BTF_FUNC_ENC(NAME_TBD, 3), /* [4] */
> + BTF_TYPE_ENC(NAME_TBD, /* [4] */
> + BTF_INFO_ENC(BTF_KIND_FUNC, 0, BTF_FUNC_GLOBAL), 3),
> BTF_END_RAW,
> },
> .str_sec = "\0a\0func",
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 08/14] selftests/bpf: Add test for arguments without names in static functions
2026-09-30 19:31 ` [PATCH bpf-next 08/14] selftests/bpf: Add test for arguments without names in static functions Alexei Starovoitov
@ 2026-10-01 21:22 ` Alan Maguire
0 siblings, 0 replies; 31+ messages in thread
From: Alan Maguire @ 2026-10-01 21:22 UTC (permalink / raw)
To: Alexei Starovoitov, bpf; +Cc: daniel, andrii, eddyz87, memxor
On 30/09/2026 20:31, Alexei Starovoitov wrote:
> From: Alexei Starovoitov <ast@kernel.org>
>
> Check that BTF with a static function that has an argument without a name
> is loaded.
>
> Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Nit: a few extra comments for readability would be no harm (see below), and
it might be useful to add a global function with a vararg parameter (signalled
by a proto parameter with type id 0), since that allows an empty name too.
Acked-by: Alan Maguire <alan.maguire@oracle.com>
> ---
> tools/testing/selftests/bpf/prog_tests/btf.c | 24 ++++++++++++++++++++
> 1 file changed, 24 insertions(+)
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/btf.c b/tools/testing/selftests/bpf/prog_tests/btf.c
> index 21fdeeb23405..751cb14c1949 100644
> --- a/tools/testing/selftests/bpf/prog_tests/btf.c
> +++ b/tools/testing/selftests/bpf/prog_tests/btf.c
> @@ -2819,6 +2819,30 @@ static struct btf_raw_test raw_tests[] = {
> .err_str = "Invalid arg#2",
> },
>
> +{
> + .descr = "func (Some arg of static func has no name)",
> + .raw_types = {
/* int */
> + BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
/* unsigned int */
> + BTF_TYPE_INT_ENC(0, 0, 0, 32, 4), /* [2] */
> + /* void (*)(int a, unsigned int) */
> + BTF_FUNC_PROTO_ENC(0, 2), /* [3] */
> + BTF_FUNC_PROTO_ARG_ENC(NAME_TBD, 1),
> + BTF_FUNC_PROTO_ARG_ENC(0, 2),
> + /* static void func(int a, unsigned int) */
> + BTF_FUNC_ENC(NAME_TBD, 3), /* [4] */
> + BTF_END_RAW,
> + },
> + .str_sec = "\0a\0func",
> + .str_sec_size = sizeof("\0a\0func"),
> + .map_type = BPF_MAP_TYPE_ARRAY,
> + .map_name = "func_type_check_btf",
> + .key_size = sizeof(int),
> + .value_size = sizeof(int),
> + .key_type_id = 1,
> + .value_type_id = 1,
> + .max_entries = 4,
> +},
> +
> {
> .descr = "func (Non zero vlen)",
> .raw_types = {
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 05/14] bpf: Allow names of Rust types and functions in BTF
2026-10-01 17:42 ` Alan Maguire
@ 2026-10-02 12:24 ` Alexei Starovoitov
2026-10-02 13:38 ` Alan Maguire
0 siblings, 1 reply; 31+ messages in thread
From: Alexei Starovoitov @ 2026-10-02 12:24 UTC (permalink / raw)
To: Alan Maguire, bpf; +Cc: daniel, andrii, eddyz87, memxor
On Thu, Oct 01, 2026 at 06:42 PM Alan Maguire <alan.maguire@oracle.com> wrote:
> The idea is that for tracing rust functions you could specify the rust name
> (from DWARF DW_AT_name) in the SEC(), but to map to the actual symbol
> address in kallsyms the decl tag that referenced that function could use
> the DW_AT_linkage name.
>
> So the decl tag would have the form
>
> <lang>:linkage:<linkage-symbol>
>
> and it would reference the FUNC type id.
>
> Could we do similar here for the name -> symbol mapping?
I don't think I got the idea.
To help with mangle/demangle?
bpf_prog_<tag>_write_fmt_scx_cosmos__BpfStream_
is imo readable enough for humans.
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 05/14] bpf: Allow names of Rust types and functions in BTF
2026-10-02 12:24 ` Alexei Starovoitov
@ 2026-10-02 13:38 ` Alan Maguire
0 siblings, 0 replies; 31+ messages in thread
From: Alan Maguire @ 2026-10-02 13:38 UTC (permalink / raw)
To: Alexei Starovoitov, bpf; +Cc: daniel, andrii, eddyz87, memxor
On 02/10/2026 13:24, Alexei Starovoitov wrote:
> On Thu, Oct 01, 2026 at 06:42 PM Alan Maguire <alan.maguire@oracle.com> wrote:
>> The idea is that for tracing rust functions you could specify the rust name
>> (from DWARF DW_AT_name) in the SEC(), but to map to the actual symbol
>> address in kallsyms the decl tag that referenced that function could use
>> the DW_AT_linkage name.
>>
>> So the decl tag would have the form
>>
>> <lang>:linkage:<linkage-symbol>
>>
>> and it would reference the FUNC type id.
>>
>> Could we do similar here for the name -> symbol mapping?
>
> I don't think I got the idea.
> To help with mangle/demangle?
> bpf_prog_<tag>_write_fmt_scx_cosmos__BpfStream_
> is imo readable enough for humans.
>
Might be more relevant for Rust kernel modules ; in that case a
function like 'fmt<str>' winds up as
ffffffffc0fec430 t _RNvXs5_NtCs1DErgtJhccC_6kernel3fmtReNtB5_7Display3fmtCs7y3Drb9gYub_15rust_print_main [rust_print]
in kallsyms due to name mangling. So if we want to trace it, we'd rather be
able to do something like
SEC("fentry/rust_print/fmt<str>")
Then libbpf or other tracer can get the kallsyms name from the decl tag
for attachment.
^ permalink raw reply [flat|nested] 31+ messages in thread
end of thread, other threads:[~2026-10-02 13:38 UTC | newest]
Thread overview: 31+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-30 19:31 [PATCH bpf-next 00/14] bpf: Support programs compiled by rust-bpf Alexei Starovoitov
2026-09-30 19:31 ` [PATCH bpf-next 01/14] bpf: Allow bitwise ops, shifts and mul/div on pointers with CAP_PERFMON Alexei Starovoitov
2026-09-30 19:31 ` [PATCH bpf-next 02/14] selftests/bpf: Add tests for ALU " Alexei Starovoitov
2026-09-30 19:31 ` [PATCH bpf-next 03/14] bpf: Treat load and store through a number as arena access Alexei Starovoitov
2026-09-30 19:31 ` [PATCH bpf-next 04/14] selftests/bpf: Add tests for arena access through numbers Alexei Starovoitov
2026-09-30 19:48 ` sashiko-bot
2026-09-30 20:22 ` bot+bpf-ci
2026-09-30 19:31 ` [PATCH bpf-next 05/14] bpf: Allow names of Rust types and functions in BTF Alexei Starovoitov
2026-09-30 20:22 ` bot+bpf-ci
2026-10-01 17:42 ` Alan Maguire
2026-10-02 12:24 ` Alexei Starovoitov
2026-10-02 13:38 ` Alan Maguire
2026-09-30 19:31 ` [PATCH bpf-next 06/14] selftests/bpf: Add tests for " Alexei Starovoitov
2026-09-30 20:22 ` bot+bpf-ci
2026-09-30 19:31 ` [PATCH bpf-next 07/14] bpf: Allow arguments without names in static " Alexei Starovoitov
2026-10-01 21:14 ` Alan Maguire
2026-09-30 19:31 ` [PATCH bpf-next 08/14] selftests/bpf: Add test for arguments without names in static functions Alexei Starovoitov
2026-10-01 21:22 ` Alan Maguire
2026-09-30 19:31 ` [PATCH bpf-next 09/14] bpf: Allow a variable in DATASEC that is smaller than its type Alexei Starovoitov
2026-09-30 19:47 ` sashiko-bot
2026-09-30 19:31 ` [PATCH bpf-next 10/14] selftests/bpf: Add tests for a variable " Alexei Starovoitov
2026-09-30 19:31 ` [PATCH bpf-next 11/14] libbpf: Keep global data in arena when the object has .arena.data Alexei Starovoitov
2026-09-30 19:46 ` sashiko-bot
2026-09-30 20:22 ` bot+bpf-ci
2026-09-30 19:31 ` [PATCH bpf-next 12/14] libbpf: Keep format strings of bpf_printk() in .rodata.str Alexei Starovoitov
2026-09-30 20:22 ` bot+bpf-ci
2026-09-30 19:31 ` [PATCH bpf-next 13/14] selftests/bpf: Add test for global data in arena Alexei Starovoitov
2026-09-30 20:22 ` bot+bpf-ci
2026-09-30 19:31 ` [PATCH bpf-next 14/14] selftests/bpf: Add test for global data of a program in Rust Alexei Starovoitov
2026-09-30 19:53 ` sashiko-bot
2026-09-30 20:22 ` 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