// SPDX-License-Identifier: GPL-2.0 #include #include "verifier_unwind.skel.h" #include "unwind_attach.skel.h" static char log_buf[16 * 1024]; static int run(int fd) { char data[64] = {}; LIBBPF_OPTS(bpf_test_run_opts, opts, .data_in = data, .data_size_in = sizeof(data), .repeat = 1, ); ASSERT_OK(bpf_prog_test_run_opts(fd, &opts), "test_run"); return opts.retval; } /* * msg: why it cannot be loaded. Otherwise retval is what the target returns * with the prog attached. */ static void attach(int tgt_fd, const char *func, const char *name, const char *msg, int retval) { struct bpf_link *link = NULL; struct unwind_attach *skel; struct bpf_program *prog; int err; skel = unwind_attach__open(); if (!ASSERT_OK_PTR(skel, "unwind_attach__open")) return; prog = bpf_object__find_program_by_name(skel->obj, name); if (!ASSERT_OK_PTR(prog, name)) goto out; bpf_program__set_autoload(prog, true); log_buf[0] = 0; bpf_program__set_log_buf(prog, log_buf, sizeof(log_buf)); err = bpf_program__set_attach_target(prog, tgt_fd, func); if (!ASSERT_OK(err, "set_attach_target")) goto out; err = unwind_attach__load(skel); if (msg) { ASSERT_ERR(err, name); ASSERT_HAS_SUBSTR(log_buf, msg, name); goto out; } if (!ASSERT_OK(err, name)) goto out; link = bpf_program__attach(prog); if (!ASSERT_OK_PTR(link, "attach")) goto out; ASSERT_EQ(run(tgt_fd), retval, "retval"); if (prog == skel->progs.fentry_unwind) ASSERT_EQ(skel->bss->fentry_cnt, 1, "fentry_cnt"); out: bpf_link__destroy(link); unwind_attach__destroy(skel); } void test_unwind_attach(void) { struct verifier_unwind *tgt; struct bpf_program *prog; 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__); test__skip(); return; } tgt = verifier_unwind__open(); if (!ASSERT_OK_PTR(tgt, "verifier_unwind__open")) 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.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__); test__skip(); goto out; } if (!ASSERT_OK(err, "verifier_unwind__load")) goto out; /* * It calls global_unwinds(), which unwinds with r0 = 5, and returns r0 + 10 * from the pad. */ tgt_fd = bpf_program__fd(tgt->progs.calls_global_unwinds); plain_fd = bpf_program__fd(tgt->progs.calls_global_never_unwinds); ASSERT_EQ(run(tgt_fd), 15, "retval"); attach(tgt_fd, "global_unwinds", "fentry_unwind", NULL, 15); /* unwind_exit would return to the trampoline */ attach(tgt_fd, "global_unwinds", "fexit_unwind", "Only fentry can attach to global_unwinds", 0); /* An extension leaves either way, like what it replaced */ attach(tgt_fd, "global_unwinds", "freplace_returns", NULL, 9); attach(tgt_fd, "global_unwinds", "freplace_unwinds", NULL, 13); /* There is no unwind_goto after the call */ 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); }