// SPDX-License-Identifier: GPL-2.0 #include #include #include "../../../include/linux/filter.h" #include "bpf_misc.h" /* * llvm lowers unwinding to two insns: * invoke foo unwind pad call foo; unwind_goto pad * resume unwind_exit * The tests spell them out as raw insns, so they don't depend on llvm version. * The argument of unwind_goto() is the number of insns to skip. */ #define unwind_goto(off) ".8byte %[insn_unwind_goto_" #off "];" #define unwind_exit ".8byte %[insn_unwind_exit];" #define __imm_unwind_goto(off) \ __imm_insn(insn_unwind_goto_##off, \ BPF_RAW_INSN(BPF_JMP | BPF_JCOND, 0, BPF_UNWIND_GOTO, off, 0)) #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_rcu_read_lock(void) __ksym; extern void bpf_rcu_read_unlock(void) __ksym; extern void bpf_throw(__u64 cookie) __ksym; struct { __uint(type, BPF_MAP_TYPE_PROG_ARRAY); __uint(max_entries, 1); __uint(key_size, sizeof(int)); __uint(value_size, sizeof(int)); } prog_array SEC(".maps"); struct timer_elem { struct bpf_timer t; }; struct { __uint(type, BPF_MAP_TYPE_ARRAY); __uint(max_entries, 1); __type(key, int); __type(value, struct timer_elem); } timer_map SEC(".maps"); /* The verifier doesn't know what is in there */ unsigned long zero; unsigned long one = 1; /* * The verifier follows a static subprog, so it knows which way it leaves when * it knows the argument. To have both ways at run time the tests pass skb->len, * which is 64, or skb->len - 64. * * r1 == 0: returns 7, otherwise unwinds with r0 = 5 */ __naked __noinline __used static unsigned long may_unwind(void) { asm volatile ( "if r1 != 0 goto 1f;" "r0 = 7;" "exit;" "1:" "r0 = 5;" unwind_exit : : __imm_unwind_exit ); } /* owns nothing: its pad only carries on */ __naked __noinline __used static unsigned long passes_on(void) { asm volatile ( "call may_unwind;" unwind_goto(2) "r0 += 1;" "exit;" unwind_exit : : __imm_unwind_goto(2), __imm_unwind_exit ); } __naked __noinline __used static unsigned long always_unwinds(void) { asm volatile ( "r0 = 5;" unwind_exit : : __imm_unwind_exit ); } __naked __noinline __used static unsigned long never_unwinds(void) { asm volatile ( "r0 = 7;" "exit;" ); } /* Both can unwind. This one returns 7. */ __naked __noinline __used unsigned long global_returns(void) { asm volatile ( "r1 = %[zero] ll;" "r1 = *(u64 *)(r1 + 0);" "if r1 != 0 goto 1f;" "r0 = 7;" "exit;" "1:" "r0 = 5;" unwind_exit : : __imm_addr(zero), __imm_unwind_exit ); } /* This one unwinds with r0 = 5 */ __naked __noinline __used unsigned long global_unwinds(void) { asm volatile ( "r1 = %[one] ll;" "r1 = *(u64 *)(r1 + 0);" "if r1 != 0 goto 1f;" "r0 = 7;" "exit;" "1:" "r0 = 5;" unwind_exit : : __imm_addr(one), __imm_unwind_exit ); } SEC("tc") __description("unwind: the callee returns") __arch_x86_64 __arch_arm64 __load_if_JITed() __success __retval(7) __naked int callee_returns(void) { asm volatile ( "r1 = *(u32 *)(r1 + %[len]);" "r1 -= 64;" "call may_unwind;" unwind_goto(1) "exit;" "r0 = 42;" "exit;" : : __imm_const(len, offsetof(struct __sk_buff, len)), __imm_unwind_goto(1) : __clobber_all ); } SEC("tc") __description("unwind: the callee unwinds") __arch_x86_64 __arch_arm64 __load_if_JITed() __success __retval(42) __naked int callee_unwinds(void) { asm volatile ( "r1 = *(u32 *)(r1 + %[len]);" "call may_unwind;" unwind_goto(1) "exit;" "r0 = 42;" "exit;" : : __imm_const(len, offsetof(struct __sk_buff, len)), __imm_unwind_goto(1) : __clobber_all ); } SEC("tc") __description("unwind: return through two frames") __arch_x86_64 __arch_arm64 __load_if_JITed() __success __retval(8) __naked int two_frames_return(void) { asm volatile ( "r1 = *(u32 *)(r1 + %[len]);" "r1 -= 64;" "call passes_on;" unwind_goto(1) "exit;" "r0 = 42;" "exit;" : : __imm_const(len, offsetof(struct __sk_buff, len)), __imm_unwind_goto(1) : __clobber_all ); } /* unwind_exit returns r0 like exit does */ SEC("tc") __description("unwind: unwind through two frames, r0 reaches the pad") __arch_x86_64 __arch_arm64 __load_if_JITed() __success __retval(15) __naked int two_frames_unwind(void) { asm volatile ( "r1 = *(u32 *)(r1 + %[len]);" "call passes_on;" unwind_goto(1) "exit;" "r0 += 10;" "exit;" : : __imm_const(len, offsetof(struct __sk_buff, len)), __imm_unwind_goto(1) : __clobber_all ); } SEC("tc") __description("unwind: r6-r9 and the stack are intact in the pad") __arch_x86_64 __arch_arm64 __load_if_JITed() __success __retval(1111) __naked int pad_sees_callee_saved(void) { asm volatile ( "r1 = *(u32 *)(r1 + %[len]);" "r6 = 1;" "r7 = 10;" "r8 = 100;" "r9 = 400;" "*(u64 *)(r10 - 8) = 600;" "call passes_on;" unwind_goto(2) "r0 = 0;" "exit;" "r0 = r6;" "r0 += r7;" "r0 += r8;" "r0 += r9;" "r1 = *(u64 *)(r10 - 8);" "r0 += r1;" "exit;" : : __imm_const(len, offsetof(struct __sk_buff, len)), __imm_unwind_goto(2) : __clobber_all ); } SEC("tc") __description("unwind: r1-r5 are not readable in the pad") __arch_x86_64 __arch_arm64 __load_if_JITed() __failure __msg("R3 !read_ok") __naked int pad_reads_r3(void) { asm volatile ( "r3 = 0;" "call always_unwinds;" unwind_goto(1) "exit;" "r0 = r3;" "exit;" : : __imm_unwind_goto(1) : __clobber_all ); } SEC("tc") __description("unwind: unwind_exit of the main prog is exit") __arch_x86_64 __arch_arm64 __load_if_JITed() __success __retval(3) __naked int main_prog_unwinds(void) { asm volatile ( "r0 = 3;" unwind_exit : : __imm_unwind_exit : __clobber_all ); } SEC("tc") __description("unwind: unwind_exit of the main prog needs r0") __arch_x86_64 __arch_arm64 __load_if_JITed() __failure __msg("R0 !read_ok") __naked int main_prog_unwinds_no_r0(void) { asm volatile ( unwind_exit : : __imm_unwind_exit : __clobber_all ); } /* * What follows unwind_goto is dead code. unwind_exit still has to find the pad, * and the cpu predicts that the callee returns: there is a trap for that. */ SEC("tc") __description("unwind: the callee never returns") __arch_x86_64 __arch_arm64 __load_if_JITed() __success __retval(42) __xlated("0: call pc+4") __xlated("1: unwind_goto pc+1") __xlated("2: goto pc-1") __xlated("3: r0 = 42") __xlated("4: exit") __naked int callee_never_returns(void) { asm volatile ( "call always_unwinds;" unwind_goto(2) "r0 = 1;" "exit;" "r0 = 42;" "exit;" : : __imm_unwind_goto(2) : __clobber_all ); } /* The pad is dead code and goes away together with unwind_goto */ SEC("tc") __description("unwind: the callee never unwinds") __arch_x86_64 __arch_arm64 __load_if_JITed() __success __retval(7) __xlated("0: call pc+1") __xlated("1: exit") __xlated("2: r0 = 7") __xlated("3: exit") __naked int callee_never_unwinds(void) { asm volatile ( "call never_unwinds;" unwind_goto(1) "exit;" "r0 = *(u64 *)(r0 + 0);" "exit;" : : __imm_unwind_goto(1) : __clobber_all ); } SEC("tc") __description("unwind: the pad drops what the frame owns") __arch_x86_64 __arch_arm64 __load_if_JITed() __success __retval(42) __naked int pad_unlocks(void) { asm volatile ( "r6 = *(u32 *)(r1 + %[len]);" "call %[bpf_rcu_read_lock];" "r1 = r6;" "call may_unwind;" unwind_goto(3) "call %[bpf_rcu_read_unlock];" "r0 = 0;" "exit;" "call %[bpf_rcu_read_unlock];" "r0 = 42;" "exit;" : : __imm_const(len, offsetof(struct __sk_buff, len)), __imm(bpf_rcu_read_lock), __imm(bpf_rcu_read_unlock), __imm_unwind_goto(3) : __clobber_all ); } SEC("tc") __description("unwind: the pad is checked like any other code") __arch_x86_64 __arch_arm64 __load_if_JITed() __failure __msg("in main prog cannot be used inside bpf_rcu_read_lock-ed region") __naked int pad_does_not_unlock(void) { asm volatile ( "r6 = *(u32 *)(r1 + %[len]);" "call %[bpf_rcu_read_lock];" "r1 = r6;" "call may_unwind;" unwind_goto(3) "call %[bpf_rcu_read_unlock];" "r0 = 0;" "exit;" "r0 = 42;" "exit;" : : __imm_const(len, offsetof(struct __sk_buff, len)), __imm(bpf_rcu_read_lock), __imm(bpf_rcu_read_unlock), __imm_unwind_goto(3) : __clobber_all ); } /* Leaves either way with the same registers */ __naked __noinline __used static unsigned long same_regs(void) { asm volatile ( "r0 = 0;" "if r1 != 0 goto 1f;" "exit;" "1:" unwind_exit : : __imm_unwind_exit ); } /* * After the call the two states differ only in the way the callee left. * The second one must not be pruned, or nobody looks at the pad. */ SEC("tc") __description("unwind: the way the callee left is a part of the state") __arch_x86_64 __arch_arm64 __load_if_JITed() __failure __msg("R0 invalid mem access 'scalar'") __flag(BPF_F_TEST_STATE_FREQ) __naked int no_pruning_after_the_call(void) { asm volatile ( "r1 = *(u32 *)(r1 + %[len]);" "call same_regs;" unwind_goto(1) "exit;" "r0 = *(u64 *)(r0 + 0);" "exit;" : : __imm_const(len, offsetof(struct __sk_buff, len)), __imm_unwind_goto(1) : __clobber_all ); } /* Returns ctx, or unwinds with a number in r0 */ __naked __noinline __used static unsigned long ctx_or_unwind(void) { asm volatile ( "r2 = *(u32 *)(r1 + %[len]);" "if r2 > 100 goto 1f;" "r0 = r1;" "exit;" "1:" "r0 = 0;" unwind_exit : : __imm_const(len, offsetof(struct __sk_buff, len)), __imm_unwind_exit ); } /* * unwind_exit changes its return address, so the cpu mispredicts the return and * runs what follows unwind_goto with r0 of unwind_exit. */ SEC("socket") __description("unwind: what follows unwind_goto runs speculatively") __arch_x86_64 __arch_arm64 __load_if_JITed() __success __retval(50) /* 64 without the ethernet header */ __success_unpriv __retval_unpriv(50) __caps_unpriv(CAP_BPF) #ifdef SPEC_V1 __xlated_unpriv("unwind_goto pc+{{.*}}") __xlated_unpriv("nospec") /* inserted to prevent `R0 invalid mem access 'scalar'` */ __xlated_unpriv("r0 = *(u32 *)(r0 +{{.*}})") #endif __naked int speculative_fall_through(void) { asm volatile ( "call ctx_or_unwind;" unwind_goto(2) "r0 = *(u32 *)(r0 + %[len]);" "exit;" "r0 = 42;" "exit;" : : __imm_const(len, offsetof(struct __sk_buff, len)), __imm_unwind_goto(2) : __clobber_all ); } SEC("tc") __description("unwind: a global subprog returns") __arch_x86_64 __arch_arm64 __load_if_JITed() __success __retval(7) __naked int calls_global_returns(void) { asm volatile ( "call global_returns;" unwind_goto(1) "exit;" "r0 = 42;" "exit;" : : __imm_unwind_goto(1) : __clobber_all ); } SEC("tc") __description("unwind: a global subprog unwinds") __arch_x86_64 __arch_arm64 __load_if_JITed() __success __retval(15) __naked int calls_global_unwinds(void) { asm volatile ( "call global_unwinds;" unwind_goto(1) "exit;" "r0 += 10;" "exit;" : : __imm_unwind_goto(1) : __clobber_all ); } /* The verifier doesn't look inside */ SEC("tc") __description("unwind: the pad of a call of a global subprog is always checked") __arch_x86_64 __arch_arm64 __load_if_JITed() __failure __msg("R0 invalid mem access 'scalar'") __naked int global_pad_is_checked(void) { asm volatile ( "call global_returns;" unwind_goto(1) "exit;" "r0 = *(u64 *)(r0 + 0);" "exit;" : : __imm_unwind_goto(1) : __clobber_all ); } __naked __noinline __used unsigned long global_never_unwinds(void) { asm volatile ( "r0 = 7;" "exit;" ); } SEC("tc") __description("unwind: a global subprog that cannot unwind") __arch_x86_64 __arch_arm64 __load_if_JITed() __success __retval(7) __naked int calls_global_never_unwinds(void) { asm volatile ( "call global_never_unwinds;" "exit;" ::: __clobber_all ); } /* * 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 __arch_arm64 __load_if_JITed() __failure __msg("no unwind_goto after the call of global_returns()") __naked int global_no_unwind_goto(void) { asm volatile ( "call global_returns;" "exit;" ::: __clobber_all ); } SEC("tc") __description("unwind: no unwind_goto after a call of a static subprog") __arch_x86_64 __arch_arm64 __load_if_JITed() __failure __msg("unwind_exit, but no unwind_goto after the call at insn 1") __naked int static_no_unwind_goto(void) { asm volatile ( "r1 = *(u32 *)(r1 + %[len]);" "call may_unwind;" "exit;" : : __imm_const(len, offsetof(struct __sk_buff, len)) : __clobber_all ); } /* The verifier sees that it doesn't get to unwind_exit */ SEC("tc") __description("unwind: no unwind_goto is fine when the callee doesn't unwind") __arch_x86_64 __arch_arm64 __load_if_JITed() __success __retval(7) __naked int static_does_not_get_there(void) { asm volatile ( "r1 = 0;" "call may_unwind;" "exit;" ::: __clobber_all ); } SEC("tc") __description("unwind: callx, the callee returns") __arch_x86_64 __arch_arm64 __load_if_JITed() __success __retval(7) __naked int callx_returns(void) { asm volatile ( "r1 = *(u32 *)(r1 + %[len]);" "r1 -= 64;" "r6 = %[may_unwind] ll;" "callx r6;" unwind_goto(1) "exit;" "r0 = 42;" "exit;" : : __imm_const(len, offsetof(struct __sk_buff, len)), __imm_addr(may_unwind), __imm_unwind_goto(1) : __clobber_all ); } SEC("tc") __description("unwind: callx, the callee unwinds") __arch_x86_64 __arch_arm64 __load_if_JITed() __success __retval(42) __naked int callx_unwinds(void) { asm volatile ( "r1 = *(u32 *)(r1 + %[len]);" "r6 = %[may_unwind] ll;" "callx r6;" unwind_goto(1) "exit;" "r0 = 42;" "exit;" : : __imm_const(len, offsetof(struct __sk_buff, len)), __imm_addr(may_unwind), __imm_unwind_goto(1) : __clobber_all ); } SEC("tc") __description("unwind: unwind_goto after a plain insn") __arch_x86_64 __arch_arm64 __load_if_JITed() __failure __msg("unwind_goto at insn 1 doesn't follow a call of a subprog") __naked int unwind_goto_after_mov(void) { asm volatile ( "r0 = 0;" unwind_goto(0) "exit;" : : __imm_unwind_goto(0) : __clobber_all ); } SEC("tc") __description("unwind: unwind_goto is the first insn") __arch_x86_64 __arch_arm64 __load_if_JITed() __failure __msg("unwind_goto at insn 0 doesn't follow a call of a subprog") __naked int unwind_goto_first(void) { asm volatile ( unwind_goto(0) "r0 = 0;" "exit;" : : __imm_unwind_goto(0) : __clobber_all ); } SEC("tc") __description("unwind: unwind_goto after a call of a helper") __arch_x86_64 __arch_arm64 __load_if_JITed() __failure __msg("unwind_goto at insn 1 doesn't follow a call of a subprog") __naked int unwind_goto_after_helper(void) { asm volatile ( "call %[bpf_get_prandom_u32];" unwind_goto(0) "exit;" : : __imm(bpf_get_prandom_u32), __imm_unwind_goto(0) : __clobber_all ); } SEC("tc") __description("unwind: unwind_goto after a call of a kfunc") __arch_x86_64 __arch_arm64 __load_if_JITed() __failure __msg("unwind_goto at insn 1 doesn't follow a call of a subprog") __naked int unwind_goto_after_kfunc(void) { asm volatile ( "call %[bpf_rcu_read_lock];" unwind_goto(0) "call %[bpf_rcu_read_unlock];" "r0 = 0;" "exit;" : : __imm(bpf_rcu_read_lock), __imm(bpf_rcu_read_unlock), __imm_unwind_goto(0) : __clobber_all ); } SEC("tc") __description("unwind: jump to unwind_goto") __arch_x86_64 __arch_arm64 __load_if_JITed() __failure __msg("jump to unwind_goto at insn 3") __naked int jump_to_unwind_goto(void) { asm volatile ( "r0 = 0;" "if r1 != 0 goto 1f;" "call never_unwinds;" "1:" unwind_goto(0) "exit;" : : __imm_unwind_goto(0) : __clobber_all ); } SEC("tc") __description("unwind: reserved fields of unwind_goto") __failure __msg("invalid may_goto imm 1") __naked int unwind_goto_reserved(void) { asm volatile ( "call never_unwinds;" ".8byte %[bad];" "exit;" : : __imm_insn(bad, BPF_RAW_INSN(BPF_JMP | BPF_JCOND, 0, BPF_UNWIND_GOTO, 0, 1)) : __clobber_all ); } SEC("tc") __description("unwind: unknown kind of jcond") __failure __msg("invalid may_goto imm 0") __naked int jcond_unknown(void) { asm volatile ( "call never_unwinds;" ".8byte %[bad];" "exit;" : : __imm_insn(bad, BPF_RAW_INSN(BPF_JMP | BPF_JCOND, 0, BPF_UNWIND_GOTO + 1, 0, 0)) : __clobber_all ); } SEC("tc") __description("unwind: unknown kind of exit") __failure __msg("BPF_EXIT uses reserved fields") __naked int exit_unknown(void) { asm volatile ( "r0 = 0;" ".8byte %[bad];" : : __imm_insn(bad, BPF_RAW_INSN(BPF_JMP | BPF_EXIT, 0, BPF_UNWIND_EXIT + 1, 0, 0)) : __clobber_all ); } /* r1 is the index: unwinds in the second iteration */ __naked __noinline __used static unsigned long cb_unwinds(void) { asm volatile ( "r0 = 0;" "if r1 != 0 goto 1f;" "exit;" "1:" unwind_exit : : __imm_unwind_exit ); } SEC("tc") __description("unwind: callback unwinds") __arch_x86_64 __arch_arm64 __load_if_JITed() __failure __msg("callback cannot unwind") __naked int callback_unwinds(void) { asm volatile ( "r1 = 3;" "r2 = %[cb_unwinds];" "r3 = 0;" "r4 = 0;" "call %[bpf_loop];" "exit;" : : __imm_ptr(cb_unwinds), __imm(bpf_loop) : __clobber_common ); } __naked __noinline __used static unsigned long async_cb_unwinds(void) { asm volatile ( "r0 = 0;" unwind_exit : : __imm_unwind_exit ); } /* The kernel calls it. It's the top frame like the main prog: unwind_exit is exit. */ SEC("tc") __description("unwind: async callback unwinds") __arch_x86_64 __arch_arm64 __load_if_JITed() __success __retval(0) int async_callback_unwinds(void *ctx) { struct timer_elem *val; int key = 0; val = bpf_map_lookup_elem(&timer_map, &key); if (!val) return 0; bpf_timer_init(&val->t, &timer_map, 0); bpf_timer_set_callback(&val->t, (void *)async_cb_unwinds); bpf_timer_start(&val->t, 0, 0); return 0; } /* Not naked: the argument has to have a name in BTF */ __noinline int exception_cb_unwinds(__u64 cookie) { if (cookie) asm volatile ("r0 = 7;" unwind_exit :: __imm_unwind_exit : "r0"); return 0; } SEC("tc") __description("unwind: exception callback unwinds") __arch_x86_64 __arch_arm64 __load_if_JITed() __attribute__((btf_decl_tag("exception_callback:exception_cb_unwinds"))) __success __retval(7) int exception_callback_unwinds(struct __sk_buff *skb) { bpf_throw(skb->len); return 0; } /* What it calls can unwind as long as it doesn't let it out */ __naked __noinline __used static unsigned long cb_catches(void) { asm volatile ( "call may_unwind;" unwind_goto(0) "r0 = 0;" "exit;" : : __imm_unwind_goto(0) ); } SEC("tc") __description("unwind: callback catches") __arch_x86_64 __arch_arm64 __load_if_JITed() __success __retval(3) __naked int callback_catches(void) { asm volatile ( "r1 = 3;" "r2 = %[cb_catches];" "r3 = 0;" "r4 = 0;" "call %[bpf_loop];" "exit;" : : __imm_ptr(cb_catches), __imm(bpf_loop) : __clobber_common ); } /* Both leave the subprog like exit does */ __naked __noinline __used static unsigned long does_tail_call(void) { asm volatile ( "r6 = *(u32 *)(r1 + %[len]);" "r2 = %[prog_array] ll;" "r3 = 0;" "call %[bpf_tail_call];" "r0 = 7;" "if r6 == 64 goto 1f;" unwind_exit "1:" "exit;" : : __imm_const(len, offsetof(struct __sk_buff, len)), __imm(bpf_tail_call), __imm_addr(prog_array), __imm_unwind_exit ); } SEC("tc") __description("unwind: tail call in a subprog that can unwind") __arch_x86_64 __arch_arm64 __load_if_JITed() __success __retval(7) __naked int tail_call_in_subprog(void) { asm volatile ( "call does_tail_call;" unwind_goto(1) "exit;" "r0 = 42;" "exit;" : : __imm_unwind_goto(1) : __clobber_all ); } /* The packet is shorter than that: ld_abs leaves the subprog with r0 = 0 */ __naked __noinline __used static unsigned long does_ld_abs(void) { asm volatile ( "r6 = r1;" "r0 = *(u8 *)skb[1000];" "r0 = 5;" unwind_exit : : __imm_unwind_exit ); } SEC("tc") __description("unwind: ld_abs in a subprog that can unwind") __arch_x86_64 __arch_arm64 __load_if_JITed() __success __retval(7) __naked int ld_abs_in_subprog(void) { asm volatile ( "call does_ld_abs;" unwind_goto(2) "r0 += 7;" "exit;" "r0 = 42;" "exit;" : : __imm_unwind_goto(2) : __clobber_all ); } /* * JITs don't emit anything for unwind_goto. It's an extable entry, where * unwind_exit looks its return address up. * With KASAN x86-64 JIT wraps a load in pushes, a call and pops. 'r2 = 0x1234' * is where the patterns start, so that they don't take that call for the one * of the subprog. */ SEC("tc") __description("unwind: jit") __success __retval(42) __load_if_JITed() __arch_x86_64 __jited(" movl $0x1234, %esi") __jited("...") /* call depth accounting */ __jited(" callq 0x{{.*}}") __jited(" movl $0x7, %eax") __jited("...") __jited(" movl $0x5, %eax") __jited(" pushq %rax") __jited(" pushq %rsi") __jited(" movq 0x8(%rbp), %rdi") __jited("...") /* call depth accounting */ __jited(" callq 0x{{.*}}") __jited(" movq %rax, 0x8(%rbp)") __jited(" popq %rsi") __jited(" popq %rax") __arch_arm64 __jited(" mov x1, #0x1234") __jited(" bl {{.*}}") __jited(" mov x8, x0") __jited(" mov x8, #0x7") __jited("...") __jited(" mov x8, #0x5") __jited(" stp x8, x1, [sp, #-0x10]!") __jited(" ldr x0, [x29, #0x8]") __jited("...") __jited(" mov x10, x0") __jited(" ldp x8, x1, [sp], #0x10") __jited("...") __jited(" add x30, x30, x10") __jited(" ret") __naked int jit(void) { asm volatile ( "r1 = *(u32 *)(r1 + %[len]);" "r2 = 0x1234;" "call may_unwind;" unwind_goto(2) "r0 = 7;" "exit;" "r0 = 42;" "exit;" : : __imm_const(len, offsetof(struct __sk_buff, len)), __imm_unwind_goto(2) : __clobber_all ); } /* r1 == 0: returns 7, otherwise unwinds with r0 = 5 */ __naked __noinline __used static unsigned long may_unwind_big_stack(void) { asm volatile ( "*(u64 *)(r10 - 200) = r1;" "if r1 != 0 goto 1f;" "r0 = 7;" "exit;" "1:" "r0 = 5;" unwind_exit : : __imm_unwind_exit ); } /* * x86-64 keeps the pointer to the private stack in r9 and saves it around a call. * The value that is read back in the pad is there only when r9 is restored. */ SEC("raw_tp") __description("unwind: private stack, the callee unwinds") __success __retval(47) __load_if_JITed() __arch_x86_64 __jited(" movl $0x1234, %esi") /* past the load, see above */ __jited(" pushq %r9") __jited("...") /* call depth accounting */ __jited(" callq 0x{{.*}}") __jited(" popq %r9") __jited(" jmp L{{.*}}") __jited(" popq %r9") __jited(" jmp L{{.*}}") __arch_arm64 __naked int private_stack_unwinds(void) { asm volatile ( "*(u64 *)(r10 - 200) = 42;" "r1 = %[one] ll;" "r1 = *(u64 *)(r1 + 0);" "r2 = 0x1234;" "call may_unwind_big_stack;" unwind_goto(1) "exit;" "r1 = *(u64 *)(r10 - 200);" "r0 += r1;" "exit;" : : __imm_addr(one), __imm_unwind_goto(1) : __clobber_all ); } SEC("raw_tp") __description("unwind: private stack, the callee returns") __success __retval(49) __load_if_JITed() __arch_x86_64 __arch_arm64 __naked int private_stack_returns(void) { asm volatile ( "*(u64 *)(r10 - 200) = 42;" "r1 = %[zero] ll;" "r1 = *(u64 *)(r1 + 0);" "call may_unwind_big_stack;" unwind_goto(3) "r1 = *(u64 *)(r10 - 200);" "r0 += r1;" "exit;" "r0 = 0;" "exit;" : : __imm_addr(zero), __imm_unwind_goto(3) : __clobber_all ); } /* * BTF FUNC records are not generated for kfuncs referenced * from inline assembly. These records are necessary for * libbpf to link the program. The function below is a hack * to ensure that BTF FUNC records are generated. */ void __kfunc_btf_root(void) { bpf_rcu_read_lock(); bpf_rcu_read_unlock(); } char _license[] SEC("license") = "GPL";