selftests/bpf: Test kfuncs returning arena pointers by value

Cover the by-value struct returns a kfunc may now make: two arena
pointers filling R0:R2, and an arena pointer beside a scalar. Two further
cases drop the tag from one member of a struct and one arm of a union,
and stay rejected naming that member, so what decides is the tag rather
than the member being a pointer. The existing cases for a struct and a
nested struct carrying a plain pointer stay rejected as well.

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

Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
Link: https://lore.kernel.org/r/20260829061610.1699950-1-yonghong.song@linux.dev
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Yonghong Song 2026-08-28 23:16:10 -07:00 committed by Alexei Starovoitov
parent 18bc618388
commit f7213961e8
4 changed files with 225 additions and 0 deletions

View File

@ -1,11 +1,53 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
#include <test_progs.h>
#include <bpf/btf.h>
#include "aggregate_ret_func.skel.h"
#include "aggregate_ret_kfunc.skel.h"
#include "aggregate_ret_kfunc_arena.skel.h"
static bool testmod_has_arena_tagged_member(void)
{
struct btf *vmlinux_btf, *module_btf = NULL;
const struct btf_type *t;
bool tagged = false;
__s32 id;
vmlinux_btf = btf__load_vmlinux_btf();
if (!vmlinux_btf)
return false;
module_btf = btf__load_module_btf("bpf_testmod", vmlinux_btf);
if (!module_btf)
goto out;
/* prog_test_ret_arena::a is 'void __arena_tag *': PTR -> TYPE_TAG -> void */
id = btf__find_by_name_kind(module_btf, "prog_test_ret_arena", BTF_KIND_STRUCT);
if (id <= 0)
goto out;
t = btf__type_by_id(module_btf, btf_members(btf__type_by_id(module_btf, id))[0].type);
if (!t || !btf_is_ptr(t))
goto out;
t = btf__type_by_id(module_btf, t->type);
tagged = t && btf_is_type_tag(t) &&
!strcmp(btf__name_by_offset(module_btf, t->name_off), "arena");
out:
btf__free(module_btf);
btf__free(vmlinux_btf);
return tagged;
}
void test_aggregate_ret(void)
{
RUN_TESTS(aggregate_ret_func);
RUN_TESTS(aggregate_ret_kfunc);
if (testmod_has_arena_tagged_member())
RUN_TESTS(aggregate_ret_kfunc_arena);
else
test__skip();
}

View File

@ -0,0 +1,121 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf_arena_common.h>
#include "bpf_misc.h"
#include "../test_kmods/bpf_testmod_kfunc.h"
#if defined(__clang_major__) && __clang_major__ >= 23
struct {
__uint(type, BPF_MAP_TYPE_ARENA);
__uint(map_flags, BPF_F_MMAPABLE);
__uint(max_entries, 2);
} arena SEC(".maps");
SEC("syscall")
__arch_x86_64 __arch_arm64
__load_if_JITed()
__success __retval(0)
int aggregate_ret_kfunc_arena(void *ctx)
{
u32 volatile __arena *page = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
u32 volatile __arena *a, *b;
struct prog_test_ret_arena r;
if (!page)
return 1;
/* Both halves come back in R0:R2, pointing at page and page + 4. */
r = bpf_kfunc_call_test_ret_arena((u64)page);
if (!r.a || !r.b)
return 2;
a = (u32 __arena *)r.a;
b = (u32 __arena *)r.b;
*a = 1;
*b = 2;
if (*a != 1)
return 3;
if (*b != 2)
return 4;
page[0] = 7;
if (*a != 7)
return 5;
page[1] = 9;
if (*b != 9)
return 6;
return 0;
}
SEC("syscall")
__arch_x86_64 __arch_arm64
__load_if_JITed()
__success __retval(0)
int aggregate_ret_kfunc_arena_mixed(void *ctx)
{
u32 __arena *page = bpf_arena_alloc_pages(&arena, NULL, 1, NUMA_NO_NODE, 0);
struct prog_test_ret_arena_mixed r;
u32 volatile __arena *p;
if (!page)
return 1;
/* An arena pointer in R0 beside a scalar in R2. */
r = bpf_kfunc_call_test_ret_arena_mixed((u64)page);
if (!r.p)
return 2;
if (r.tag != 0xbeef)
return 3;
p = (u32 __arena *)r.p;
*p = 3;
if (*p != 3)
return 4;
return 0;
}
SEC("syscall")
__arch_x86_64 __arch_arm64
__failure __msg("is not composed of scalars or arena pointers")
__msg("member 'b' has type PTR")
int aggregate_ret_kfunc_arena_untagged_fail(void *ctx)
{
struct prog_test_ret_arena_untagged r;
r = bpf_kfunc_call_test_ret_arena_untagged(0);
return r.a == r.b;
}
SEC("syscall")
__arch_x86_64 __arch_arm64
__failure __msg("is not composed of scalars or arena pointers")
__msg("member 'b' has type PTR")
int aggregate_ret_kfunc_arena_union_fail(void *ctx)
{
union prog_test_ret_arena_union r;
r = bpf_kfunc_call_test_ret_arena_union(0);
return r.a == r.b;
}
#else
SEC("socket")
__description("aggregate_ret_kfunc_arena: needs LLVM 23, dummy test")
__skip("needs LLVM 23")
__success
int dummy_test(void)
{
return 0;
}
#endif
char _license[] SEC("license") = "GPL";

View File

@ -981,6 +981,34 @@ __bpf_kfunc struct prog_test_ret_ptr bpf_kfunc_call_test_ret_ptr(u64 tag)
return r;
}
__bpf_kfunc struct prog_test_ret_arena bpf_kfunc_call_test_ret_arena(u64 addr)
{
struct prog_test_ret_arena r = { .a = (void *)addr, .b = (void *)(addr + 4) };
return r;
}
__bpf_kfunc struct prog_test_ret_arena_mixed bpf_kfunc_call_test_ret_arena_mixed(u64 addr)
{
struct prog_test_ret_arena_mixed r = { .p = (void *)addr, .tag = 0xbeef };
return r;
}
__bpf_kfunc struct prog_test_ret_arena_untagged bpf_kfunc_call_test_ret_arena_untagged(u64 addr)
{
struct prog_test_ret_arena_untagged r = { .a = (void *)addr, .b = NULL };
return r;
}
__bpf_kfunc union prog_test_ret_arena_union bpf_kfunc_call_test_ret_arena_union(u64 addr)
{
union prog_test_ret_arena_union r = { .a = (void *)addr };
return r;
}
__bpf_kfunc struct prog_test_ret_nested bpf_kfunc_call_test_ret_nested(u64 tag)
{
struct prog_test_ret_nested r = { .in = { .p = NULL }, .tag = tag };
@ -1574,6 +1602,10 @@ BTF_ID_FLAGS(func, bpf_kfunc_call_test_i128)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_pair)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_fastcall, KF_FASTCALL)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_ptr)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_arena)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_arena_mixed)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_arena_untagged)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_arena_union)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_nested)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_ptr_arr)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_ret_arr_struct)

View File

@ -26,6 +26,12 @@ struct prog_test_ref_kfunc {
};
#endif
#if __has_attribute(btf_type_tag)
#define __arena_tag __attribute__((btf_type_tag("arena")))
#else
#define __arena_tag
#endif
struct bpf_iter_testmod_seq;
struct prog_test_pass1 {
@ -70,6 +76,26 @@ struct prog_test_ret_ptr { /* 16 bytes: contains a pointer */
__u64 tag;
};
struct prog_test_ret_arena { /* 16 bytes: two arena pointers */
void __arena_tag *a;
void __arena_tag *b;
};
struct prog_test_ret_arena_mixed { /* 16 bytes: an arena pointer and a scalar */
void __arena_tag *p;
__u64 tag;
};
struct prog_test_ret_arena_untagged { /* 16 bytes: 'b' lacks the arena tag */
void __arena_tag *a;
void *b;
};
union prog_test_ret_arena_union { /* 8 bytes: 'b' lacks the arena tag */
void __arena_tag *a;
void *b;
};
struct prog_test_ret_nested { /* 16 bytes: the pointer hides one level down */
struct {
void *p;
@ -200,6 +226,10 @@ struct prog_test_ret_nested bpf_kfunc_call_test_ret_nested(__u64 tag) __ksym;
struct prog_test_ret_ptr_arr bpf_kfunc_call_test_ret_ptr_arr(void) __ksym;
struct prog_test_ret_arr_struct bpf_kfunc_call_test_ret_arr_struct(void) __ksym;
struct prog_test_ret_arr2d bpf_kfunc_call_test_ret_arr2d(void) __ksym;
struct prog_test_ret_arena bpf_kfunc_call_test_ret_arena(__u64 addr) __ksym;
struct prog_test_ret_arena_mixed bpf_kfunc_call_test_ret_arena_mixed(__u64 addr) __ksym;
struct prog_test_ret_arena_untagged bpf_kfunc_call_test_ret_arena_untagged(__u64 addr) __ksym;
union prog_test_ret_arena_union bpf_kfunc_call_test_ret_arena_union(__u64 addr) __ksym;
struct prog_test_ret_deep bpf_kfunc_call_test_ret_deep(__u64 v) __ksym;
struct prog_test_ret_big bpf_kfunc_call_test_ret_big(void) __ksym;
__u64 bpf_kfunc_call_stack_arg(__u64 a, __u64 b, __u64 c, __u64 d,