Re: [PATCH bpf-next v4 12/13] selftests/bpf: Add tests for callbacks returning more than 8 bytes

Yonghong Song <[email protected]>
Newsgroups org.kernel.vger.bpf
Message-ID <[email protected]>

On 8/10/26 6:24 PM, [email protected] wrote:
>> selftests/bpf: Add tests for callbacks returning more than 8 bytes
>>
>> Add two __failure tests covering the callback return-size checks:
>>
>>   - timer_ret_pair_fail: a bpf_timer callback declared to return more than
>>     8 bytes, rejected by check_ld_imm() where the callback's PTR_TO_FUNC is
>>     created, with "callback function with >8-byte return value is not
>>     supported".
>>
>>   - exceptions_ret_pair_fail: an exception callback declared to return more
>>     than 8 bytes, rejected by do_check_common() when the callback
>>     subprogram is verified, with "exception cb cannot return value larger
>>     than 8 bytes".
>>
>> Both callback bodies are written in inline asm so that the tests do not
>> depend on LLVM 23 R0:R2 codegen and run on any compiler. The verifier reads
>> the return type from BTF rather than from the instructions, so the >8 byte
>> return prototype is supplied through __btf_func_path(), pointing at a
>> companion btf__*.c program that exists only to carry that BTF.
> Does __btf_func_path() actually supply the return type? Looking at
> fixup_btf_from_path() in tools/testing/selftests/bpf/test_loader.c, it
> appears to keep the prog's return type and parameter types while
> substituting only parameter names:
>
>    ret_type_id = fp->type;                    /* fp is the prog FUNC_PROTO */
>    for (j = 0; j < vlen; j++)
>        prog_param_types[j] = params[j].type;  /* prog param types */
>    new_proto_id = btf__add_func_proto(prog_btf, ret_type_id);
>    ...
>    pname = btf__name_by_offset(custom_btf, custom_params[j].name_off);
>    err = btf__add_func_param(prog_btf, pname ?: "", prog_param_types[j]);
>
> The comment at the call site says "Fix up __naked subprog BTF using a
> separate file with named params", and the >8 byte return type already
> comes from each test object's own BTF: exceptions_ret_pair_fail.c
> declares 'unsigned __int128 exception_cb_bad_ret_type3(u64 cookie)' and
> timer_ret_pair_fail.c declares 'static unsigned __int128
> timer_cb_ret_pair(...)'.
>
> The companion btf__*.c objects supply parameter names, which are needed
> because clang emits anonymous parameters for __naked functions and
> btf_func_check() in kernel/bpf/btf.c rejects a BTF_KIND_FUNC whose
> FUNC_PROTO has a typed-but-unnamed argument, which would make the whole
> object's BTF fail to load.
>
> Could the explanation be more precise about what __btf_func_path()
> actually provides?

__btf_func_path() provides precise function BTF which won't be
available for inline asm code.

>
>> Signed-off-by: Yonghong Song <[email protected]>
>> diff --git a/tools/testing/selftests/bpf/prog_tests/exceptions.c b/tools/testing/selftests/bpf/prog_tests/exceptions.c
>> index 3588d6f97fd4e..71d00c568d802 100644
>> --- a/tools/testing/selftests/bpf/prog_tests/exceptions.c
>> +++ b/tools/testing/selftests/bpf/prog_tests/exceptions.c
>> @@ -5,6 +5,7 @@
>>   #include "exceptions.skel.h"
>>   #include "exceptions_ext.skel.h"
>>   #include "exceptions_fail.skel.h"
>> +#include "exceptions_ret_pair_fail.skel.h"
>>   #include "exceptions_assert.skel.h"
>>
>>   static char log_buf[1024 * 1024];
>> @@ -12,6 +13,7 @@ static char log_buf[1024 * 1024];
>>   static void test_exceptions_failure(void)
>>   {
>>   	RUN_TESTS(exceptions_fail);
>> +	RUN_TESTS(exceptions_ret_pair_fail);
>>   }
>>
>>   static void test_exceptions_success(void)
>> diff --git a/tools/testing/selftests/bpf/prog_tests/timer.c b/tools/testing/selftests/bpf/prog_tests/timer.c
>> index 09ff21e1ad2f0..593e56d8964ea 100644
>> --- a/tools/testing/selftests/bpf/prog_tests/timer.c
>> +++ b/tools/testing/selftests/bpf/prog_tests/timer.c
>> @@ -6,6 +6,7 @@
>>   #include <sys/syscall.h>
>>   #include "timer.skel.h"
>>   #include "timer_failure.skel.h"
>> +#include "timer_ret_pair_fail.skel.h"
>>   #include "timer_interrupt.skel.h"
>>
>>   #define NUM_THR 8
>> @@ -285,6 +286,7 @@ void serial_test_timer(void)
>>   	test_timer(timer);
>>
>>   	RUN_TESTS(timer_failure);
>> +	RUN_TESTS(timer_ret_pair_fail);
>>   }
>>
>>   void serial_test_timer_stress(void)
> [ ... ]
>
>> diff --git a/tools/testing/selftests/bpf/progs/exceptions_ret_pair_fail.c b/tools/testing/selftests/bpf/progs/exceptions_ret_pair_fail.c
>> new file mode 100644
>> index 0000000000000..842f86ad8659e
>> --- /dev/null
>> +++ b/tools/testing/selftests/bpf/progs/exceptions_ret_pair_fail.c
>> @@ -0,0 +1,30 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
>> +#include <vmlinux.h>
>> +#include <bpf/bpf_tracing.h>
>> +#include <bpf/bpf_helpers.h>
>> +
>> +#include "bpf_misc.h"
>> +#include "bpf_experimental.h"
>> +
>> +__naked __noinline __used
>> +unsigned __int128 exception_cb_bad_ret_type3(u64 cookie)
>> +{
>> +	asm volatile (
>> +	"r0 = r1;"
>> +	"r2 = 0;"
>> +	"exit;"
>> +	::: __clobber_all);
>> +}
>> +
>> +SEC("?tc")
>> +__exception_cb(exception_cb_bad_ret_type3)
>> +__failure __msg("exception cb cannot return value larger than 8 bytes")
>> +__btf_func_path("btf__exceptions_ret_pair_fail.bpf.o")
>> +int reject_exception_cb_ret_pair(void *ctx)
>> +{
>> +	bpf_throw(0);
>> +	return 0;
>> +}
>> +
>> +char _license[] SEC("license") = "GPL";
> A subsystem pattern flags this as potentially concerning:
> exceptions_ret_pair_fail.c is a new prog file added for one narrow
> variant of behaviour tools/testing/selftests/bpf/progs/exceptions_fail.c
> already covers: rejected exception-callback return types.
>
> exceptions_fail.c already holds exception_cb_bad_ret_type1(),
> exception_cb_bad_ret_type2() and the corresponding '__failure
> __msg("exception cb cannot return void")' case. The new callback is even
> named exception_cb_bad_ret_type3, continuing that file's numbering while
> living in a different file, so a reader grepping exceptions_fail.c for
> the series will not find it next to its siblings.
>
> There is a concrete technical reason the exceptions case cannot simply
> be appended to exceptions_fail.c: __btf_func_path also sets
> bpf_object_open_opts.btf_custom_path, and libbpf uses that as the CO-RE
> target BTF for the whole object. exceptions_fail.c includes <vmlinux.h>,
> which applies preserve_access_index to all records, and it dereferences
> vmlinux structs (ctx->len, ctx->protocol, ctx->tstamp), so it does have
> CO-RE relocations that would then be resolved against the 10-line
> companion BTF. That makes the split legitimate here; the remaining point
> is only that the shared naming (exception_cb_bad_ret_type3) does not
> reflect the split.
>
> Is the naming pattern worth revisiting given that the test lives in a
> separate file?

Right, the prog itself is in a dedicated file. But user space part
is in prog_tests/exceptions.c.

>
>> diff --git a/tools/testing/selftests/bpf/progs/timer_ret_pair_fail.c b/tools/testing/selftests/bpf/progs/timer_ret_pair_fail.c
>> new file mode 100644
>> index 0000000000000..29fd294dfd49b
>> --- /dev/null
>> +++ b/tools/testing/selftests/bpf/progs/timer_ret_pair_fail.c
>> @@ -0,0 +1,49 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
>> +
>> +#include <linux/bpf.h>
>> +#include <time.h>
>> +#include <bpf/bpf_helpers.h>
>> +#include <bpf/bpf_tracing.h>
>> +#include "bpf_misc.h"
>> +
>> +char _license[] SEC("license") = "GPL";
>> +
>> +struct elem {
>> +	struct bpf_timer t;
>> +};
>> +
>> +struct {
>> +	__uint(type, BPF_MAP_TYPE_ARRAY);
>> +	__uint(max_entries, 1);
>> +	__type(key, int);
>> +	__type(value, struct elem);
>> +} timer_map SEC(".maps");
>> +
>> +__naked __noinline __used
>> +static unsigned __int128 timer_cb_ret_pair(void *map, int *key, struct bpf_timer *timer)
>> +{
>> +	asm volatile (
>> +		"r0 = 0;"
>> +		"r2 = 0;"
>> +		"exit;"
>> +		::: __clobber_all
>> +	);
>> +}
>> +
>> +SEC("fentry/bpf_fentry_test1")
>> +__failure __msg("callback function with >8-byte return value is not supported")
>> +__btf_func_path("btf__timer_ret_pair_fail.bpf.o")
>> +long BPF_PROG2(test_bad_ret_pair, int, a)
>> +{
>> +	int key = 0;
>> +	struct bpf_timer *timer;
>> +
>> +	timer = bpf_map_lookup_elem(&timer_map, &key);
>> +	if (timer) {
>> +		bpf_timer_init(timer, &timer_map, CLOCK_BOOTTIME);
>> +		bpf_timer_set_callback(timer, timer_cb_ret_pair);
>> +	}
>> +
>> +	return 0;
>> +}
> A subsystem pattern flags this as potentially concerning:
> timer_ret_pair_fail.c is a new prog file added for one narrow variant (a
> callback whose declared return value is larger than 8 bytes) of
> behaviour that tools/testing/selftests/bpf/progs/timer_failure.c already
> covers more generally: bad bpf_timer callback return values.
>
> The two files are near-identical scaffolding - the same '#include
> <linux/bpf.h> / <time.h> / bpf_helpers.h / bpf_tracing.h / bpf_misc.h'
> set, the same 'struct elem { struct bpf_timer t; }', the same
> BPF_MAP_TYPE_ARRAY 'timer_map' with max_entries 1, the same
> SEC("fentry/bpf_fentry_test1") + BPF_PROG2 entry program, and the same
> '__naked __noinline __used static' callback idiom already used by
> timer_cb_ret_bad().
>
> The new program adds ~10 lines of actual test content on top of ~35
> lines of copied setup, plus a new skeleton, plus a companion
> btf__timer_ret_pair_fail.c, plus the RUN_TESTS()/#include churn in
> prog_tests/timer.c. Adding it as a third program inside timer_failure.c
> would reuse all of that. Since __btf_func_path is a per-program tag
> consumed per-subtest in run_subtest(), and timer_failure.c has no CO-RE
> relocations (it includes uapi <linux/bpf.h>, not <vmlinux.h>), the
> btf_custom_path side effect of the tag would not affect the existing
> subtests in that object.
>
> A separate file is defensible for symmetry with the exceptions half of
> the patch, where a separate file IS required due to CO-RE relocations.
> Should the new timer case be a new subtest in timer_failure.c, or does
> the symmetry with exceptions_ret_pair_fail.c make the separate file
> structure clearer?

In this case, the prog is in timer_failure.c for failure testing.

>
>
> ---
> 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/31446101762
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.