mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
Merge branch 'bpf-unwinding-with-unwind_goto-and-unwind_exit'
Alexei Starovoitov says: ==================== bpf: Unwinding with unwind_goto and unwind_exit From: Alexei Starovoitov <ast@kernel.org> 1. What is the problem? A rust panic or a C++ exception has to run Drop impls or destructors of every frame it passes through. bpf_throw() discards the frames, so the verifier doesn't let a frame that owns a lock or a reference throw. In rust every index and every unwrap() can panic. 2. Is it worth solving? Without it rust-bpf [1] drops all landing pads and a panic is bpf_throw(). A prog that can panic with a guard held doesn't load. 3. Is there a simpler way? [2] keeps the normal path free of unwind code. llvm emits a table, libbpf passes it to the kernel, bpf_unwind() walks the stack and rewrites return addresses, and the verifier models hidden edges from calls to pads. That's 1400 lines in the kernel, with new uapi and two kfuncs, and 440 lines in libbpf. Here the edge from a call to its pad is an insn, and so is the other way to leave a frame [3]: ret exit invoke foo unwind pad call foo; unwind_goto pad resume r0 = 0; unwind_exit Pads are ordinary code. The verifier learns one bit: how the callee left. The normal path is free of unwind code too: JITs emit nothing for unwind_goto, and exit is what it was. 200 lines in patch 1 and 100 lines in each JIT. 4. Will the api and the layers last? Two new insns: one more kind of BPF_JCOND and one more kind of BPF_EXIT. No kfuncs, no BTF, no changes in libbpf and other loaders. The source level entry point is _Unwind_RaiseException() of the Itanium ABI. It's what the runtimes of rust and C++ call on other architectures. unwind_exit returns r0 like exit does. Today it's 0. That's where an exception object would go. 5. Is it good taste? Every frame returns through its own epilogue. The only question is where it returns to. That's one bit, and it's a bit of control flow. v1 kept it in a register. It's not specific to rust. A C++ prog with throw, catch and destructors compiles, loads and runs. It's a selftest in patch 5. The llvm side is a new PR [3]. It replaces [4]. v3 -> v4: - CI stayed red on x86-64. Call depth accounting was not the reason. CI builds x86-64 with KASAN, where the JIT wraps a load in pushes, a call and pops. The patterns of two __jited tests took that call for the call of the subprog. Now they start after the loads. - When the callee never exits, what follows unwind_goto is seen only speculatively. It was removed as dead code, and other code moved to where the cpu predicts the return. Now 'goto pc-1' is there (sashiko). - unwind_exit of an async or an exception callback is exit, like in the main prog. It was rejected. A sync callback still cannot unwind. - arm64: no autia1716 and pacia1716. The address of the pad isn't signed. The epilogue authenticates LR as it is, and the offset of the pad is added to it afterwards. - BPF_UNWIND_EXIT is enum instead of #define. - unwind_attach and unwind_cxx skip when JIT is disabled, and don't define ENOTSUPP (bpf-ci). v2 -> v3: - bpf_unwind_target() is back in the JITs. In core.c it broke the build on MIPS, where struct exception_table_entry has no fixup (sashiko, bpf-ci). - CI was red on x86-64: two __jited tests didn't expect call depth accounting in front of a call (bpf-ci). - A global subprog that returns 16 bytes couldn't unwind: the verifier checks r0 and r2 at unwind_exit, and llvm set r0 only. Fixed in llvm. Patch 6 is Kumar's test. - freplace: unwind_exit of an extension was exit, so the exception was lost (Eduard). Now the extension returns to the pad of the caller of what it replaced. An extension that can unwind replaces only a subprog that can. fexit cannot attach to such an extension. - unwind_exit makes the cpu mispredict the return. Without CAP_PERFMON the verifier looks at what follows unwind_goto as a speculative path (bpf-ci). - unwind_goto was 'call pc+0' in the xlated dump: BPF_JCOND is the same opcode as BPF_CALL_ARGS. - selftests: 'the callee never returns' looks at xlated insns, and 'the way the callee left is a part of the state' has BPF_F_TEST_STATE_FREQ. Neither tested what it said (bpf-ci). New tests for async and exception callbacks, speculation and freplace. Every check that the series adds was removed in turn to see that a test fails. - llvm: a pad doesn't set r0 when it only writes w0. Yonghong's callx case from [4] is a lit test. - Rebased. v1 -> v2: - unwind_goto and unwind_exit instead of 'if r3 != 0 goto pad' and 'r3 = 1; exit' (Kumar). - JIT support is not an optimization anymore. There are no shape tests, no look at jump_target, which was stale (sashiko), no exit that looks at r3 and no fallback to a branch. - r3 is an argument again and nothing else. In v1 a subprog that didn't write it passed it through, and fentry didn't restore it (bpf-ci). - Async and exception callbacks cannot unwind (sashiko). - No BTF decl_tag, which was matched by prefix (bpf-ci). It works without BTF. - x86 private stack is supported. - Tail call and ld_abs are allowed. - selftests: no #ifdef by arch, which broke the build on the rest of them. The messages are checked. fentry is attached and the target is run. Skips say why (bpf-ci). - clang defines __BPF_FEATURE_UNWIND. v3: https://lore.kernel.org/bpf/20261020043319.3031996-1-alexei.starovoitov@gmail.com/ v2: https://lore.kernel.org/bpf/20261019051846.3012089-1-alexei.starovoitov@gmail.com/ v1: https://lore.kernel.org/bpf/20261018010246.1643877-1-alexei.starovoitov@gmail.com/ Tested on x86-64 in kvm with KASAN, like CI does: test_progs, test_progs-no_alu32, test_progs-cpuv4 and test_verifier, all of them in full. The new tests also with retbleed=stuff. C, C++ and rust progs built with [3] were run on x86-64 and arm64, with and without bpf_jit_harden=2. arm64 is qemu, with and without pointer authentication, and a small loader instead of test_progs. v3 passed test_progs on arm64 in CI. [1] https://github.com/4ast/rust-bpf [2] https://lore.kernel.org/bpf/20261017025129.1914859-1-yonghong.song@linux.dev/ [3] https://github.com/llvm/llvm-project/pull/233649 [4] https://github.com/llvm/llvm-project/pull/233140 ==================== Link: https://patch.msgid.link/20261020181320.1284798-1-alexei.starovoitov@gmail.com Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
commit
221f119796
|
|
@ -11,6 +11,7 @@
|
|||
#include <linux/bitfield.h>
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/cfi.h>
|
||||
#include <linux/extable.h>
|
||||
#include <linux/filter.h>
|
||||
#include <linux/memory.h>
|
||||
#include <linux/printk.h>
|
||||
|
|
@ -1044,8 +1045,9 @@ static void __maybe_unused build_bhb_mitigation(struct jit_ctx *ctx)
|
|||
}
|
||||
}
|
||||
|
||||
static void build_epilogue(struct jit_ctx *ctx, bool was_classic)
|
||||
static void build_epilogue(struct jit_ctx *ctx, bool was_classic, bool unwind)
|
||||
{
|
||||
const u8 tmp = bpf2a64[TMP_REG_1];
|
||||
const u8 r0 = bpf2a64[BPF_REG_0];
|
||||
const u8 ptr = bpf2a64[TCCNT_PTR];
|
||||
|
||||
|
|
@ -1073,6 +1075,10 @@ static void build_epilogue(struct jit_ctx *ctx, bool was_classic)
|
|||
if (IS_ENABLED(CONFIG_ARM64_PTR_AUTH_KERNEL))
|
||||
emit(A64_AUTIASP, ctx);
|
||||
|
||||
/* tmp is the offset of the pad, see emit_unwind_exit() */
|
||||
if (unwind)
|
||||
emit(A64_ADD(1, A64_LR, A64_LR, tmp), ctx);
|
||||
|
||||
emit(A64_RET(A64_LR), ctx);
|
||||
}
|
||||
|
||||
|
|
@ -1221,6 +1227,76 @@ static int add_exception_handler(const struct bpf_insn *insn,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* There is no branch for unwind_goto, only an extable entry
|
||||
* { .insn = ret_addr - 1, .fixup = pad - ret_addr }
|
||||
* where ret_addr is the return address of the call in front of it. unwind_exit
|
||||
* adds .fixup to its LR.
|
||||
* ret_addr - 1, because what follows the call can have an entry of its own.
|
||||
* No insn starts there, so a fault cannot hit the entry.
|
||||
* 'bl foo; mov x8, x0' is in front of unwind_goto. The pad doesn't miss the mov:
|
||||
* r0 is x8 and the epilogue of the callee copies it to x0 and leaves x8 as it is.
|
||||
*/
|
||||
static int add_unwind_exentry(struct jit_ctx *ctx, int pad)
|
||||
{
|
||||
int ret_addr = ctx->idx - 1;
|
||||
struct exception_table_entry *ex;
|
||||
off_t ins_offset;
|
||||
|
||||
if (!ctx->image)
|
||||
/* First pass */
|
||||
return 0;
|
||||
|
||||
if (!ctx->prog->aux->extable ||
|
||||
WARN_ON_ONCE(ctx->exentry_idx >= ctx->prog->aux->num_exentries))
|
||||
return -EINVAL;
|
||||
|
||||
ex = &ctx->prog->aux->extable[ctx->exentry_idx];
|
||||
ins_offset = (long)&ctx->ro_image[ret_addr] - 1 - (long)&ex->insn;
|
||||
if (WARN_ON_ONCE(ins_offset >= 0 || ins_offset < INT_MIN))
|
||||
return -ERANGE;
|
||||
|
||||
/* switch ex to rw buffer for writing */
|
||||
ex = (void *)ctx->image + ((void *)ex - (void *)ctx->ro_image);
|
||||
ex->insn = ins_offset;
|
||||
ex->fixup = (ctx->offset[pad] - ret_addr) * AARCH64_INSN_SIZE;
|
||||
ex->type = EX_TYPE_BPF;
|
||||
|
||||
ctx->exentry_idx++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long bpf_unwind_fixup(u64 lr)
|
||||
{
|
||||
const struct exception_table_entry *e;
|
||||
|
||||
e = search_bpf_extables(ptrauth_strip_kernel_insn_pac(lr) - 1);
|
||||
/*
|
||||
* The verifier made sure that unwind_goto follows a call in a prog. When
|
||||
* the kernel called an async or an exception callback, it's exit.
|
||||
*/
|
||||
return e ? e->fixup : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* unwind_exit: return to the pad. LR in the frame record is signed. Instead of
|
||||
* signing the address of the pad, let the epilogue authenticate LR as it is and
|
||||
* add the offset of the pad to it. r0 and r2 are returned.
|
||||
*/
|
||||
static void emit_unwind_exit(struct jit_ctx *ctx)
|
||||
{
|
||||
const u8 tmp = bpf2a64[TMP_REG_1];
|
||||
const u8 r0 = bpf2a64[BPF_REG_0];
|
||||
const u8 r2 = bpf2a64[BPF_REG_2];
|
||||
|
||||
emit(A64_PUSH(r0, r2, A64_SP), ctx);
|
||||
emit(A64_LDR64I(A64_R(0), A64_FP, 8), ctx);
|
||||
emit_call((u64)bpf_unwind_fixup, ctx);
|
||||
emit(A64_MOV(1, tmp, A64_R(0)), ctx);
|
||||
emit(A64_POP(r0, r2, A64_SP), ctx);
|
||||
build_epilogue(ctx, false, true);
|
||||
}
|
||||
|
||||
static const struct bpf_jit_arg_abi arm64_arg_abi = {
|
||||
.nr_arg_regs = 8,
|
||||
.even_reg_align = true,
|
||||
|
|
@ -1806,8 +1882,18 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn
|
|||
if (emit_bpf_tail_call(ctx))
|
||||
return -EFAULT;
|
||||
break;
|
||||
/* unwind_goto. The verifier expanded may_goto. */
|
||||
case BPF_JMP | BPF_JCOND:
|
||||
ret = add_unwind_exentry(ctx, i + 1 + off);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
/* function return */
|
||||
case BPF_JMP | BPF_EXIT:
|
||||
if (bpf_is_unwind_exit(insn) && bpf_prog_can_unwind(ctx->prog)) {
|
||||
emit_unwind_exit(ctx);
|
||||
break;
|
||||
}
|
||||
/* Optimization: when last instruction is EXIT,
|
||||
simply fallthrough to epilogue. */
|
||||
if (i == ctx->prog->len - 1)
|
||||
|
|
@ -2312,7 +2398,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr
|
|||
goto out_off;
|
||||
|
||||
ctx.epilogue_offset = ctx.idx;
|
||||
build_epilogue(&ctx, was_classic);
|
||||
build_epilogue(&ctx, was_classic, false);
|
||||
build_plt(&ctx);
|
||||
|
||||
extable_align = __alignof__(struct exception_table_entry);
|
||||
|
|
@ -2369,7 +2455,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr
|
|||
WARN_ON_ONCE(ctx.idx != ctx.epilogue_offset))
|
||||
goto out_free_hdr;
|
||||
|
||||
build_epilogue(&ctx, was_classic);
|
||||
build_epilogue(&ctx, was_classic, false);
|
||||
build_plt(&ctx);
|
||||
|
||||
/* Extra pass to validate JITed code. */
|
||||
|
|
@ -2466,6 +2552,11 @@ bool bpf_jit_supports_kfunc_ret_reg_pair(void)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool bpf_jit_supports_unwind(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const struct bpf_jit_arg_abi *bpf_jit_arg_abi(void)
|
||||
{
|
||||
return &arm64_arg_abi;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <linux/memory.h>
|
||||
#include <linux/sort.h>
|
||||
#include <linux/execmem.h>
|
||||
#include <linux/extable.h>
|
||||
#include <asm/extable.h>
|
||||
#include <asm/ftrace.h>
|
||||
#include <asm/set_memory.h>
|
||||
|
|
@ -2511,6 +2512,68 @@ static void adjust_cmp_imm(u32 *op, s32 *imm)
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* There is no branch for unwind_goto, only an extable entry
|
||||
* { .insn = ret_addr - 1, .fixup = target - ret_addr }
|
||||
* where ret_addr is the return address of the call in front of it. unwind_exit
|
||||
* replaces the return address of its frame with bpf_unwind_target() of it.
|
||||
* ret_addr - 1, because what follows the call can have an entry of its own.
|
||||
* No insn starts there, so a fault cannot hit the entry.
|
||||
*/
|
||||
static int add_unwind_exentry(struct bpf_prog *prog, u8 *image, u8 *rw_image, int *excnt,
|
||||
int ret_addr, int target)
|
||||
{
|
||||
struct exception_table_entry *ex;
|
||||
s64 delta;
|
||||
|
||||
if (!prog->aux->extable)
|
||||
return 0;
|
||||
if (*excnt >= prog->aux->num_exentries) {
|
||||
pr_err("unwind extable bug\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
ex = &prog->aux->extable[(*excnt)++];
|
||||
delta = image + ret_addr - 1 - (u8 *)&ex->insn;
|
||||
if (!is_simm32(delta)) {
|
||||
pr_err("extable->insn doesn't fit into 32-bit\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
/* switch ex to rw buffer for writes */
|
||||
ex = (void *)rw_image + ((void *)ex - (void *)image);
|
||||
ex->insn = delta;
|
||||
ex->fixup = target - ret_addr;
|
||||
ex->data = EX_TYPE_BPF;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u64 bpf_unwind_target(u64 ret_addr)
|
||||
{
|
||||
const struct exception_table_entry *e = search_bpf_extables(ret_addr - 1);
|
||||
|
||||
/*
|
||||
* The verifier made sure that unwind_goto follows a call in a prog. When
|
||||
* the kernel called an async or an exception callback, it's exit.
|
||||
*/
|
||||
return e ? ret_addr + e->fixup : ret_addr;
|
||||
}
|
||||
|
||||
/* unwind_exit: fix the return address of this frame. r0 and r2 are returned. */
|
||||
static int emit_unwind_ret_addr(u8 **pprog, u8 *ip)
|
||||
{
|
||||
u8 *prog = *pprog, *start = prog;
|
||||
|
||||
EMIT2(0x50, 0x56); /* push rax; push rsi */
|
||||
EMIT4(0x48, 0x8B, 0x7D, 0x08); /* mov rdi, [rbp + 8] */
|
||||
ip += prog - start;
|
||||
ip += x86_call_depth_emit_accounting(&prog, bpf_unwind_target, ip);
|
||||
if (emit_call(&prog, bpf_unwind_target, ip))
|
||||
return -EINVAL;
|
||||
EMIT4(0x48, 0x89, 0x45, 0x08); /* mov [rbp + 8], rax */
|
||||
EMIT2(0x5E, 0x58); /* pop rsi; pop rax */
|
||||
*pprog = prog;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *addrs, u8 *image,
|
||||
u8 *rw_image, int oldproglen, struct jit_context *ctx, bool jmp_padding)
|
||||
{
|
||||
|
|
@ -3493,6 +3556,34 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
|
|||
pop_r9(&prog);
|
||||
break;
|
||||
|
||||
/* unwind_goto. The verifier expanded may_goto. */
|
||||
case BPF_JMP | BPF_JCOND:
|
||||
if (!priv_frame_ptr) {
|
||||
err = add_unwind_exentry(bpf_prog, image, rw_image, &excnt,
|
||||
addrs[i - 1], addrs[i + insn->off]);
|
||||
if (err)
|
||||
return err;
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* 'pop r9' after the call has to be done on the way to
|
||||
* the pad too:
|
||||
* call foo
|
||||
* pop r9 <- exit returns here
|
||||
* jmp 1f
|
||||
* pop r9 <- unwind_exit returns here
|
||||
* jmp pad
|
||||
* 1:
|
||||
*/
|
||||
err = add_unwind_exentry(bpf_prog, image, rw_image, &excnt,
|
||||
addrs[i - 1] - 2, addrs[i - 1] + 2);
|
||||
if (err)
|
||||
return err;
|
||||
EMIT2(0xEB, 7);
|
||||
pop_r9(&prog);
|
||||
EMIT1_off32(0xE9, addrs[i + insn->off] - (addrs[i - 1] + 9));
|
||||
break;
|
||||
|
||||
case BPF_JMP | BPF_TAIL_CALL:
|
||||
if (imm32)
|
||||
emit_bpf_tail_call_direct(bpf_prog,
|
||||
|
|
@ -3774,13 +3865,16 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
|
|||
break;
|
||||
|
||||
case BPF_JMP | BPF_EXIT:
|
||||
if (src_reg == BPF_UNWIND_EXIT && bpf_prog_can_unwind(bpf_prog) &&
|
||||
emit_unwind_ret_addr(&prog, ip))
|
||||
return -EINVAL;
|
||||
if (seen_exit) {
|
||||
jmp_offset = ctx->cleanup_addr - addrs[i];
|
||||
goto emit_jmp;
|
||||
}
|
||||
seen_exit = true;
|
||||
/* Update cleanup_addr */
|
||||
ctx->cleanup_addr = proglen;
|
||||
ctx->cleanup_addr = proglen + (prog - temp);
|
||||
if (bpf_prog_was_classic(bpf_prog) &&
|
||||
!ns_capable_noaudit(&init_user_ns, CAP_SYS_ADMIN)) {
|
||||
if (emit_spectre_bhb_barrier(&prog, ip, bpf_prog))
|
||||
|
|
@ -5027,6 +5121,11 @@ bool bpf_jit_supports_kfunc_ret_reg_pair(void)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool bpf_jit_supports_unwind(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const struct bpf_jit_arg_abi *bpf_jit_arg_abi(void)
|
||||
{
|
||||
return &x86_arg_abi;
|
||||
|
|
|
|||
|
|
@ -1897,6 +1897,7 @@ struct bpf_prog_aux {
|
|||
bool changes_pkt_data;
|
||||
bool has_prologue;
|
||||
bool might_sleep;
|
||||
bool can_unwind;
|
||||
bool kprobe_write_ctx;
|
||||
/* has insns that work with the arena base, see bpf_prog_has_arena_insns() */
|
||||
bool uses_arena_insns;
|
||||
|
|
|
|||
|
|
@ -532,6 +532,8 @@ struct bpf_verifier_state {
|
|||
|
||||
bool speculative;
|
||||
bool in_sleepable;
|
||||
/* The callee left with unwind_exit. Next insn is unwind_goto. */
|
||||
bool unwound;
|
||||
|
||||
/*
|
||||
* First and last insn idx of this verifier state.
|
||||
|
|
@ -957,6 +959,7 @@ struct bpf_subprog_info {
|
|||
s16 fastcall_stack_off;
|
||||
bool has_tail_call: 1;
|
||||
bool might_throw: 1;
|
||||
bool can_unwind: 1; /* has unwind_exit */
|
||||
bool tail_call_reachable: 1;
|
||||
bool has_ld_abs: 1;
|
||||
bool is_cb: 1;
|
||||
|
|
|
|||
|
|
@ -254,6 +254,25 @@ static inline bool insn_is_mov_percpu_addr(const struct bpf_insn *insn)
|
|||
.off = 0, \
|
||||
.imm = 1 })
|
||||
|
||||
static inline bool bpf_is_unwind_goto(const struct bpf_insn *insn)
|
||||
{
|
||||
return insn->code == (BPF_JMP | BPF_JCOND) && insn->src_reg == BPF_UNWIND_GOTO;
|
||||
}
|
||||
|
||||
static inline bool bpf_is_unwind_exit(const struct bpf_insn *insn)
|
||||
{
|
||||
return insn->code == (BPF_JMP | BPF_EXIT) && insn->src_reg == BPF_UNWIND_EXIT;
|
||||
}
|
||||
|
||||
/*
|
||||
* The caller of the main prog doesn't tell unwind_exit from exit. An extension
|
||||
* is different: its caller is the caller of the subprog that it replaced.
|
||||
*/
|
||||
static inline bool bpf_prog_can_unwind(const struct bpf_prog *prog)
|
||||
{
|
||||
return bpf_is_subprog(prog) || prog->type == BPF_PROG_TYPE_EXT;
|
||||
}
|
||||
|
||||
static inline bool insn_is_zext(const struct bpf_insn *insn)
|
||||
{
|
||||
return insn->code == (BPF_ALU | BPF_MOV | BPF_X) && insn->imm == 1;
|
||||
|
|
@ -1257,6 +1276,7 @@ bool bpf_jit_supports_percpu_insn(void);
|
|||
bool bpf_jit_supports_kfunc_call(void);
|
||||
bool bpf_jit_supports_callx(void);
|
||||
bool bpf_jit_supports_kfunc_ret_reg_pair(void);
|
||||
bool bpf_jit_supports_unwind(void);
|
||||
bool bpf_jit_supports_stack_args(void);
|
||||
bool bpf_jit_supports_arena_args(void);
|
||||
bool bpf_jit_supports_far_kfunc_call(void);
|
||||
|
|
|
|||
|
|
@ -56,6 +56,19 @@
|
|||
|
||||
enum bpf_cond_pseudo_jmp {
|
||||
BPF_MAY_GOTO = 0,
|
||||
/*
|
||||
* Follows a call of a bpf subprog. Taken when the subprog left with
|
||||
* BPF_EXIT that has src_reg == BPF_UNWIND_EXIT.
|
||||
*/
|
||||
BPF_UNWIND_GOTO = 1,
|
||||
};
|
||||
|
||||
enum bpf_pseudo_exit {
|
||||
/*
|
||||
* When bpf_exit->src_reg == BPF_UNWIND_EXIT, it's the same as bpf_exit,
|
||||
* except that BPF_UNWIND_GOTO after the call is taken.
|
||||
*/
|
||||
BPF_UNWIND_EXIT = 1,
|
||||
};
|
||||
|
||||
/* Register numbers */
|
||||
|
|
|
|||
|
|
@ -357,7 +357,7 @@ int bpf_prune_dead_branches(struct bpf_verifier_env *env)
|
|||
|
||||
if (!bpf_insn_is_cond_jump(insn->code))
|
||||
continue;
|
||||
if (bpf_is_may_goto_insn(insn))
|
||||
if (BPF_OP(insn->code) == BPF_JCOND)
|
||||
continue;
|
||||
|
||||
if (!(aux->const_reg_mask & BIT(insn->dst_reg)))
|
||||
|
|
|
|||
|
|
@ -3399,6 +3399,12 @@ bool __weak bpf_jit_supports_kfunc_ret_reg_pair(void)
|
|||
return false;
|
||||
}
|
||||
|
||||
/* unwind_goto and unwind_exit */
|
||||
bool __weak bpf_jit_supports_unwind(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* How this arch places a by-value kfunc argument, or NULL for one that has
|
||||
* not opted in and so only takes an argument of a single eightbyte, which
|
||||
|
|
|
|||
|
|
@ -375,11 +375,16 @@ void print_bpf_insn(const struct bpf_insn_cbs *cbs,
|
|||
insn->src_reg == BPF_MAY_GOTO) {
|
||||
verbose(cbs->private_data, "(%02x) may_goto pc%+d",
|
||||
insn->code, insn->off);
|
||||
} else if (insn->code == (BPF_JMP | BPF_JCOND) &&
|
||||
insn->src_reg == BPF_UNWIND_GOTO) {
|
||||
verbose(cbs->private_data, "(%02x) unwind_goto pc%+d",
|
||||
insn->code, insn->off);
|
||||
} else if (insn->code == (BPF_JMP32 | BPF_JA)) {
|
||||
verbose(cbs->private_data, "(%02x) gotol pc%+d",
|
||||
insn->code, insn->imm);
|
||||
} else if (insn->code == (BPF_JMP | BPF_EXIT)) {
|
||||
verbose(cbs->private_data, "(%02x) exit", insn->code);
|
||||
verbose(cbs->private_data, "(%02x) %s", insn->code,
|
||||
insn->src_reg == BPF_UNWIND_EXIT ? "unwind_exit" : "exit");
|
||||
} else if (BPF_SRC(insn->code) == BPF_X) {
|
||||
verbose(cbs->private_data,
|
||||
"(%02x) if %c%d %s %c%d goto pc%+d",
|
||||
|
|
|
|||
|
|
@ -658,6 +658,7 @@ void bpf_opt_hard_wire_dead_code_branches(struct bpf_verifier_env *env)
|
|||
{
|
||||
struct bpf_insn_aux_data *aux_data = env->insn_aux_data;
|
||||
struct bpf_insn ja = BPF_JMP_IMM(BPF_JA, 0, 0, 0);
|
||||
struct bpf_insn trap = BPF_JMP_IMM(BPF_JA, 0, 0, -1);
|
||||
struct bpf_insn *insn = env->prog->insnsi;
|
||||
const int insn_cnt = env->prog->len;
|
||||
int i;
|
||||
|
|
@ -666,6 +667,20 @@ void bpf_opt_hard_wire_dead_code_branches(struct bpf_verifier_env *env)
|
|||
if (!bpf_insn_is_cond_jump(insn->code))
|
||||
continue;
|
||||
|
||||
if (bpf_is_unwind_goto(insn) && aux_data[i + 1 + insn->off].seen) {
|
||||
/*
|
||||
* The callee never exits. Its unwind_exit needs
|
||||
* unwind_goto all the same, and the cpu predicts that it
|
||||
* returns to what follows. Don't let the removal of dead
|
||||
* code put something else there.
|
||||
*/
|
||||
if (!aux_data[i + 1].seen) {
|
||||
memcpy(insn + 1, &trap, sizeof(trap));
|
||||
aux_data[i + 1].seen = env->pass_cnt;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!aux_data[i + 1].seen)
|
||||
ja.off = insn->off;
|
||||
else if (!aux_data[i + 1 + insn->off].seen)
|
||||
|
|
@ -1467,12 +1482,15 @@ static int jit_subprogs(struct bpf_verifier_env *env)
|
|||
if (BPF_CLASS(insn->code) == BPF_STX &&
|
||||
BPF_MODE(insn->code) == BPF_PROBE_ATOMIC)
|
||||
num_exentries++;
|
||||
if (bpf_is_unwind_goto(insn))
|
||||
num_exentries++;
|
||||
}
|
||||
func[i]->aux->num_exentries = num_exentries;
|
||||
func[i]->aux->tail_call_reachable = env->subprog_info[i].tail_call_reachable;
|
||||
func[i]->aux->exception_cb = env->subprog_info[i].is_exception_cb;
|
||||
func[i]->aux->changes_pkt_data = env->subprog_info[i].changes_pkt_data;
|
||||
func[i]->aux->might_sleep = env->subprog_info[i].might_sleep;
|
||||
func[i]->aux->can_unwind = env->subprog_info[i].can_unwind;
|
||||
func[i]->aux->token = prog->aux->token;
|
||||
if (!i)
|
||||
func[i]->aux->exception_boundary = env->seen_exception;
|
||||
|
|
|
|||
|
|
@ -1046,6 +1046,9 @@ static bool states_equal(struct bpf_verifier_env *env,
|
|||
if (old->in_sleepable != cur->in_sleepable)
|
||||
return false;
|
||||
|
||||
if (old->unwound != cur->unwound)
|
||||
return false;
|
||||
|
||||
if (!refsafe(old, cur, &env->idmap_scratch))
|
||||
return false;
|
||||
|
||||
|
|
|
|||
|
|
@ -5087,6 +5087,12 @@ static struct bpf_insn *bpf_insn_prepare_dump(const struct bpf_prog *prog,
|
|||
insns[i].imm = BPF_FUNC_tail_call;
|
||||
/* fall-through */
|
||||
}
|
||||
/*
|
||||
* BPF_JCOND is the same opcode as BPF_CALL_ARGS. The latter is for
|
||||
* the interpreter, and a prog with unwind_goto is always JITed.
|
||||
*/
|
||||
if (prog->jited && bpf_is_unwind_goto(&insns[i]))
|
||||
continue;
|
||||
if (code == (BPF_JMP | BPF_CALL) ||
|
||||
code == (BPF_JMP | BPF_CALL_ARGS)) {
|
||||
/* Restore the legacy xlated dump layout. */
|
||||
|
|
|
|||
|
|
@ -1769,6 +1769,7 @@ int bpf_copy_verifier_state(struct bpf_verifier_state *dst_state,
|
|||
return err;
|
||||
dst_state->speculative = src->speculative;
|
||||
dst_state->in_sleepable = src->in_sleepable;
|
||||
dst_state->unwound = src->unwound;
|
||||
dst_state->curframe = src->curframe;
|
||||
dst_state->branches = src->branches;
|
||||
dst_state->parent = src->parent;
|
||||
|
|
@ -3430,6 +3431,46 @@ static int next_callx_callee(struct bpf_verifier_env *env, int caller, int from)
|
|||
return bit < end ? bit - caller * cnt : -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* 'call foo; unwind_goto pad' goes to the pad when foo left with unwind_exit.
|
||||
* JITs don't emit a branch for it. unwind_exit finds the pad by its return
|
||||
* address. So unwind_goto has to follow the call and nothing may jump to it.
|
||||
*/
|
||||
static int check_unwind_insns(struct bpf_verifier_env *env)
|
||||
{
|
||||
struct bpf_insn *insn = env->prog->insnsi;
|
||||
bool seen = false;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < env->prog->len; i++, insn++) {
|
||||
if (bpf_is_unwind_exit(insn)) {
|
||||
bpf_find_containing_subprog(env, i)->can_unwind = true;
|
||||
seen = true;
|
||||
}
|
||||
if (!bpf_is_unwind_goto(insn))
|
||||
continue;
|
||||
seen = true;
|
||||
if (!i || !(bpf_pseudo_call(insn - 1) || bpf_is_callx(insn - 1))) {
|
||||
verbose(env, "unwind_goto at insn %d doesn't follow a call of a subprog\n",
|
||||
i);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (bpf_is_jump_target(env, i)) {
|
||||
verbose(env, "jump to unwind_goto at insn %d\n", i);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
if (!seen)
|
||||
return 0;
|
||||
if (!bpf_jit_supports_unwind()) {
|
||||
verbose(env, "JIT does not support unwinding\n");
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
env->prog->jit_required = 1;
|
||||
env->prog->aux->can_unwind = env->subprog_info[0].can_unwind;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sort subprogs in topological order so that leaf subprogs come first and
|
||||
* their callers come later. This is a DFS post-order traversal of the call
|
||||
|
|
@ -11584,6 +11625,19 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
|
|||
}
|
||||
}
|
||||
|
||||
if (env->subprog_info[subprog].can_unwind) {
|
||||
struct bpf_verifier_state *branch;
|
||||
|
||||
if (!bpf_is_unwind_goto(insn + 1)) {
|
||||
verbose(env, "no unwind_goto after the call of %s()\n", sub_name);
|
||||
return -EINVAL;
|
||||
}
|
||||
branch = push_stack(env, *insn_idx + 1, *insn_idx, false);
|
||||
if (IS_ERR(branch))
|
||||
return PTR_ERR(branch);
|
||||
branch->unwound = true;
|
||||
}
|
||||
|
||||
if (env->subprog_info[subprog].might_throw) {
|
||||
struct bpf_verifier_state *branch;
|
||||
|
||||
|
|
@ -12057,6 +12111,19 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
|
|||
}
|
||||
|
||||
caller = state->frame[state->curframe - 1];
|
||||
if (bpf_is_unwind_exit(&env->prog->insnsi[*insn_idx])) {
|
||||
/* the helper or the kfunc that called it doesn't expect that */
|
||||
if (callee->in_callback_fn) {
|
||||
verbose(env, "callback cannot unwind\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!bpf_is_unwind_goto(&env->prog->insnsi[callee->callsite + 1])) {
|
||||
verbose(env, "unwind_exit, but no unwind_goto after the call at insn %d\n",
|
||||
callee->callsite);
|
||||
return -EINVAL;
|
||||
}
|
||||
state->unwound = true;
|
||||
}
|
||||
if (callee->in_callback_fn) {
|
||||
if (r0->type != SCALAR_VALUE) {
|
||||
verbose(env, "R0 not a scalar value\n");
|
||||
|
|
@ -18759,6 +18826,23 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (bpf_is_unwind_goto(insn)) {
|
||||
if (!env->cur_state->unwound)
|
||||
return 0;
|
||||
env->cur_state->unwound = false;
|
||||
/*
|
||||
* unwind_exit changed its return address, so the return is
|
||||
* mispredicted and what follows unwind_goto runs speculatively.
|
||||
*/
|
||||
if (!env->bypass_spec_v1) {
|
||||
err = sanitize_speculative_path(env, NULL, *insn_idx + 1, *insn_idx);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
*insn_idx += insn->off;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (opcode == BPF_JCOND) {
|
||||
struct bpf_verifier_state *cur_st = env->cur_state, *queued_st, *prev_st;
|
||||
int idx = *insn_idx;
|
||||
|
|
@ -21223,14 +21307,14 @@ static int check_jmp_fields(struct bpf_verifier_env *env, struct bpf_insn *insn)
|
|||
return 0;
|
||||
case BPF_EXIT:
|
||||
if (BPF_SRC(insn->code) != BPF_K || insn->imm != 0 ||
|
||||
insn->src_reg != BPF_REG_0 || insn->dst_reg != BPF_REG_0 ||
|
||||
insn->src_reg > BPF_UNWIND_EXIT || insn->dst_reg != BPF_REG_0 ||
|
||||
class == BPF_JMP32) {
|
||||
verbose(env, "BPF_EXIT uses reserved fields\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
case BPF_JCOND:
|
||||
if (insn->code != (BPF_JMP | BPF_JCOND) || insn->src_reg != BPF_MAY_GOTO ||
|
||||
if (insn->code != (BPF_JMP | BPF_JCOND) || insn->src_reg > BPF_UNWIND_GOTO ||
|
||||
insn->dst_reg || insn->imm) {
|
||||
verbose(env, "invalid may_goto imm %d\n", insn->imm);
|
||||
return -EINVAL;
|
||||
|
|
@ -22436,6 +22520,7 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,
|
|||
struct bpf_prog_aux *aux = tgt_prog->aux;
|
||||
bool tgt_changes_pkt_data;
|
||||
bool tgt_might_sleep;
|
||||
bool tgt_can_unwind;
|
||||
|
||||
if (bpf_prog_is_dev_bound(prog->aux) &&
|
||||
!bpf_prog_dev_bound_match(prog, tgt_prog)) {
|
||||
|
|
@ -22464,6 +22549,16 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,
|
|||
prog_extension ? "Extension" : "Tracing");
|
||||
return -EINVAL;
|
||||
}
|
||||
/* unwind_exit of the main prog is exit, unless it's an extension */
|
||||
tgt_can_unwind = (subprog || tgt_prog->type == BPF_PROG_TYPE_EXT) &&
|
||||
(aux->func ? aux->func[subprog]->aux->can_unwind
|
||||
: aux->can_unwind);
|
||||
/* unwind_exit would return to the trampoline that called the target */
|
||||
if (tgt_can_unwind && !prog_extension &&
|
||||
prog->expected_attach_type != BPF_TRACE_FENTRY) {
|
||||
bpf_log(log, "Only fentry can attach to %s, which can unwind\n", tname);
|
||||
return -EINVAL;
|
||||
}
|
||||
conservative = aux->func_info_aux[subprog].unreliable;
|
||||
if (prog_extension) {
|
||||
if (aux->func_info_aux[subprog].linkage != BTF_FUNC_GLOBAL ||
|
||||
|
|
@ -22511,6 +22606,12 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,
|
|||
"Extension program may sleep, while original does not\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
/* There is no unwind_goto after the calls of the original */
|
||||
if (prog->aux->can_unwind && !tgt_can_unwind) {
|
||||
bpf_log(log,
|
||||
"Extension program can unwind, while original does not\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
if (!tgt_prog->jited) {
|
||||
bpf_log(log, "Can attach to only JITed progs\n");
|
||||
|
|
@ -23845,6 +23946,10 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
|
|||
if (ret < 0)
|
||||
goto skip_full_check;
|
||||
|
||||
ret = check_unwind_insns(env);
|
||||
if (ret < 0)
|
||||
goto skip_full_check;
|
||||
|
||||
ret = bpf_compute_postorder(env);
|
||||
if (ret < 0)
|
||||
goto skip_full_check;
|
||||
|
|
|
|||
|
|
@ -56,6 +56,19 @@
|
|||
|
||||
enum bpf_cond_pseudo_jmp {
|
||||
BPF_MAY_GOTO = 0,
|
||||
/*
|
||||
* Follows a call of a bpf subprog. Taken when the subprog left with
|
||||
* BPF_EXIT that has src_reg == BPF_UNWIND_EXIT.
|
||||
*/
|
||||
BPF_UNWIND_GOTO = 1,
|
||||
};
|
||||
|
||||
enum bpf_pseudo_exit {
|
||||
/*
|
||||
* When bpf_exit->src_reg == BPF_UNWIND_EXIT, it's the same as bpf_exit,
|
||||
* except that BPF_UNWIND_GOTO after the call is taken.
|
||||
*/
|
||||
BPF_UNWIND_EXIT = 1,
|
||||
};
|
||||
|
||||
/* Register numbers */
|
||||
|
|
|
|||
|
|
@ -153,4 +153,15 @@ $(RUST_OBJS): $(RDIR)/%.bpf.o: progs/%.rs $(RUST_CORE) | $(RDIR)
|
|||
endif
|
||||
endif
|
||||
|
||||
# Programs in C++. No skeletons: gcc doesn't build them.
|
||||
ifeq ($(BPF_CC),$(CLANG))
|
||||
CXX_OBJS := $(patsubst progs/%.cpp,$(RDIR)/%.bpf.o,$(wildcard progs/*.cpp))
|
||||
BPF_OBJS += $(CXX_OBJS)
|
||||
|
||||
$(CXX_OBJS): $(RDIR)/%.bpf.o: progs/%.cpp | $(RDIR)
|
||||
$(call msg,$(BPF_CC_MSG),$(BINARY),$@)
|
||||
$(Q)$(BPF_CC) -x c++ -g -Wall -Werror -fexceptions -funwind-tables -fno-rtti \
|
||||
$(BPF_CC_FLAGS) -c $< -o $@ $(call skip_on_fail,BPF)
|
||||
endif
|
||||
|
||||
endif # BPF_CC
|
||||
|
|
|
|||
112
tools/testing/selftests/bpf/prog_tests/unwind_attach.c
Normal file
112
tools/testing/selftests/bpf/prog_tests/unwind_attach.c
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include <test_progs.h>
|
||||
#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, err;
|
||||
|
||||
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);
|
||||
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");
|
||||
out:
|
||||
verifier_unwind__destroy(tgt);
|
||||
}
|
||||
99
tools/testing/selftests/bpf/prog_tests/unwind_cxx.c
Normal file
99
tools/testing/selftests/bpf/prog_tests/unwind_cxx.c
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include <test_progs.h>
|
||||
#include <bpf/btf.h>
|
||||
|
||||
/* The progs are under #ifdef __BPF_FEATURE_UNWIND */
|
||||
static bool llvm_lowers_unwinding(const char *file)
|
||||
{
|
||||
struct btf *btf = btf__parse(file, NULL);
|
||||
bool ret;
|
||||
|
||||
ret = btf && btf__find_by_name_kind(btf, "catches", BTF_KIND_FUNC) > 0;
|
||||
btf__free(btf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void run(struct bpf_object *obj, const char *name, int len, int retval, int dtors)
|
||||
{
|
||||
struct bpf_program *prog = bpf_object__find_program_by_name(obj, name);
|
||||
struct bpf_map *map = bpf_object__find_map_by_name(obj, ".data.dtors");
|
||||
char data[256] = {};
|
||||
LIBBPF_OPTS(bpf_test_run_opts, opts,
|
||||
.data_in = data,
|
||||
.data_size_in = len,
|
||||
.repeat = 1,
|
||||
);
|
||||
int key = 0, val = 0;
|
||||
|
||||
if (!ASSERT_OK_PTR(prog, name) || !ASSERT_OK_PTR(map, "dtors"))
|
||||
return;
|
||||
ASSERT_OK(bpf_map__update_elem(map, &key, sizeof(key), &val, sizeof(val), 0), "clear");
|
||||
ASSERT_OK(bpf_prog_test_run_opts(bpf_program__fd(prog), &opts), "test_run");
|
||||
ASSERT_EQ(opts.retval, retval, "retval");
|
||||
ASSERT_OK(bpf_map__lookup_elem(map, &key, sizeof(key), &val, sizeof(val), 0), "lookup");
|
||||
ASSERT_EQ(val, dtors, "dtors");
|
||||
}
|
||||
|
||||
void test_unwind_cxx(void)
|
||||
{
|
||||
const char *file = "./unwind_cxx.bpf.o";
|
||||
struct bpf_object *obj;
|
||||
struct bpf_program *prog;
|
||||
int err = -EINVAL;
|
||||
|
||||
if (access(file, R_OK)) {
|
||||
printf("%s:SKIP:gcc doesn't build %s\n", __func__, file);
|
||||
test__skip();
|
||||
return;
|
||||
}
|
||||
if (!is_jit_enabled()) {
|
||||
printf("%s:SKIP:JIT is disabled\n", __func__);
|
||||
test__skip();
|
||||
return;
|
||||
}
|
||||
if (!llvm_lowers_unwinding(file)) {
|
||||
printf("%s:SKIP:llvm doesn't lower unwinding\n", __func__);
|
||||
test__skip();
|
||||
return;
|
||||
}
|
||||
obj = bpf_object__open_file(file, NULL);
|
||||
if (!ASSERT_OK_PTR(obj, "open"))
|
||||
return;
|
||||
prog = bpf_object__find_program_by_name(obj, "catches_aggregate");
|
||||
if (!ASSERT_OK_PTR(prog, "catches_aggregate"))
|
||||
goto out;
|
||||
bpf_program__set_autoload(prog, false);
|
||||
err = bpf_object__load(obj);
|
||||
if (err == -ENOTSUPP) {
|
||||
printf("%s:SKIP:JIT does not support unwinding\n", __func__);
|
||||
test__skip();
|
||||
goto out;
|
||||
}
|
||||
if (!ASSERT_OK(err, "load"))
|
||||
goto out;
|
||||
|
||||
/* inner's destructor, then middle's */
|
||||
run(obj, "catches", 64, 65, 12);
|
||||
run(obj, "catches", 200, 42, 12);
|
||||
run(obj, "does_not_catch", 64, 65, 12);
|
||||
run(obj, "does_not_catch", 200, 0, 12);
|
||||
out:
|
||||
bpf_object__close(obj);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
if (!test__start_subtest("aggregate_return"))
|
||||
return;
|
||||
obj = bpf_object__open_file(file, NULL);
|
||||
if (!ASSERT_OK_PTR(obj, "open_aggregate"))
|
||||
return;
|
||||
bpf_object__for_each_program(prog, obj)
|
||||
bpf_program__set_autoload(prog,
|
||||
strcmp(bpf_program__name(prog), "catches_aggregate") == 0);
|
||||
if (!ASSERT_OK(bpf_object__load(obj), "load_aggregate"))
|
||||
goto out_aggregate;
|
||||
run(obj, "catches_aggregate", 64, 71, 0);
|
||||
run(obj, "catches_aggregate", 200, 42, 0);
|
||||
out_aggregate:
|
||||
bpf_object__close(obj);
|
||||
}
|
||||
|
|
@ -130,6 +130,7 @@
|
|||
#include "verifier_typedef.skel.h"
|
||||
#include "verifier_uninit.skel.h"
|
||||
#include "verifier_unpriv.skel.h"
|
||||
#include "verifier_unwind.skel.h"
|
||||
#include "verifier_unpriv_perf.skel.h"
|
||||
#include "verifier_value_adj_spill.skel.h"
|
||||
#include "verifier_value.skel.h"
|
||||
|
|
@ -320,6 +321,7 @@ void test_verifier_tailcall_jit(void) { RUN(verifier_tailcall_jit); }
|
|||
void test_verifier_typedef(void) { RUN(verifier_typedef); }
|
||||
void test_verifier_uninit(void) { RUN(verifier_uninit); }
|
||||
void test_verifier_unpriv(void) { RUN(verifier_unpriv); }
|
||||
void test_verifier_unwind(void) { RUN(verifier_unwind); }
|
||||
void test_verifier_unpriv_perf(void) { RUN(verifier_unpriv_perf); }
|
||||
void test_verifier_value_adj_spill(void) { RUN(verifier_value_adj_spill); }
|
||||
void test_verifier_value(void) { RUN(verifier_value); }
|
||||
|
|
|
|||
43
tools/testing/selftests/bpf/progs/unwind_attach.c
Normal file
43
tools/testing/selftests/bpf/progs/unwind_attach.c
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include "../../../include/linux/filter.h"
|
||||
#include "bpf_misc.h"
|
||||
|
||||
/* The targets are global_unwinds() and global_never_unwinds() in verifier_unwind.c */
|
||||
|
||||
int fentry_cnt;
|
||||
|
||||
SEC("?fentry/global_unwinds")
|
||||
int BPF_PROG(fentry_unwind)
|
||||
{
|
||||
fentry_cnt++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("?fexit/global_unwinds")
|
||||
int BPF_PROG(fexit_unwind)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("?freplace/global_unwinds")
|
||||
unsigned long freplace_returns(void)
|
||||
{
|
||||
return 9;
|
||||
}
|
||||
|
||||
SEC("?freplace/global_unwinds")
|
||||
__naked unsigned long freplace_unwinds(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r0 = 3;"
|
||||
".8byte %[unwind_exit];"
|
||||
:
|
||||
: __imm_insn(unwind_exit,
|
||||
BPF_RAW_INSN(BPF_JMP | BPF_EXIT, 0, BPF_UNWIND_EXIT, 0, 0))
|
||||
);
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
150
tools/testing/selftests/bpf/progs/unwind_cxx.cpp
Normal file
150
tools/testing/selftests/bpf/progs/unwind_cxx.cpp
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* C++ exceptions. llvm lowers unwinding to two insns:
|
||||
* invoke foo unwind pad call foo; unwind_goto pad
|
||||
* resume r0 = 0; unwind_exit
|
||||
* so 'throw' is a jump or an exit, 'catch (...)' and the destructors that run on
|
||||
* the way are ordinary blocks.
|
||||
*
|
||||
* vmlinux.h and bpf_helpers.h are not C++.
|
||||
*/
|
||||
#define SEC(name) __attribute__((section(name), used))
|
||||
#define __ksym __attribute__((section(".ksyms")))
|
||||
#define __noinline __attribute__((noinline))
|
||||
#define __always_inline inline __attribute__((always_inline))
|
||||
|
||||
#ifdef __BPF_FEATURE_UNWIND
|
||||
|
||||
struct __sk_buff {
|
||||
unsigned int len;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
void bpf_rcu_read_lock(void) noexcept __ksym;
|
||||
void bpf_rcu_read_unlock(void) noexcept __ksym;
|
||||
void bpf_preempt_disable(void) noexcept __ksym;
|
||||
void bpf_preempt_enable(void) noexcept __ksym;
|
||||
|
||||
/* Starts an unwind. Not a kfunc: llvm lowers the call and it doesn't come back. */
|
||||
int _Unwind_RaiseException(void *exc);
|
||||
|
||||
/* All that is left of the C++ runtime. Nobody looks at the exception object. */
|
||||
static char exc_obj[16];
|
||||
|
||||
__always_inline void *__cxa_allocate_exception(unsigned long size) noexcept
|
||||
{
|
||||
return exc_obj;
|
||||
}
|
||||
|
||||
__always_inline void __cxa_throw(void *exc, void *tinfo, void (*dtor)(void *))
|
||||
{
|
||||
_Unwind_RaiseException(exc);
|
||||
}
|
||||
|
||||
__always_inline void *__cxa_begin_catch(void *exc) noexcept
|
||||
{
|
||||
return exc;
|
||||
}
|
||||
|
||||
__always_inline void __cxa_end_catch(void) noexcept
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/* One decimal digit per destructor, in the order they ran */
|
||||
volatile int dtors SEC(".data.dtors");
|
||||
|
||||
/* The verifier insists that both are undone on every path out of the prog */
|
||||
struct rcu_guard {
|
||||
__always_inline rcu_guard()
|
||||
{
|
||||
bpf_rcu_read_lock();
|
||||
}
|
||||
__always_inline ~rcu_guard()
|
||||
{
|
||||
bpf_rcu_read_unlock();
|
||||
dtors = dtors * 10 + 1;
|
||||
}
|
||||
};
|
||||
|
||||
struct plain_rcu_guard {
|
||||
__always_inline plain_rcu_guard()
|
||||
{
|
||||
bpf_rcu_read_lock();
|
||||
}
|
||||
__always_inline ~plain_rcu_guard()
|
||||
{
|
||||
bpf_rcu_read_unlock();
|
||||
}
|
||||
};
|
||||
|
||||
struct preempt_guard {
|
||||
__always_inline preempt_guard()
|
||||
{
|
||||
bpf_preempt_disable();
|
||||
}
|
||||
__always_inline ~preempt_guard()
|
||||
{
|
||||
bpf_preempt_enable();
|
||||
dtors = dtors * 10 + 2;
|
||||
}
|
||||
};
|
||||
|
||||
static __noinline int inner(int len)
|
||||
{
|
||||
rcu_guard guard;
|
||||
|
||||
if (len > 100)
|
||||
throw 1;
|
||||
return len;
|
||||
}
|
||||
|
||||
static __noinline int middle(int len)
|
||||
{
|
||||
preempt_guard guard;
|
||||
|
||||
return inner(len) + 1;
|
||||
}
|
||||
|
||||
using u128 = unsigned __int128;
|
||||
|
||||
/* Global subprogs returning 16 bytes use R0:R2, including unwind exits. */
|
||||
extern "C" __noinline u128 aggregate_return(int len)
|
||||
{
|
||||
plain_rcu_guard guard;
|
||||
|
||||
if (len > 100)
|
||||
throw 1;
|
||||
return (static_cast<u128>(len) << 64) | 7;
|
||||
}
|
||||
|
||||
extern "C" SEC("tc") int catches(struct __sk_buff *skb)
|
||||
{
|
||||
try {
|
||||
return middle(skb->len);
|
||||
} catch (...) {
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
|
||||
/* The exception leaves the prog: it returns 0 */
|
||||
extern "C" SEC("tc") int does_not_catch(struct __sk_buff *skb)
|
||||
{
|
||||
return middle(skb->len);
|
||||
}
|
||||
|
||||
extern "C" SEC("tc") int catches_aggregate(struct __sk_buff *skb)
|
||||
{
|
||||
try {
|
||||
u128 value = aggregate_return(skb->len);
|
||||
|
||||
return static_cast<unsigned long>(value) +
|
||||
static_cast<unsigned long>(value >> 64);
|
||||
} catch (...) {
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
1211
tools/testing/selftests/bpf/progs/verifier_unwind.c
Normal file
1211
tools/testing/selftests/bpf/progs/verifier_unwind.c
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -1546,7 +1546,8 @@ void run_subtest(struct test_loader *tester,
|
|||
}
|
||||
|
||||
err = do_prog_test_run(bpf_program__fd(tprog), &retval,
|
||||
bpf_program__type(tprog) == BPF_PROG_TYPE_SYSCALL ? true : false,
|
||||
bpf_program__type(tprog) == BPF_PROG_TYPE_SYSCALL ||
|
||||
bpf_program__type(tprog) == BPF_PROG_TYPE_RAW_TRACEPOINT,
|
||||
spec->linear_sz);
|
||||
if (!err && retval != subspec->retval && subspec->retval != POINTER_VALUE) {
|
||||
PRINT_FAIL("Unexpected retval: %d != %d\n", retval, subspec->retval);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user