selftests/bpf: Test unwind_exit of an exception callback

In both tests the prog that throws returns to a call that unwind_goto
follows, and its exception callback ends with unwind_exit:
- An extension replaces a subprog that cannot unwind. unwind_goto after
  the call stays, since it shares the pad with a call of a subprog that
  can unwind.
- A subprog that can unwind does a tail call.

unwind_exit of the callback has to be exit. Without the fix the callers go
to the pad and return 2 instead of 3 and 42 instead of 7.

Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://patch.msgid.link/20261020231942.2568163-4-alexei.starovoitov@gmail.com
This commit is contained in:
Alexei Starovoitov 2026-10-20 23:19:41 +00:00
parent 9b27cd0554
commit 4c2cdb6156
No known key found for this signature in database
3 changed files with 75 additions and 5 deletions

View File

@ -67,7 +67,7 @@ void test_unwind_attach(void)
{
struct verifier_unwind *tgt;
struct bpf_program *prog;
int tgt_fd, plain_fd, err;
int tgt_fd, plain_fd, shared_fd, tail_fd, throws_fd, err, key = 0;
if (!is_jit_enabled()) {
printf("%s:SKIP:JIT is disabled\n", __func__);
@ -79,7 +79,10 @@ void test_unwind_attach(void)
return;
bpf_object__for_each_program(prog, tgt->obj)
bpf_program__set_autoload(prog, prog == tgt->progs.calls_global_unwinds ||
prog == tgt->progs.calls_global_never_unwinds);
prog == tgt->progs.calls_global_never_unwinds ||
prog == tgt->progs.shared_pad ||
prog == tgt->progs.tail_call_in_subprog ||
prog == tgt->progs.exception_callback_unwinds);
err = verifier_unwind__load(tgt);
if (err == -ENOTSUPP) {
printf("%s:SKIP:JIT does not support unwinding\n", __func__);
@ -107,6 +110,21 @@ void test_unwind_attach(void)
attach(plain_fd, "global_never_unwinds", "freplace_unwinds",
"Extension program can unwind, while original does not", 0);
ASSERT_EQ(run(tgt_fd), 15, "retval");
/*
* An exception callback returns from the frame of the main prog. Here
* that's a return to a call that unwind_goto follows. unwind_exit of
* the callback has to be exit: the pad wasn't verified with it.
*/
shared_fd = bpf_program__fd(tgt->progs.shared_pad);
attach(shared_fd, "global_never_unwinds", "freplace_throws", NULL, 3);
tail_fd = bpf_program__fd(tgt->progs.tail_call_in_subprog);
throws_fd = bpf_program__fd(tgt->progs.exception_callback_unwinds);
err = bpf_map__update_elem(tgt->maps.prog_array, &key, sizeof(key),
&throws_fd, sizeof(throws_fd), 0);
if (ASSERT_OK(err, "prog_array"))
ASSERT_EQ(run(tail_fd), 7, "retval");
out:
verifier_unwind__destroy(tgt);
}

View File

@ -7,6 +7,13 @@
/* The targets are global_unwinds() and global_never_unwinds() in verifier_unwind.c */
#define unwind_exit ".8byte %[insn_unwind_exit];"
#define __imm_unwind_exit \
__imm_insn(insn_unwind_exit, \
BPF_RAW_INSN(BPF_JMP | BPF_EXIT, 0, BPF_UNWIND_EXIT, 0, 0))
extern void bpf_throw(__u64 cookie) __ksym;
int fentry_cnt;
SEC("?fentry/global_unwinds")
@ -33,11 +40,27 @@ __naked unsigned long freplace_unwinds(void)
{
asm volatile (
"r0 = 3;"
".8byte %[unwind_exit];"
unwind_exit
:
: __imm_insn(unwind_exit,
BPF_RAW_INSN(BPF_JMP | BPF_EXIT, 0, BPF_UNWIND_EXIT, 0, 0))
: __imm_unwind_exit
);
}
/* Not naked: the argument has to have a name in BTF */
__noinline int throws_cb(__u64 cookie)
{
if (cookie)
asm volatile ("r0 = 3;" unwind_exit :: __imm_unwind_exit : "r0");
return 0;
}
/* It has no unwind_exit, so it can replace a subprog that cannot unwind */
SEC("?freplace/global_never_unwinds")
__attribute__((btf_decl_tag("exception_callback:throws_cb")))
unsigned long freplace_throws(void)
{
bpf_throw(1);
return 0;
}
char _license[] SEC("license") = "GPL";

View File

@ -611,6 +611,35 @@ __naked int calls_global_never_unwinds(void)
);
}
/*
* The verifier gets to the pad from the first call only, with r6 = 1. The second
* unwind_goto stays, since the pad is not dead code.
*/
SEC("tc")
__description("unwind: a pad shared with a call of a subprog that cannot unwind")
__arch_x86_64
__arch_arm64
__load_if_JITed()
__success __retval(7)
__naked int shared_pad(void)
{
asm volatile (
"r6 = 1;"
"call global_returns;"
unwind_goto(4)
"r6 = 2;"
"call global_never_unwinds;"
unwind_goto(1)
"exit;"
"r0 = r6;"
"exit;"
:
: __imm_unwind_goto(4),
__imm_unwind_goto(1)
: __clobber_all
);
}
SEC("tc")
__description("unwind: no unwind_goto after a call of a global subprog")
__arch_x86_64