Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Zi Shen Lim | 3978 | 30.64% | 8 | 7.34% |
Xu Kuohai | 3917 | 30.17% | 14 | 12.84% |
Puranjay Mohan | 1431 | 11.02% | 11 | 10.09% |
Hou Tao | 1073 | 8.26% | 6 | 5.50% |
Daniel Borkmann | 899 | 6.92% | 19 | 17.43% |
Jean-Philippe Brucker | 385 | 2.97% | 1 | 0.92% |
Alexei Starovoitov | 304 | 2.34% | 3 | 2.75% |
Luke Nelson | 277 | 2.13% | 2 | 1.83% |
Jiong Wang | 162 | 1.25% | 1 | 0.92% |
Yang Shi | 123 | 0.95% | 4 | 3.67% |
Song Liu | 89 | 0.69% | 2 | 1.83% |
Ilias Apalodimas | 50 | 0.39% | 1 | 0.92% |
Florent Revest | 46 | 0.35% | 1 | 0.92% |
Xi Wang | 38 | 0.29% | 3 | 2.75% |
Mark Brown | 26 | 0.20% | 2 | 1.83% |
Martin KaFai Lau | 22 | 0.17% | 3 | 2.75% |
Yonghong Song | 21 | 0.16% | 1 | 0.92% |
Ilya Leoshkevich | 20 | 0.15% | 1 | 0.92% |
Ard Biesheuvel | 17 | 0.13% | 2 | 1.83% |
Catalin Marinas | 16 | 0.12% | 1 | 0.92% |
Mark Rutland | 15 | 0.12% | 3 | 2.75% |
Jakub Sitnicki | 11 | 0.08% | 1 | 0.92% |
Lorenz Bauer | 9 | 0.07% | 1 | 0.92% |
Tiezhu Yang | 8 | 0.06% | 3 | 2.75% |
Jiang Liu | 7 | 0.05% | 1 | 0.92% |
Eric Dumazet | 6 | 0.05% | 1 | 0.92% |
Jerin Jacob | 6 | 0.05% | 1 | 0.92% |
Kumar Kartikeya Dwivedi | 4 | 0.03% | 1 | 0.92% |
Christophe Leroy | 4 | 0.03% | 1 | 0.92% |
Luc Van Oostenryck | 3 | 0.02% | 1 | 0.92% |
Russell King | 3 | 0.02% | 1 | 0.92% |
Laura Abbott | 3 | 0.02% | 1 | 0.92% |
Brendan Jackman | 2 | 0.02% | 1 | 0.92% |
Alexander Duyck | 2 | 0.02% | 1 | 0.92% |
Thomas Gleixner | 2 | 0.02% | 1 | 0.92% |
Aijun Sun | 2 | 0.02% | 1 | 0.92% |
Christoph Hellwig | 1 | 0.01% | 1 | 0.92% |
Nathan Chancellor | 1 | 0.01% | 1 | 0.92% |
Artem Savkov | 1 | 0.01% | 1 | 0.92% |
Total | 12984 | 109 |
// SPDX-License-Identifier: GPL-2.0-only /* * BPF JIT compiler for ARM64 * * Copyright (C) 2014-2016 Zi Shen Lim <zlim.lnx@gmail.com> */ #define pr_fmt(fmt) "bpf_jit: " fmt #include <linux/bitfield.h> #include <linux/bpf.h> #include <linux/filter.h> #include <linux/memory.h> #include <linux/printk.h> #include <linux/slab.h> #include <asm/asm-extable.h> #include <asm/byteorder.h> #include <asm/cacheflush.h> #include <asm/debug-monitors.h> #include <asm/insn.h> #include <asm/patching.h> #include <asm/set_memory.h> #include "bpf_jit.h" #define TMP_REG_1 (MAX_BPF_JIT_REG + 0) #define TMP_REG_2 (MAX_BPF_JIT_REG + 1) #define TCALL_CNT (MAX_BPF_JIT_REG + 2) #define TMP_REG_3 (MAX_BPF_JIT_REG + 3) #define FP_BOTTOM (MAX_BPF_JIT_REG + 4) #define ARENA_VM_START (MAX_BPF_JIT_REG + 5) #define check_imm(bits, imm) do { \ if ((((imm) > 0) && ((imm) >> (bits))) || \ (((imm) < 0) && (~(imm) >> (bits)))) { \ pr_info("[%2d] imm=%d(0x%x) out of range\n", \ i, imm, imm); \ return -EINVAL; \ } \ } while (0) #define check_imm19(imm) check_imm(19, imm) #define check_imm26(imm) check_imm(26, imm) /* Map BPF registers to A64 registers */ static const int bpf2a64[] = { /* return value from in-kernel function, and exit value from eBPF */ [BPF_REG_0] = A64_R(7), /* arguments from eBPF program to in-kernel function */ [BPF_REG_1] = A64_R(0), [BPF_REG_2] = A64_R(1), [BPF_REG_3] = A64_R(2), [BPF_REG_4] = A64_R(3), [BPF_REG_5] = A64_R(4), /* callee saved registers that in-kernel function will preserve */ [BPF_REG_6] = A64_R(19), [BPF_REG_7] = A64_R(20), [BPF_REG_8] = A64_R(21), [BPF_REG_9] = A64_R(22), /* read-only frame pointer to access stack */ [BPF_REG_FP] = A64_R(25), /* temporary registers for BPF JIT */ [TMP_REG_1] = A64_R(10), [TMP_REG_2] = A64_R(11), [TMP_REG_3] = A64_R(12), /* tail_call_cnt */ [TCALL_CNT] = A64_R(26), /* temporary register for blinding constants */ [BPF_REG_AX] = A64_R(9), [FP_BOTTOM] = A64_R(27), /* callee saved register for kern_vm_start address */ [ARENA_VM_START] = A64_R(28), }; struct jit_ctx { const struct bpf_prog *prog; int idx; int epilogue_offset; int *offset; int exentry_idx; __le32 *image; __le32 *ro_image; u32 stack_size; int fpb_offset; u64 user_vm_start; }; struct bpf_plt { u32 insn_ldr; /* load target */ u32 insn_br; /* branch to target */ u64 target; /* target value */ }; #define PLT_TARGET_SIZE sizeof_field(struct bpf_plt, target) #define PLT_TARGET_OFFSET offsetof(struct bpf_plt, target) static inline void emit(const u32 insn, struct jit_ctx *ctx) { if (ctx->image != NULL) ctx->image[ctx->idx] = cpu_to_le32(insn); ctx->idx++; } static inline void emit_a64_mov_i(const int is64, const int reg, const s32 val, struct jit_ctx *ctx) { u16 hi = val >> 16; u16 lo = val & 0xffff; if (hi & 0x8000) { if (hi == 0xffff) { emit(A64_MOVN(is64, reg, (u16)~lo, 0), ctx); } else { emit(A64_MOVN(is64, reg, (u16)~hi, 16), ctx); if (lo != 0xffff) emit(A64_MOVK(is64, reg, lo, 0), ctx); } } else { emit(A64_MOVZ(is64, reg, lo, 0), ctx); if (hi) emit(A64_MOVK(is64, reg, hi, 16), ctx); } } static int i64_i16_blocks(const u64 val, bool inverse) { return (((val >> 0) & 0xffff) != (inverse ? 0xffff : 0x0000)) + (((val >> 16) & 0xffff) != (inverse ? 0xffff : 0x0000)) + (((val >> 32) & 0xffff) != (inverse ? 0xffff : 0x0000)) + (((val >> 48) & 0xffff) != (inverse ? 0xffff : 0x0000)); } static inline void emit_a64_mov_i64(const int reg, const u64 val, struct jit_ctx *ctx) { u64 nrm_tmp = val, rev_tmp = ~val; bool inverse; int shift; if (!(nrm_tmp >> 32)) return emit_a64_mov_i(0, reg, (u32)val, ctx); inverse = i64_i16_blocks(nrm_tmp, true) < i64_i16_blocks(nrm_tmp, false); shift = max(round_down((inverse ? (fls64(rev_tmp) - 1) : (fls64(nrm_tmp) - 1)), 16), 0); if (inverse) emit(A64_MOVN(1, reg, (rev_tmp >> shift) & 0xffff, shift), ctx); else emit(A64_MOVZ(1, reg, (nrm_tmp >> shift) & 0xffff, shift), ctx); shift -= 16; while (shift >= 0) { if (((nrm_tmp >> shift) & 0xffff) != (inverse ? 0xffff : 0x0000)) emit(A64_MOVK(1, reg, (nrm_tmp >> shift) & 0xffff, shift), ctx); shift -= 16; } } static inline void emit_bti(u32 insn, struct jit_ctx *ctx) { if (IS_ENABLED(CONFIG_ARM64_BTI_KERNEL)) emit(insn, ctx); } /* * Kernel addresses in the vmalloc space use at most 48 bits, and the * remaining bits are guaranteed to be 0x1. So we can compose the address * with a fixed length movn/movk/movk sequence. */ static inline void emit_addr_mov_i64(const int reg, const u64 val, struct jit_ctx *ctx) { u64 tmp = val; int shift = 0; emit(A64_MOVN(1, reg, ~tmp & 0xffff, shift), ctx); while (shift < 32) { tmp >>= 16; shift += 16; emit(A64_MOVK(1, reg, tmp & 0xffff, shift), ctx); } } static inline void emit_call(u64 target, struct jit_ctx *ctx) { u8 tmp = bpf2a64[TMP_REG_1]; emit_addr_mov_i64(tmp, target, ctx); emit(A64_BLR(tmp), ctx); } static inline int bpf2a64_offset(int bpf_insn, int off, const struct jit_ctx *ctx) { /* BPF JMP offset is relative to the next instruction */ bpf_insn++; /* * Whereas arm64 branch instructions encode the offset * from the branch itself, so we must subtract 1 from the * instruction offset. */ return ctx->offset[bpf_insn + off] - (ctx->offset[bpf_insn] - 1); } static void jit_fill_hole(void *area, unsigned int size) { __le32 *ptr; /* We are guaranteed to have aligned memory. */ for (ptr = area; size >= sizeof(u32); size -= sizeof(u32)) *ptr++ = cpu_to_le32(AARCH64_BREAK_FAULT); } int bpf_arch_text_invalidate(void *dst, size_t len) { if (!aarch64_insn_set(dst, AARCH64_BREAK_FAULT, len)) return -EINVAL; return 0; } static inline int epilogue_offset(const struct jit_ctx *ctx) { int to = ctx->epilogue_offset; int from = ctx->idx; return to - from; } static bool is_addsub_imm(u32 imm) { /* Either imm12 or shifted imm12. */ return !(imm & ~0xfff) || !(imm & ~0xfff000); } /* * There are 3 types of AArch64 LDR/STR (immediate) instruction: * Post-index, Pre-index, Unsigned offset. * * For BPF ldr/str, the "unsigned offset" type is sufficient. * * "Unsigned offset" type LDR(immediate) format: * * 3 2 1 0 * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * |x x|1 1 1 0 0 1 0 1| imm12 | Rn | Rt | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * scale * * "Unsigned offset" type STR(immediate) format: * 3 2 1 0 * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * |x x|1 1 1 0 0 1 0 0| imm12 | Rn | Rt | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * scale * * The offset is calculated from imm12 and scale in the following way: * * offset = (u64)imm12 << scale */ static bool is_lsi_offset(int offset, int scale) { if (offset < 0) return false; if (offset > (0xFFF << scale)) return false; if (offset & ((1 << scale) - 1)) return false; return true; } /* generated prologue: * bti c // if CONFIG_ARM64_BTI_KERNEL * mov x9, lr * nop // POKE_OFFSET * paciasp // if CONFIG_ARM64_PTR_AUTH_KERNEL * stp x29, lr, [sp, #-16]! * mov x29, sp * stp x19, x20, [sp, #-16]! * stp x21, x22, [sp, #-16]! * stp x25, x26, [sp, #-16]! * stp x27, x28, [sp, #-16]! * mov x25, sp * mov tcc, #0 * // PROLOGUE_OFFSET */ #define BTI_INSNS (IS_ENABLED(CONFIG_ARM64_BTI_KERNEL) ? 1 : 0) #define PAC_INSNS (IS_ENABLED(CONFIG_ARM64_PTR_AUTH_KERNEL) ? 1 : 0) /* Offset of nop instruction in bpf prog entry to be poked */ #define POKE_OFFSET (BTI_INSNS + 1) /* Tail call offset to jump into */ #define PROLOGUE_OFFSET (BTI_INSNS + 2 + PAC_INSNS + 8) static int build_prologue(struct jit_ctx *ctx, bool ebpf_from_cbpf, bool is_exception_cb, u64 arena_vm_start) { const struct bpf_prog *prog = ctx->prog; const bool is_main_prog = !bpf_is_subprog(prog); const u8 r6 = bpf2a64[BPF_REG_6]; const u8 r7 = bpf2a64[BPF_REG_7]; const u8 r8 = bpf2a64[BPF_REG_8]; const u8 r9 = bpf2a64[BPF_REG_9]; const u8 fp = bpf2a64[BPF_REG_FP]; const u8 tcc = bpf2a64[TCALL_CNT]; const u8 fpb = bpf2a64[FP_BOTTOM]; const u8 arena_vm_base = bpf2a64[ARENA_VM_START]; const int idx0 = ctx->idx; int cur_offset; /* * BPF prog stack layout * * high * original A64_SP => 0:+-----+ BPF prologue * |FP/LR| * current A64_FP => -16:+-----+ * | ... | callee saved registers * BPF fp register => -64:+-----+ <= (BPF_FP) * | | * | ... | BPF prog stack * | | * +-----+ <= (BPF_FP - prog->aux->stack_depth) * |RSVD | padding * current A64_SP => +-----+ <= (BPF_FP - ctx->stack_size) * | | * | ... | Function call stack * | | * +-----+ * low * */ /* bpf function may be invoked by 3 instruction types: * 1. bl, attached via freplace to bpf prog via short jump * 2. br, attached via freplace to bpf prog via long jump * 3. blr, working as a function pointer, used by emit_call. * So BTI_JC should used here to support both br and blr. */ emit_bti(A64_BTI_JC, ctx); emit(A64_MOV(1, A64_R(9), A64_LR), ctx); emit(A64_NOP, ctx); if (!is_exception_cb) { /* Sign lr */ if (IS_ENABLED(CONFIG_ARM64_PTR_AUTH_KERNEL)) emit(A64_PACIASP, ctx); /* Save FP and LR registers to stay align with ARM64 AAPCS */ emit(A64_PUSH(A64_FP, A64_LR, A64_SP), ctx); emit(A64_MOV(1, A64_FP, A64_SP), ctx); /* Save callee-saved registers */ emit(A64_PUSH(r6, r7, A64_SP), ctx); emit(A64_PUSH(r8, r9, A64_SP), ctx); emit(A64_PUSH(fp, tcc, A64_SP), ctx); emit(A64_PUSH(fpb, A64_R(28), A64_SP), ctx); } else { /* * Exception callback receives FP of Main Program as third * parameter */ emit(A64_MOV(1, A64_FP, A64_R(2)), ctx); /* * Main Program already pushed the frame record and the * callee-saved registers. The exception callback will not push * anything and re-use the main program's stack. * * 10 registers are on the stack */ emit(A64_SUB_I(1, A64_SP, A64_FP, 80), ctx); } /* Set up BPF prog stack base register */ emit(A64_MOV(1, fp, A64_SP), ctx); if (!ebpf_from_cbpf && is_main_prog) { /* Initialize tail_call_cnt */ emit(A64_MOVZ(1, tcc, 0, 0), ctx); cur_offset = ctx->idx - idx0; if (cur_offset != PROLOGUE_OFFSET) { pr_err_once("PROLOGUE_OFFSET = %d, expected %d!\n", cur_offset, PROLOGUE_OFFSET); return -1; } /* BTI landing pad for the tail call, done with a BR */ emit_bti(A64_BTI_J, ctx); } /* * Program acting as exception boundary should save all ARM64 * Callee-saved registers as the exception callback needs to recover * all ARM64 Callee-saved registers in its epilogue. */ if (prog->aux->exception_boundary) { /* * As we are pushing two more registers, BPF_FP should be moved * 16 bytes */ emit(A64_SUB_I(1, fp, fp, 16), ctx); emit(A64_PUSH(A64_R(23), A64_R(24), A64_SP), ctx); } emit(A64_SUB_I(1, fpb, fp, ctx->fpb_offset), ctx); /* Stack must be multiples of 16B */ ctx->stack_size = round_up(prog->aux->stack_depth, 16); /* Set up function call stack */ emit(A64_SUB_I(1, A64_SP, A64_SP, ctx->stack_size), ctx); if (arena_vm_start) emit_a64_mov_i64(arena_vm_base, arena_vm_start, ctx); return 0; } static int out_offset = -1; /* initialized on the first pass of build_body() */ static int emit_bpf_tail_call(struct jit_ctx *ctx) { /* bpf_tail_call(void *prog_ctx, struct bpf_array *array, u64 index) */ const u8 r2 = bpf2a64[BPF_REG_2]; const u8 r3 = bpf2a64[BPF_REG_3]; const u8 tmp = bpf2a64[TMP_REG_1]; const u8 prg = bpf2a64[TMP_REG_2]; const u8 tcc = bpf2a64[TCALL_CNT]; const int idx0 = ctx->idx; #define cur_offset (ctx->idx - idx0) #define jmp_offset (out_offset - (cur_offset)) size_t off; /* if (index >= array->map.max_entries) * goto out; */ off = offsetof(struct bpf_array, map.max_entries); emit_a64_mov_i64(tmp, off, ctx); emit(A64_LDR32(tmp, r2, tmp), ctx); emit(A64_MOV(0, r3, r3), ctx); emit(A64_CMP(0, r3, tmp), ctx); emit(A64_B_(A64_COND_CS, jmp_offset), ctx); /* * if (tail_call_cnt >= MAX_TAIL_CALL_CNT) * goto out; * tail_call_cnt++; */ emit_a64_mov_i64(tmp, MAX_TAIL_CALL_CNT, ctx); emit(A64_CMP(1, tcc, tmp), ctx); emit(A64_B_(A64_COND_CS, jmp_offset), ctx); emit(A64_ADD_I(1, tcc, tcc, 1), ctx); /* prog = array->ptrs[index]; * if (prog == NULL) * goto out; */ off = offsetof(struct bpf_array, ptrs); emit_a64_mov_i64(tmp, off, ctx); emit(A64_ADD(1, tmp, r2, tmp), ctx); emit(A64_LSL(1, prg, r3, 3), ctx); emit(A64_LDR64(prg, tmp, prg), ctx); emit(A64_CBZ(1, prg, jmp_offset), ctx); /* goto *(prog->bpf_func + prologue_offset); */ off = offsetof(struct bpf_prog, bpf_func); emit_a64_mov_i64(tmp, off, ctx); emit(A64_LDR64(tmp, prg, tmp), ctx); emit(A64_ADD_I(1, tmp, tmp, sizeof(u32) * PROLOGUE_OFFSET), ctx); emit(A64_ADD_I(1, A64_SP, A64_SP, ctx->stack_size), ctx); emit(A64_BR(tmp), ctx); /* out: */ if (out_offset == -1) out_offset = cur_offset; if (cur_offset != out_offset) { pr_err_once("tail_call out_offset = %d, expected %d!\n", cur_offset, out_offset); return -1; } return 0; #undef cur_offset #undef jmp_offset } #ifdef CONFIG_ARM64_LSE_ATOMICS static int emit_lse_atomic(const struct bpf_insn *insn, struct jit_ctx *ctx) { const u8 code = insn->code; const u8 arena_vm_base = bpf2a64[ARENA_VM_START]; const u8 dst = bpf2a64[insn->dst_reg]; const u8 src = bpf2a64[insn->src_reg]; const u8 tmp = bpf2a64[TMP_REG_1]; const u8 tmp2 = bpf2a64[TMP_REG_2]; const bool isdw = BPF_SIZE(code) == BPF_DW; const bool arena = BPF_MODE(code) == BPF_PROBE_ATOMIC; const s16 off = insn->off; u8 reg = dst; if (off || arena) { if (off) { emit_a64_mov_i(1, tmp, off, ctx); emit(A64_ADD(1, tmp, tmp, dst), ctx); reg = tmp; } if (arena) { emit(A64_ADD(1, tmp, reg, arena_vm_base), ctx); reg = tmp; } } switch (insn->imm) { /* lock *(u32/u64 *)(dst_reg + off) <op>= src_reg */ case BPF_ADD: emit(A64_STADD(isdw, reg, src), ctx); break; case BPF_AND: emit(A64_MVN(isdw, tmp2, src), ctx); emit(A64_STCLR(isdw, reg, tmp2), ctx); break; case BPF_OR: emit(A64_STSET(isdw, reg, src), ctx); break; case BPF_XOR: emit(A64_STEOR(isdw, reg, src), ctx); break; /* src_reg = atomic_fetch_<op>(dst_reg + off, src_reg) */ case BPF_ADD | BPF_FETCH: emit(A64_LDADDAL(isdw, src, reg, src), ctx); break; case BPF_AND | BPF_FETCH: emit(A64_MVN(isdw, tmp2, src), ctx); emit(A64_LDCLRAL(isdw, src, reg, tmp2), ctx); break; case BPF_OR | BPF_FETCH: emit(A64_LDSETAL(isdw, src, reg, src), ctx); break; case BPF_XOR | BPF_FETCH: emit(A64_LDEORAL(isdw, src, reg, src), ctx); break; /* src_reg = atomic_xchg(dst_reg + off, src_reg); */ case BPF_XCHG: emit(A64_SWPAL(isdw, src, reg, src), ctx); break; /* r0 = atomic_cmpxchg(dst_reg + off, r0, src_reg); */ case BPF_CMPXCHG: emit(A64_CASAL(isdw, src, reg, bpf2a64[BPF_REG_0]), ctx); break; default: pr_err_once("unknown atomic op code %02x\n", insn->imm); return -EINVAL; } return 0; } #else static inline int emit_lse_atomic(const struct bpf_insn *insn, struct jit_ctx *ctx) { return -EINVAL; } #endif static int emit_ll_sc_atomic(const struct bpf_insn *insn, struct jit_ctx *ctx) { const u8 code = insn->code; const u8 dst = bpf2a64[insn->dst_reg]; const u8 src = bpf2a64[insn->src_reg]; const u8 tmp = bpf2a64[TMP_REG_1]; const u8 tmp2 = bpf2a64[TMP_REG_2]; const u8 tmp3 = bpf2a64[TMP_REG_3]; const int i = insn - ctx->prog->insnsi; const s32 imm = insn->imm; const s16 off = insn->off; const bool isdw = BPF_SIZE(code) == BPF_DW; u8 reg; s32 jmp_offset; if (BPF_MODE(code) == BPF_PROBE_ATOMIC) { /* ll_sc based atomics don't support unsafe pointers yet. */ pr_err_once("unknown atomic opcode %02x\n", code); return -EINVAL; } if (!off) { reg = dst; } else { emit_a64_mov_i(1, tmp, off, ctx); emit(A64_ADD(1, tmp, tmp, dst), ctx); reg = tmp; } if (imm == BPF_ADD || imm == BPF_AND || imm == BPF_OR || imm == BPF_XOR) { /* lock *(u32/u64 *)(dst_reg + off) <op>= src_reg */ emit(A64_LDXR(isdw, tmp2, reg), ctx); if (imm == BPF_ADD) emit(A64_ADD(isdw, tmp2, tmp2, src), ctx); else if (imm == BPF_AND) emit(A64_AND(isdw, tmp2, tmp2, src), ctx); else if (imm == BPF_OR) emit(A64_ORR(isdw, tmp2, tmp2, src), ctx); else emit(A64_EOR(isdw, tmp2, tmp2, src), ctx); emit(A64_STXR(isdw, tmp2, reg, tmp3), ctx); jmp_offset = -3; check_imm19(jmp_offset); emit(A64_CBNZ(0, tmp3, jmp_offset), ctx); } else if (imm == (BPF_ADD | BPF_FETCH) || imm == (BPF_AND | BPF_FETCH) || imm == (BPF_OR | BPF_FETCH) || imm == (BPF_XOR | BPF_FETCH)) { /* src_reg = atomic_fetch_<op>(dst_reg + off, src_reg) */ const u8 ax = bpf2a64[BPF_REG_AX]; emit(A64_MOV(isdw, ax, src), ctx); emit(A64_LDXR(isdw, src, reg), ctx); if (imm == (BPF_ADD | BPF_FETCH)) emit(A64_ADD(isdw, tmp2, src, ax), ctx); else if (imm == (BPF_AND | BPF_FETCH)) emit(A64_AND(isdw, tmp2, src, ax), ctx); else if (imm == (BPF_OR | BPF_FETCH)) emit(A64_ORR(isdw, tmp2, src, ax), ctx); else emit(A64_EOR(isdw, tmp2, src, ax), ctx); emit(A64_STLXR(isdw, tmp2, reg, tmp3), ctx); jmp_offset = -3; check_imm19(jmp_offset); emit(A64_CBNZ(0, tmp3, jmp_offset), ctx); emit(A64_DMB_ISH, ctx); } else if (imm == BPF_XCHG) { /* src_reg = atomic_xchg(dst_reg + off, src_reg); */ emit(A64_MOV(isdw, tmp2, src), ctx); emit(A64_LDXR(isdw, src, reg), ctx); emit(A64_STLXR(isdw, tmp2, reg, tmp3), ctx); jmp_offset = -2; check_imm19(jmp_offset); emit(A64_CBNZ(0, tmp3, jmp_offset), ctx); emit(A64_DMB_ISH, ctx); } else if (imm == BPF_CMPXCHG) { /* r0 = atomic_cmpxchg(dst_reg + off, r0, src_reg); */ const u8 r0 = bpf2a64[BPF_REG_0]; emit(A64_MOV(isdw, tmp2, r0), ctx); emit(A64_LDXR(isdw, r0, reg), ctx); emit(A64_EOR(isdw, tmp3, r0, tmp2), ctx); jmp_offset = 4; check_imm19(jmp_offset); emit(A64_CBNZ(isdw, tmp3, jmp_offset), ctx); emit(A64_STLXR(isdw, src, reg, tmp3), ctx); jmp_offset = -4; check_imm19(jmp_offset); emit(A64_CBNZ(0, tmp3, jmp_offset), ctx); emit(A64_DMB_ISH, ctx); } else { pr_err_once("unknown atomic op code %02x\n", imm); return -EINVAL; } return 0; } void dummy_tramp(void); asm ( " .pushsection .text, \"ax\", @progbits\n" " .global dummy_tramp\n" " .type dummy_tramp, %function\n" "dummy_tramp:" #if IS_ENABLED(CONFIG_ARM64_BTI_KERNEL) " bti j\n" /* dummy_tramp is called via "br x10" */ #endif " mov x10, x30\n" " mov x30, x9\n" " ret x10\n" " .size dummy_tramp, .-dummy_tramp\n" " .popsection\n" ); /* build a plt initialized like this: * * plt: * ldr tmp, target * br tmp * target: * .quad dummy_tramp * * when a long jump trampoline is attached, target is filled with the * trampoline address, and when the trampoline is removed, target is * restored to dummy_tramp address. */ static void build_plt(struct jit_ctx *ctx) { const u8 tmp = bpf2a64[TMP_REG_1]; struct bpf_plt *plt = NULL; /* make sure target is 64-bit aligned */ if ((ctx->idx + PLT_TARGET_OFFSET / AARCH64_INSN_SIZE) % 2) emit(A64_NOP, ctx); plt = (struct bpf_plt *)(ctx->image + ctx->idx); /* plt is called via bl, no BTI needed here */ emit(A64_LDR64LIT(tmp, 2 * AARCH64_INSN_SIZE), ctx); emit(A64_BR(tmp), ctx); if (ctx->image) plt->target = (u64)&dummy_tramp; } static void build_epilogue(struct jit_ctx *ctx, bool is_exception_cb) { const u8 r0 = bpf2a64[BPF_REG_0]; const u8 r6 = bpf2a64[BPF_REG_6]; const u8 r7 = bpf2a64[BPF_REG_7]; const u8 r8 = bpf2a64[BPF_REG_8]; const u8 r9 = bpf2a64[BPF_REG_9]; const u8 fp = bpf2a64[BPF_REG_FP]; const u8 fpb = bpf2a64[FP_BOTTOM]; /* We're done with BPF stack */ emit(A64_ADD_I(1, A64_SP, A64_SP, ctx->stack_size), ctx); /* * Program acting as exception boundary pushes R23 and R24 in addition * to BPF callee-saved registers. Exception callback uses the boundary * program's stack frame, so recover these extra registers in the above * two cases. */ if (ctx->prog->aux->exception_boundary || is_exception_cb) emit(A64_POP(A64_R(23), A64_R(24), A64_SP), ctx); /* Restore x27 and x28 */ emit(A64_POP(fpb, A64_R(28), A64_SP), ctx); /* Restore fs (x25) and x26 */ emit(A64_POP(fp, A64_R(26), A64_SP), ctx); /* Restore callee-saved register */ emit(A64_POP(r8, r9, A64_SP), ctx); emit(A64_POP(r6, r7, A64_SP), ctx); /* Restore FP/LR registers */ emit(A64_POP(A64_FP, A64_LR, A64_SP), ctx); /* Set return value */ emit(A64_MOV(1, A64_R(0), r0), ctx); /* Authenticate lr */ if (IS_ENABLED(CONFIG_ARM64_PTR_AUTH_KERNEL)) emit(A64_AUTIASP, ctx); emit(A64_RET(A64_LR), ctx); } #define BPF_FIXUP_OFFSET_MASK GENMASK(26, 0) #define BPF_FIXUP_REG_MASK GENMASK(31, 27) #define DONT_CLEAR 5 /* Unused ARM64 register from BPF's POV */ bool ex_handler_bpf(const struct exception_table_entry *ex, struct pt_regs *regs) { off_t offset = FIELD_GET(BPF_FIXUP_OFFSET_MASK, ex->fixup); int dst_reg = FIELD_GET(BPF_FIXUP_REG_MASK, ex->fixup); if (dst_reg != DONT_CLEAR) regs->regs[dst_reg] = 0; regs->pc = (unsigned long)&ex->fixup - offset; return true; } /* For accesses to BTF pointers, add an entry to the exception table */ static int add_exception_handler(const struct bpf_insn *insn, struct jit_ctx *ctx, int dst_reg) { off_t ins_offset; off_t fixup_offset; unsigned long pc; struct exception_table_entry *ex; if (!ctx->image) /* First pass */ return 0; if (BPF_MODE(insn->code) != BPF_PROBE_MEM && BPF_MODE(insn->code) != BPF_PROBE_MEMSX && BPF_MODE(insn->code) != BPF_PROBE_MEM32 && BPF_MODE(insn->code) != BPF_PROBE_ATOMIC) 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]; pc = (unsigned long)&ctx->ro_image[ctx->idx - 1]; /* * This is the relative offset of the instruction that may fault from * the exception table itself. This will be written to the exception * table and if this instruction faults, the destination register will * be set to '0' and the execution will jump to the next instruction. */ ins_offset = pc - (long)&ex->insn; if (WARN_ON_ONCE(ins_offset >= 0 || ins_offset < INT_MIN)) return -ERANGE; /* * Since the extable follows the program, the fixup offset is always * negative and limited to BPF_JIT_REGION_SIZE. Store a positive value * to keep things simple, and put the destination register in the upper * bits. We don't need to worry about buildtime or runtime sort * modifying the upper bits because the table is already sorted, and * isn't part of the main exception table. * * The fixup_offset is set to the next instruction from the instruction * that may fault. The execution will jump to this after handling the * fault. */ fixup_offset = (long)&ex->fixup - (pc + AARCH64_INSN_SIZE); if (!FIELD_FIT(BPF_FIXUP_OFFSET_MASK, fixup_offset)) return -ERANGE; /* * The offsets above have been calculated using the RO buffer but we * need to use the R/W buffer for writes. * switch ex to rw buffer for writing. */ ex = (void *)ctx->image + ((void *)ex - (void *)ctx->ro_image); ex->insn = ins_offset; if (BPF_CLASS(insn->code) != BPF_LDX) dst_reg = DONT_CLEAR; ex->fixup = FIELD_PREP(BPF_FIXUP_OFFSET_MASK, fixup_offset) | FIELD_PREP(BPF_FIXUP_REG_MASK, dst_reg); ex->type = EX_TYPE_BPF; ctx->exentry_idx++; return 0; } /* JITs an eBPF instruction. * Returns: * 0 - successfully JITed an 8-byte eBPF instruction. * >0 - successfully JITed a 16-byte eBPF instruction. * <0 - failed to JIT. */ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool extra_pass) { const u8 code = insn->code; u8 dst = bpf2a64[insn->dst_reg]; u8 src = bpf2a64[insn->src_reg]; const u8 tmp = bpf2a64[TMP_REG_1]; const u8 tmp2 = bpf2a64[TMP_REG_2]; const u8 fp = bpf2a64[BPF_REG_FP]; const u8 fpb = bpf2a64[FP_BOTTOM]; const u8 arena_vm_base = bpf2a64[ARENA_VM_START]; const s16 off = insn->off; const s32 imm = insn->imm; const int i = insn - ctx->prog->insnsi; const bool is64 = BPF_CLASS(code) == BPF_ALU64 || BPF_CLASS(code) == BPF_JMP; u8 jmp_cond; s32 jmp_offset; u32 a64_insn; u8 src_adj; u8 dst_adj; int off_adj; int ret; bool sign_extend; switch (code) { /* dst = src */ case BPF_ALU | BPF_MOV | BPF_X: case BPF_ALU64 | BPF_MOV | BPF_X: if (insn_is_cast_user(insn)) { emit(A64_MOV(0, tmp, src), ctx); // 32-bit mov clears the upper 32 bits emit_a64_mov_i(0, dst, ctx->user_vm_start >> 32, ctx); emit(A64_LSL(1, dst, dst, 32), ctx); emit(A64_CBZ(1, tmp, 2), ctx); emit(A64_ORR(1, tmp, dst, tmp), ctx); emit(A64_MOV(1, dst, tmp), ctx); break; } else if (insn_is_mov_percpu_addr(insn)) { if (dst != src) emit(A64_MOV(1, dst, src), ctx); if (cpus_have_cap(ARM64_HAS_VIRT_HOST_EXTN)) emit(A64_MRS_TPIDR_EL2(tmp), ctx); else emit(A64_MRS_TPIDR_EL1(tmp), ctx); emit(A64_ADD(1, dst, dst, tmp), ctx); break; } switch (insn->off) { case 0: emit(A64_MOV(is64, dst, src), ctx); break; case 8: emit(A64_SXTB(is64, dst, src), ctx); break; case 16: emit(A64_SXTH(is64, dst, src), ctx); break; case 32: emit(A64_SXTW(is64, dst, src), ctx); break; } break; /* dst = dst OP src */ case BPF_ALU | BPF_ADD | BPF_X: case BPF_ALU64 | BPF_ADD | BPF_X: emit(A64_ADD(is64, dst, dst, src), ctx); break; case BPF_ALU | BPF_SUB | BPF_X: case BPF_ALU64 | BPF_SUB | BPF_X: emit(A64_SUB(is64, dst, dst, src), ctx); break; case BPF_ALU | BPF_AND | BPF_X: case BPF_ALU64 | BPF_AND | BPF_X: emit(A64_AND(is64, dst, dst, src), ctx); break; case BPF_ALU | BPF_OR | BPF_X: case BPF_ALU64 | BPF_OR | BPF_X: emit(A64_ORR(is64, dst, dst, src), ctx); break; case BPF_ALU | BPF_XOR | BPF_X: case BPF_ALU64 | BPF_XOR | BPF_X: emit(A64_EOR(is64, dst, dst, src), ctx); break; case BPF_ALU | BPF_MUL | BPF_X: case BPF_ALU64 | BPF_MUL | BPF_X: emit(A64_MUL(is64, dst, dst, src), ctx); break; case BPF_ALU | BPF_DIV | BPF_X: case BPF_ALU64 | BPF_DIV | BPF_X: if (!off) emit(A64_UDIV(is64, dst, dst, src), ctx); else emit(A64_SDIV(is64, dst, dst, src), ctx); break; case BPF_ALU | BPF_MOD | BPF_X: case BPF_ALU64 | BPF_MOD | BPF_X: if (!off) emit(A64_UDIV(is64, tmp, dst, src), ctx); else emit(A64_SDIV(is64, tmp, dst, src), ctx); emit(A64_MSUB(is64, dst, dst, tmp, src), ctx); break; case BPF_ALU | BPF_LSH | BPF_X: case BPF_ALU64 | BPF_LSH | BPF_X: emit(A64_LSLV(is64, dst, dst, src), ctx); break; case BPF_ALU | BPF_RSH | BPF_X: case BPF_ALU64 | BPF_RSH | BPF_X: emit(A64_LSRV(is64, dst, dst, src), ctx); break; case BPF_ALU | BPF_ARSH | BPF_X: case BPF_ALU64 | BPF_ARSH | BPF_X: emit(A64_ASRV(is64, dst, dst, src), ctx); break; /* dst = -dst */ case BPF_ALU | BPF_NEG: case BPF_ALU64 | BPF_NEG: emit(A64_NEG(is64, dst, dst), ctx); break; /* dst = BSWAP##imm(dst) */ case BPF_ALU | BPF_END | BPF_FROM_LE: case BPF_ALU | BPF_END | BPF_FROM_BE: case BPF_ALU64 | BPF_END | BPF_FROM_LE: #ifdef CONFIG_CPU_BIG_ENDIAN if (BPF_CLASS(code) == BPF_ALU && BPF_SRC(code) == BPF_FROM_BE) goto emit_bswap_uxt; #else /* !CONFIG_CPU_BIG_ENDIAN */ if (BPF_CLASS(code) == BPF_ALU && BPF_SRC(code) == BPF_FROM_LE) goto emit_bswap_uxt; #endif switch (imm) { case 16: emit(A64_REV16(is64, dst, dst), ctx); /* zero-extend 16 bits into 64 bits */ emit(A64_UXTH(is64, dst, dst), ctx); break; case 32: emit(A64_REV32(0, dst, dst), ctx); /* upper 32 bits already cleared */ break; case 64: emit(A64_REV64(dst, dst), ctx); break; } break; emit_bswap_uxt: switch (imm) { case 16: /* zero-extend 16 bits into 64 bits */ emit(A64_UXTH(is64, dst, dst), ctx); break; case 32: /* zero-extend 32 bits into 64 bits */ emit(A64_UXTW(is64, dst, dst), ctx); break; case 64: /* nop */ break; } break; /* dst = imm */ case BPF_ALU | BPF_MOV | BPF_K: case BPF_ALU64 | BPF_MOV | BPF_K: emit_a64_mov_i(is64, dst, imm, ctx); break; /* dst = dst OP imm */ case BPF_ALU | BPF_ADD | BPF_K: case BPF_ALU64 | BPF_ADD | BPF_K: if (is_addsub_imm(imm)) { emit(A64_ADD_I(is64, dst, dst, imm), ctx); } else if (is_addsub_imm(-imm)) { emit(A64_SUB_I(is64, dst, dst, -imm), ctx); } else { emit_a64_mov_i(is64, tmp, imm, ctx); emit(A64_ADD(is64, dst, dst, tmp), ctx); } break; case BPF_ALU | BPF_SUB | BPF_K: case BPF_ALU64 | BPF_SUB | BPF_K: if (is_addsub_imm(imm)) { emit(A64_SUB_I(is64, dst, dst, imm), ctx); } else if (is_addsub_imm(-imm)) { emit(A64_ADD_I(is64, dst, dst, -imm), ctx); } else { emit_a64_mov_i(is64, tmp, imm, ctx); emit(A64_SUB(is64, dst, dst, tmp), ctx); } break; case BPF_ALU | BPF_AND | BPF_K: case BPF_ALU64 | BPF_AND | BPF_K: a64_insn = A64_AND_I(is64, dst, dst, imm); if (a64_insn != AARCH64_BREAK_FAULT) { emit(a64_insn, ctx); } else { emit_a64_mov_i(is64, tmp, imm, ctx); emit(A64_AND(is64, dst, dst, tmp), ctx); } break; case BPF_ALU | BPF_OR | BPF_K: case BPF_ALU64 | BPF_OR | BPF_K: a64_insn = A64_ORR_I(is64, dst, dst, imm); if (a64_insn != AARCH64_BREAK_FAULT) { emit(a64_insn, ctx); } else { emit_a64_mov_i(is64, tmp, imm, ctx); emit(A64_ORR(is64, dst, dst, tmp), ctx); } break; case BPF_ALU | BPF_XOR | BPF_K: case BPF_ALU64 | BPF_XOR | BPF_K: a64_insn = A64_EOR_I(is64, dst, dst, imm); if (a64_insn != AARCH64_BREAK_FAULT) { emit(a64_insn, ctx); } else { emit_a64_mov_i(is64, tmp, imm, ctx); emit(A64_EOR(is64, dst, dst, tmp), ctx); } break; case BPF_ALU | BPF_MUL | BPF_K: case BPF_ALU64 | BPF_MUL | BPF_K: emit_a64_mov_i(is64, tmp, imm, ctx); emit(A64_MUL(is64, dst, dst, tmp), ctx); break; case BPF_ALU | BPF_DIV | BPF_K: case BPF_ALU64 | BPF_DIV | BPF_K: emit_a64_mov_i(is64, tmp, imm, ctx); if (!off) emit(A64_UDIV(is64, dst, dst, tmp), ctx); else emit(A64_SDIV(is64, dst, dst, tmp), ctx); break; case BPF_ALU | BPF_MOD | BPF_K: case BPF_ALU64 | BPF_MOD | BPF_K: emit_a64_mov_i(is64, tmp2, imm, ctx); if (!off) emit(A64_UDIV(is64, tmp, dst, tmp2), ctx); else emit(A64_SDIV(is64, tmp, dst, tmp2), ctx); emit(A64_MSUB(is64, dst, dst, tmp, tmp2), ctx); break; case BPF_ALU | BPF_LSH | BPF_K: case BPF_ALU64 | BPF_LSH | BPF_K: emit(A64_LSL(is64, dst, dst, imm), ctx); break; case BPF_ALU | BPF_RSH | BPF_K: case BPF_ALU64 | BPF_RSH | BPF_K: emit(A64_LSR(is64, dst, dst, imm), ctx); break; case BPF_ALU | BPF_ARSH | BPF_K: case BPF_ALU64 | BPF_ARSH | BPF_K: emit(A64_ASR(is64, dst, dst, imm), ctx); break; /* JUMP off */ case BPF_JMP | BPF_JA: case BPF_JMP32 | BPF_JA: if (BPF_CLASS(code) == BPF_JMP) jmp_offset = bpf2a64_offset(i, off, ctx); else jmp_offset = bpf2a64_offset(i, imm, ctx); check_imm26(jmp_offset); emit(A64_B(jmp_offset), ctx); break; /* IF (dst COND src) JUMP off */ case BPF_JMP | BPF_JEQ | BPF_X: case BPF_JMP | BPF_JGT | BPF_X: case BPF_JMP | BPF_JLT | BPF_X: case BPF_JMP | BPF_JGE | BPF_X: case BPF_JMP | BPF_JLE | BPF_X: case BPF_JMP | BPF_JNE | BPF_X: case BPF_JMP | BPF_JSGT | BPF_X: case BPF_JMP | BPF_JSLT | BPF_X: case BPF_JMP | BPF_JSGE | BPF_X: case BPF_JMP | BPF_JSLE | BPF_X: case BPF_JMP32 | BPF_JEQ | BPF_X: case BPF_JMP32 | BPF_JGT | BPF_X: case BPF_JMP32 | BPF_JLT | BPF_X: case BPF_JMP32 | BPF_JGE | BPF_X: case BPF_JMP32 | BPF_JLE | BPF_X: case BPF_JMP32 | BPF_JNE | BPF_X: case BPF_JMP32 | BPF_JSGT | BPF_X: case BPF_JMP32 | BPF_JSLT | BPF_X: case BPF_JMP32 | BPF_JSGE | BPF_X: case BPF_JMP32 | BPF_JSLE | BPF_X: emit(A64_CMP(is64, dst, src), ctx); emit_cond_jmp: jmp_offset = bpf2a64_offset(i, off, ctx); check_imm19(jmp_offset); switch (BPF_OP(code)) { case BPF_JEQ: jmp_cond = A64_COND_EQ; break; case BPF_JGT: jmp_cond = A64_COND_HI; break; case BPF_JLT: jmp_cond = A64_COND_CC; break; case BPF_JGE: jmp_cond = A64_COND_CS; break; case BPF_JLE: jmp_cond = A64_COND_LS; break; case BPF_JSET: case BPF_JNE: jmp_cond = A64_COND_NE; break; case BPF_JSGT: jmp_cond = A64_COND_GT; break; case BPF_JSLT: jmp_cond = A64_COND_LT; break; case BPF_JSGE: jmp_cond = A64_COND_GE; break; case BPF_JSLE: jmp_cond = A64_COND_LE; break; default: return -EFAULT; } emit(A64_B_(jmp_cond, jmp_offset), ctx); break; case BPF_JMP | BPF_JSET | BPF_X: case BPF_JMP32 | BPF_JSET | BPF_X: emit(A64_TST(is64, dst, src), ctx); goto emit_cond_jmp; /* IF (dst COND imm) JUMP off */ case BPF_JMP | BPF_JEQ | BPF_K: case BPF_JMP | BPF_JGT | BPF_K: case BPF_JMP | BPF_JLT | BPF_K: case BPF_JMP | BPF_JGE | BPF_K: case BPF_JMP | BPF_JLE | BPF_K: case BPF_JMP | BPF_JNE | BPF_K: case BPF_JMP | BPF_JSGT | BPF_K: case BPF_JMP | BPF_JSLT | BPF_K: case BPF_JMP | BPF_JSGE | BPF_K: case BPF_JMP | BPF_JSLE | BPF_K: case BPF_JMP32 | BPF_JEQ | BPF_K: case BPF_JMP32 | BPF_JGT | BPF_K: case BPF_JMP32 | BPF_JLT | BPF_K: case BPF_JMP32 | BPF_JGE | BPF_K: case BPF_JMP32 | BPF_JLE | BPF_K: case BPF_JMP32 | BPF_JNE | BPF_K: case BPF_JMP32 | BPF_JSGT | BPF_K: case BPF_JMP32 | BPF_JSLT | BPF_K: case BPF_JMP32 | BPF_JSGE | BPF_K: case BPF_JMP32 | BPF_JSLE | BPF_K: if (is_addsub_imm(imm)) { emit(A64_CMP_I(is64, dst, imm), ctx); } else if (is_addsub_imm(-imm)) { emit(A64_CMN_I(is64, dst, -imm), ctx); } else { emit_a64_mov_i(is64, tmp, imm, ctx); emit(A64_CMP(is64, dst, tmp), ctx); } goto emit_cond_jmp; case BPF_JMP | BPF_JSET | BPF_K: case BPF_JMP32 | BPF_JSET | BPF_K: a64_insn = A64_TST_I(is64, dst, imm); if (a64_insn != AARCH64_BREAK_FAULT) { emit(a64_insn, ctx); } else { emit_a64_mov_i(is64, tmp, imm, ctx); emit(A64_TST(is64, dst, tmp), ctx); } goto emit_cond_jmp; /* function call */ case BPF_JMP | BPF_CALL: { const u8 r0 = bpf2a64[BPF_REG_0]; bool func_addr_fixed; u64 func_addr; u32 cpu_offset; /* Implement helper call to bpf_get_smp_processor_id() inline */ if (insn->src_reg == 0 && insn->imm == BPF_FUNC_get_smp_processor_id) { cpu_offset = offsetof(struct thread_info, cpu); emit(A64_MRS_SP_EL0(tmp), ctx); if (is_lsi_offset(cpu_offset, 2)) { emit(A64_LDR32I(r0, tmp, cpu_offset), ctx); } else { emit_a64_mov_i(1, tmp2, cpu_offset, ctx); emit(A64_LDR32(r0, tmp, tmp2), ctx); } break; } /* Implement helper call to bpf_get_current_task/_btf() inline */ if (insn->src_reg == 0 && (insn->imm == BPF_FUNC_get_current_task || insn->imm == BPF_FUNC_get_current_task_btf)) { emit(A64_MRS_SP_EL0(r0), ctx); break; } ret = bpf_jit_get_func_addr(ctx->prog, insn, extra_pass, &func_addr, &func_addr_fixed); if (ret < 0) return ret; emit_call(func_addr, ctx); emit(A64_MOV(1, r0, A64_R(0)), ctx); break; } /* tail call */ case BPF_JMP | BPF_TAIL_CALL: if (emit_bpf_tail_call(ctx)) return -EFAULT; break; /* function return */ case BPF_JMP | BPF_EXIT: /* Optimization: when last instruction is EXIT, simply fallthrough to epilogue. */ if (i == ctx->prog->len - 1) break; jmp_offset = epilogue_offset(ctx); check_imm26(jmp_offset); emit(A64_B(jmp_offset), ctx); break; /* dst = imm64 */ case BPF_LD | BPF_IMM | BPF_DW: { const struct bpf_insn insn1 = insn[1]; u64 imm64; imm64 = (u64)insn1.imm << 32 | (u32)imm; if (bpf_pseudo_func(insn)) emit_addr_mov_i64(dst, imm64, ctx); else emit_a64_mov_i64(dst, imm64, ctx); return 1; } /* LDX: dst = (u64)*(unsigned size *)(src + off) */ case BPF_LDX | BPF_MEM | BPF_W: case BPF_LDX | BPF_MEM | BPF_H: case BPF_LDX | BPF_MEM | BPF_B: case BPF_LDX | BPF_MEM | BPF_DW: case BPF_LDX | BPF_PROBE_MEM | BPF_DW: case BPF_LDX | BPF_PROBE_MEM | BPF_W: case BPF_LDX | BPF_PROBE_MEM | BPF_H: case BPF_LDX | BPF_PROBE_MEM | BPF_B: /* LDXS: dst_reg = (s64)*(signed size *)(src_reg + off) */ case BPF_LDX | BPF_MEMSX | BPF_B: case BPF_LDX | BPF_MEMSX | BPF_H: case BPF_LDX | BPF_MEMSX | BPF_W: case BPF_LDX | BPF_PROBE_MEMSX | BPF_B: case BPF_LDX | BPF_PROBE_MEMSX | BPF_H: case BPF_LDX | BPF_PROBE_MEMSX | BPF_W: case BPF_LDX | BPF_PROBE_MEM32 | BPF_B: case BPF_LDX | BPF_PROBE_MEM32 | BPF_H: case BPF_LDX | BPF_PROBE_MEM32 | BPF_W: case BPF_LDX | BPF_PROBE_MEM32 | BPF_DW: if (BPF_MODE(insn->code) == BPF_PROBE_MEM32) { emit(A64_ADD(1, tmp2, src, arena_vm_base), ctx); src = tmp2; } if (ctx->fpb_offset > 0 && src == fp && BPF_MODE(insn->code) != BPF_PROBE_MEM32) { src_adj = fpb; off_adj = off + ctx->fpb_offset; } else { src_adj = src; off_adj = off; } sign_extend = (BPF_MODE(insn->code) == BPF_MEMSX || BPF_MODE(insn->code) == BPF_PROBE_MEMSX); switch (BPF_SIZE(code)) { case BPF_W: if (is_lsi_offset(off_adj, 2)) { if (sign_extend) emit(A64_LDRSWI(dst, src_adj, off_adj), ctx); else emit(A64_LDR32I(dst, src_adj, off_adj), ctx); } else { emit_a64_mov_i(1, tmp, off, ctx); if (sign_extend) emit(A64_LDRSW(dst, src, tmp), ctx); else emit(A64_LDR32(dst, src, tmp), ctx); } break; case BPF_H: if (is_lsi_offset(off_adj, 1)) { if (sign_extend) emit(A64_LDRSHI(dst, src_adj, off_adj), ctx); else emit(A64_LDRHI(dst, src_adj, off_adj), ctx); } else { emit_a64_mov_i(1, tmp, off, ctx); if (sign_extend) emit(A64_LDRSH(dst, src, tmp), ctx); else emit(A64_LDRH(dst, src, tmp), ctx); } break; case BPF_B: if (is_lsi_offset(off_adj, 0)) { if (sign_extend) emit(A64_LDRSBI(dst, src_adj, off_adj), ctx); else emit(A64_LDRBI(dst, src_adj, off_adj), ctx); } else { emit_a64_mov_i(1, tmp, off, ctx); if (sign_extend) emit(A64_LDRSB(dst, src, tmp), ctx); else emit(A64_LDRB(dst, src, tmp), ctx); } break; case BPF_DW: if (is_lsi_offset(off_adj, 3)) { emit(A64_LDR64I(dst, src_adj, off_adj), ctx); } else { emit_a64_mov_i(1, tmp, off, ctx); emit(A64_LDR64(dst, src, tmp), ctx); } break; } ret = add_exception_handler(insn, ctx, dst); if (ret) return ret; break; /* speculation barrier */ case BPF_ST | BPF_NOSPEC: /* * Nothing required here. * * In case of arm64, we rely on the firmware mitigation of * Speculative Store Bypass as controlled via the ssbd kernel * parameter. Whenever the mitigation is enabled, it works * for all of the kernel code with no need to provide any * additional instructions. */ break; /* ST: *(size *)(dst + off) = imm */ case BPF_ST | BPF_MEM | BPF_W: case BPF_ST | BPF_MEM | BPF_H: case BPF_ST | BPF_MEM | BPF_B: case BPF_ST | BPF_MEM | BPF_DW: case BPF_ST | BPF_PROBE_MEM32 | BPF_B: case BPF_ST | BPF_PROBE_MEM32 | BPF_H: case BPF_ST | BPF_PROBE_MEM32 | BPF_W: case BPF_ST | BPF_PROBE_MEM32 | BPF_DW: if (BPF_MODE(insn->code) == BPF_PROBE_MEM32) { emit(A64_ADD(1, tmp2, dst, arena_vm_base), ctx); dst = tmp2; } if (ctx->fpb_offset > 0 && dst == fp && BPF_MODE(insn->code) != BPF_PROBE_MEM32) { dst_adj = fpb; off_adj = off + ctx->fpb_offset; } else { dst_adj = dst; off_adj = off; } /* Load imm to a register then store it */ emit_a64_mov_i(1, tmp, imm, ctx); switch (BPF_SIZE(code)) { case BPF_W: if (is_lsi_offset(off_adj, 2)) { emit(A64_STR32I(tmp, dst_adj, off_adj), ctx); } else { emit_a64_mov_i(1, tmp2, off, ctx); emit(A64_STR32(tmp, dst, tmp2), ctx); } break; case BPF_H: if (is_lsi_offset(off_adj, 1)) { emit(A64_STRHI(tmp, dst_adj, off_adj), ctx); } else { emit_a64_mov_i(1, tmp2, off, ctx); emit(A64_STRH(tmp, dst, tmp2), ctx); } break; case BPF_B: if (is_lsi_offset(off_adj, 0)) { emit(A64_STRBI(tmp, dst_adj, off_adj), ctx); } else { emit_a64_mov_i(1, tmp2, off, ctx); emit(A64_STRB(tmp, dst, tmp2), ctx); } break; case BPF_DW: if (is_lsi_offset(off_adj, 3)) { emit(A64_STR64I(tmp, dst_adj, off_adj), ctx); } else { emit_a64_mov_i(1, tmp2, off, ctx); emit(A64_STR64(tmp, dst, tmp2), ctx); } break; } ret = add_exception_handler(insn, ctx, dst); if (ret) return ret; break; /* STX: *(size *)(dst + off) = src */ case BPF_STX | BPF_MEM | BPF_W: case BPF_STX | BPF_MEM | BPF_H: case BPF_STX | BPF_MEM | BPF_B: case BPF_STX | BPF_MEM | BPF_DW: case BPF_STX | BPF_PROBE_MEM32 | BPF_B: case BPF_STX | BPF_PROBE_MEM32 | BPF_H: case BPF_STX | BPF_PROBE_MEM32 | BPF_W: case BPF_STX | BPF_PROBE_MEM32 | BPF_DW: if (BPF_MODE(insn->code) == BPF_PROBE_MEM32) { emit(A64_ADD(1, tmp2, dst, arena_vm_base), ctx); dst = tmp2; } if (ctx->fpb_offset > 0 && dst == fp && BPF_MODE(insn->code) != BPF_PROBE_MEM32) { dst_adj = fpb; off_adj = off + ctx->fpb_offset; } else { dst_adj = dst; off_adj = off; } switch (BPF_SIZE(code)) { case BPF_W: if (is_lsi_offset(off_adj, 2)) { emit(A64_STR32I(src, dst_adj, off_adj), ctx); } else { emit_a64_mov_i(1, tmp, off, ctx); emit(A64_STR32(src, dst, tmp), ctx); } break; case BPF_H: if (is_lsi_offset(off_adj, 1)) { emit(A64_STRHI(src, dst_adj, off_adj), ctx); } else { emit_a64_mov_i(1, tmp, off, ctx); emit(A64_STRH(src, dst, tmp), ctx); } break; case BPF_B: if (is_lsi_offset(off_adj, 0)) { emit(A64_STRBI(src, dst_adj, off_adj), ctx); } else { emit_a64_mov_i(1, tmp, off, ctx); emit(A64_STRB(src, dst, tmp), ctx); } break; case BPF_DW: if (is_lsi_offset(off_adj, 3)) { emit(A64_STR64I(src, dst_adj, off_adj), ctx); } else { emit_a64_mov_i(1, tmp, off, ctx); emit(A64_STR64(src, dst, tmp), ctx); } break; } ret = add_exception_handler(insn, ctx, dst); if (ret) return ret; break; case BPF_STX | BPF_ATOMIC | BPF_W: case BPF_STX | BPF_ATOMIC | BPF_DW: case BPF_STX | BPF_PROBE_ATOMIC | BPF_W: case BPF_STX | BPF_PROBE_ATOMIC | BPF_DW: if (cpus_have_cap(ARM64_HAS_LSE_ATOMICS)) ret = emit_lse_atomic(insn, ctx); else ret = emit_ll_sc_atomic(insn, ctx); if (ret) return ret; ret = add_exception_handler(insn, ctx, dst); if (ret) return ret; break; default: pr_err_once("unknown opcode %02x\n", code); return -EINVAL; } return 0; } /* * Return 0 if FP may change at runtime, otherwise find the minimum negative * offset to FP, converts it to positive number, and align down to 8 bytes. */ static int find_fpb_offset(struct bpf_prog *prog) { int i; int offset = 0; for (i = 0; i < prog->len; i++) { const struct bpf_insn *insn = &prog->insnsi[i]; const u8 class = BPF_CLASS(insn->code); const u8 mode = BPF_MODE(insn->code); const u8 src = insn->src_reg; const u8 dst = insn->dst_reg; const s32 imm = insn->imm; const s16 off = insn->off; switch (class) { case BPF_STX: case BPF_ST: /* fp holds atomic operation result */ if (class == BPF_STX && mode == BPF_ATOMIC && ((imm == BPF_XCHG || imm == (BPF_FETCH | BPF_ADD) || imm == (BPF_FETCH | BPF_AND) || imm == (BPF_FETCH | BPF_XOR) || imm == (BPF_FETCH | BPF_OR)) && src == BPF_REG_FP)) return 0; if (mode == BPF_MEM && dst == BPF_REG_FP && off < offset) offset = insn->off; break; case BPF_JMP32: case BPF_JMP: break; case BPF_LDX: case BPF_LD: /* fp holds load result */ if (dst == BPF_REG_FP) return 0; if (class == BPF_LDX && mode == BPF_MEM && src == BPF_REG_FP && off < offset) offset = off; break; case BPF_ALU: case BPF_ALU64: default: /* fp holds ALU result */ if (dst == BPF_REG_FP) return 0; } } if (offset < 0) { /* * safely be converted to a positive 'int', since insn->off * is 's16' */ offset = -offset; /* align down to 8 bytes */ offset = ALIGN_DOWN(offset, 8); } return offset; } static int build_body(struct jit_ctx *ctx, bool extra_pass) { const struct bpf_prog *prog = ctx->prog; int i; /* * - offset[0] offset of the end of prologue, * start of the 1st instruction. * - offset[1] - offset of the end of 1st instruction, * start of the 2nd instruction * [....] * - offset[3] - offset of the end of 3rd instruction, * start of 4th instruction */ for (i = 0; i < prog->len; i++) { const struct bpf_insn *insn = &prog->insnsi[i]; int ret; if (ctx->image == NULL) ctx->offset[i] = ctx->idx; ret = build_insn(insn, ctx, extra_pass); if (ret > 0) { i++; if (ctx->image == NULL) ctx->offset[i] = ctx->idx; continue; } if (ret) return ret; } /* * offset is allocated with prog->len + 1 so fill in * the last element with the offset after the last * instruction (end of program) */ if (ctx->image == NULL) ctx->offset[i] = ctx->idx; return 0; } static int validate_code(struct jit_ctx *ctx) { int i; for (i = 0; i < ctx->idx; i++) { u32 a64_insn = le32_to_cpu(ctx->image[i]); if (a64_insn == AARCH64_BREAK_FAULT) return -1; } return 0; } static int validate_ctx(struct jit_ctx *ctx) { if (validate_code(ctx)) return -1; if (WARN_ON_ONCE(ctx->exentry_idx != ctx->prog->aux->num_exentries)) return -1; return 0; } static inline void bpf_flush_icache(void *start, void *end) { flush_icache_range((unsigned long)start, (unsigned long)end); } struct arm64_jit_data { struct bpf_binary_header *header; u8 *ro_image; struct bpf_binary_header *ro_header; struct jit_ctx ctx; }; struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) { int image_size, prog_size, extable_size, extable_align, extable_offset; struct bpf_prog *tmp, *orig_prog = prog; struct bpf_binary_header *header; struct bpf_binary_header *ro_header; struct arm64_jit_data *jit_data; bool was_classic = bpf_prog_was_classic(prog); bool tmp_blinded = false; bool extra_pass = false; struct jit_ctx ctx; u64 arena_vm_start; u8 *image_ptr; u8 *ro_image_ptr; if (!prog->jit_requested) return orig_prog; tmp = bpf_jit_blind_constants(prog); /* If blinding was requested and we failed during blinding, * we must fall back to the interpreter. */ if (IS_ERR(tmp)) return orig_prog; if (tmp != prog) { tmp_blinded = true; prog = tmp; } arena_vm_start = bpf_arena_get_kern_vm_start(prog->aux->arena); jit_data = prog->aux->jit_data; if (!jit_data) { jit_data = kzalloc(sizeof(*jit_data), GFP_KERNEL); if (!jit_data) { prog = orig_prog; goto out; } prog->aux->jit_data = jit_data; } if (jit_data->ctx.offset) { ctx = jit_data->ctx; ro_image_ptr = jit_data->ro_image; ro_header = jit_data->ro_header; header = jit_data->header; image_ptr = (void *)header + ((void *)ro_image_ptr - (void *)ro_header); extra_pass = true; prog_size = sizeof(u32) * ctx.idx; goto skip_init_ctx; } memset(&ctx, 0, sizeof(ctx)); ctx.prog = prog; ctx.offset = kvcalloc(prog->len + 1, sizeof(int), GFP_KERNEL); if (ctx.offset == NULL) { prog = orig_prog; goto out_off; } ctx.fpb_offset = find_fpb_offset(prog); ctx.user_vm_start = bpf_arena_get_user_vm_start(prog->aux->arena); /* * 1. Initial fake pass to compute ctx->idx and ctx->offset. * * BPF line info needs ctx->offset[i] to be the offset of * instruction[i] in jited image, so build prologue first. */ if (build_prologue(&ctx, was_classic, prog->aux->exception_cb, arena_vm_start)) { prog = orig_prog; goto out_off; } if (build_body(&ctx, extra_pass)) { prog = orig_prog; goto out_off; } ctx.epilogue_offset = ctx.idx; build_epilogue(&ctx, prog->aux->exception_cb); build_plt(&ctx); extable_align = __alignof__(struct exception_table_entry); extable_size = prog->aux->num_exentries * sizeof(struct exception_table_entry); /* Now we know the actual image size. */ prog_size = sizeof(u32) * ctx.idx; /* also allocate space for plt target */ extable_offset = round_up(prog_size + PLT_TARGET_SIZE, extable_align); image_size = extable_offset + extable_size; ro_header = bpf_jit_binary_pack_alloc(image_size, &ro_image_ptr, sizeof(u32), &header, &image_ptr, jit_fill_hole); if (!ro_header) { prog = orig_prog; goto out_off; } /* 2. Now, the actual pass. */ /* * Use the image(RW) for writing the JITed instructions. But also save * the ro_image(RX) for calculating the offsets in the image. The RW * image will be later copied to the RX image from where the program * will run. The bpf_jit_binary_pack_finalize() will do this copy in the * final step. */ ctx.image = (__le32 *)image_ptr; ctx.ro_image = (__le32 *)ro_image_ptr; if (extable_size) prog->aux->extable = (void *)ro_image_ptr + extable_offset; skip_init_ctx: ctx.idx = 0; ctx.exentry_idx = 0; build_prologue(&ctx, was_classic, prog->aux->exception_cb, arena_vm_start); if (build_body(&ctx, extra_pass)) { prog = orig_prog; goto out_free_hdr; } build_epilogue(&ctx, prog->aux->exception_cb); build_plt(&ctx); /* 3. Extra pass to validate JITed code. */ if (validate_ctx(&ctx)) { prog = orig_prog; goto out_free_hdr; } /* And we're done. */ if (bpf_jit_enable > 1) bpf_jit_dump(prog->len, prog_size, 2, ctx.image); if (!prog->is_func || extra_pass) { if (extra_pass && ctx.idx != jit_data->ctx.idx) { pr_err_once("multi-func JIT bug %d != %d\n", ctx.idx, jit_data->ctx.idx); prog->bpf_func = NULL; prog->jited = 0; prog->jited_len = 0; goto out_free_hdr; } if (WARN_ON(bpf_jit_binary_pack_finalize(ro_header, header))) { /* ro_header has been freed */ ro_header = NULL; prog = orig_prog; goto out_off; } /* * The instructions have now been copied to the ROX region from * where they will execute. Now the data cache has to be cleaned to * the PoU and the I-cache has to be invalidated for the VAs. */ bpf_flush_icache(ro_header, ctx.ro_image + ctx.idx); } else { jit_data->ctx = ctx; jit_data->ro_image = ro_image_ptr; jit_data->header = header; jit_data->ro_header = ro_header; } prog->bpf_func = (void *)ctx.ro_image; prog->jited = 1; prog->jited_len = prog_size; if (!prog->is_func || extra_pass) { int i; /* offset[prog->len] is the size of program */ for (i = 0; i <= prog->len; i++) ctx.offset[i] *= AARCH64_INSN_SIZE; bpf_prog_fill_jited_linfo(prog, ctx.offset + 1); out_off: kvfree(ctx.offset); kfree(jit_data); prog->aux->jit_data = NULL; } out: if (tmp_blinded) bpf_jit_prog_release_other(prog, prog == orig_prog ? tmp : orig_prog); return prog; out_free_hdr: if (header) { bpf_arch_text_copy(&ro_header->size, &header->size, sizeof(header->size)); bpf_jit_binary_pack_free(ro_header, header); } goto out_off; } bool bpf_jit_supports_kfunc_call(void) { return true; } void *bpf_arch_text_copy(void *dst, void *src, size_t len) { if (!aarch64_insn_copy(dst, src, len)) return ERR_PTR(-EINVAL); return dst; } u64 bpf_jit_alloc_exec_limit(void) { return VMALLOC_END - VMALLOC_START; } /* Indicate the JIT backend supports mixing bpf2bpf and tailcalls. */ bool bpf_jit_supports_subprog_tailcalls(void) { return true; } static void invoke_bpf_prog(struct jit_ctx *ctx, struct bpf_tramp_link *l, int args_off, int retval_off, int run_ctx_off, bool save_ret) { __le32 *branch; u64 enter_prog; u64 exit_prog; struct bpf_prog *p = l->link.prog; int cookie_off = offsetof(struct bpf_tramp_run_ctx, bpf_cookie); enter_prog = (u64)bpf_trampoline_enter(p); exit_prog = (u64)bpf_trampoline_exit(p); if (l->cookie == 0) { /* if cookie is zero, one instruction is enough to store it */ emit(A64_STR64I(A64_ZR, A64_SP, run_ctx_off + cookie_off), ctx); } else { emit_a64_mov_i64(A64_R(10), l->cookie, ctx); emit(A64_STR64I(A64_R(10), A64_SP, run_ctx_off + cookie_off), ctx); } /* save p to callee saved register x19 to avoid loading p with mov_i64 * each time. */ emit_addr_mov_i64(A64_R(19), (const u64)p, ctx); /* arg1: prog */ emit(A64_MOV(1, A64_R(0), A64_R(19)), ctx); /* arg2: &run_ctx */ emit(A64_ADD_I(1, A64_R(1), A64_SP, run_ctx_off), ctx); emit_call(enter_prog, ctx); /* save return value to callee saved register x20 */ emit(A64_MOV(1, A64_R(20), A64_R(0)), ctx); /* if (__bpf_prog_enter(prog) == 0) * goto skip_exec_of_prog; */ branch = ctx->image + ctx->idx; emit(A64_NOP, ctx); emit(A64_ADD_I(1, A64_R(0), A64_SP, args_off), ctx); if (!p->jited) emit_addr_mov_i64(A64_R(1), (const u64)p->insnsi, ctx); emit_call((const u64)p->bpf_func, ctx); if (save_ret) emit(A64_STR64I(A64_R(0), A64_SP, retval_off), ctx); if (ctx->image) { int offset = &ctx->image[ctx->idx] - branch; *branch = cpu_to_le32(A64_CBZ(1, A64_R(0), offset)); } /* arg1: prog */ emit(A64_MOV(1, A64_R(0), A64_R(19)), ctx); /* arg2: start time */ emit(A64_MOV(1, A64_R(1), A64_R(20)), ctx); /* arg3: &run_ctx */ emit(A64_ADD_I(1, A64_R(2), A64_SP, run_ctx_off), ctx); emit_call(exit_prog, ctx); } static void invoke_bpf_mod_ret(struct jit_ctx *ctx, struct bpf_tramp_links *tl, int args_off, int retval_off, int run_ctx_off, __le32 **branches) { int i; /* The first fmod_ret program will receive a garbage return value. * Set this to 0 to avoid confusing the program. */ emit(A64_STR64I(A64_ZR, A64_SP, retval_off), ctx); for (i = 0; i < tl->nr_links; i++) { invoke_bpf_prog(ctx, tl->links[i], args_off, retval_off, run_ctx_off, true); /* if (*(u64 *)(sp + retval_off) != 0) * goto do_fexit; */ emit(A64_LDR64I(A64_R(10), A64_SP, retval_off), ctx); /* Save the location of branch, and generate a nop. * This nop will be replaced with a cbnz later. */ branches[i] = ctx->image + ctx->idx; emit(A64_NOP, ctx); } } static void save_args(struct jit_ctx *ctx, int args_off, int nregs) { int i; for (i = 0; i < nregs; i++) { emit(A64_STR64I(i, A64_SP, args_off), ctx); args_off += 8; } } static void restore_args(struct jit_ctx *ctx, int args_off, int nregs) { int i; for (i = 0; i < nregs; i++) { emit(A64_LDR64I(i, A64_SP, args_off), ctx); args_off += 8; } } /* Based on the x86's implementation of arch_prepare_bpf_trampoline(). * * bpf prog and function entry before bpf trampoline hooked: * mov x9, lr * nop * * bpf prog and function entry after bpf trampoline hooked: * mov x9, lr * bl <bpf_trampoline or plt> * */ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im, struct bpf_tramp_links *tlinks, void *func_addr, int nregs, u32 flags) { int i; int stack_size; int retaddr_off; int regs_off; int retval_off; int args_off; int nregs_off; int ip_off; int run_ctx_off; struct bpf_tramp_links *fentry = &tlinks[BPF_TRAMP_FENTRY]; struct bpf_tramp_links *fexit = &tlinks[BPF_TRAMP_FEXIT]; struct bpf_tramp_links *fmod_ret = &tlinks[BPF_TRAMP_MODIFY_RETURN]; bool save_ret; __le32 **branches = NULL; /* trampoline stack layout: * [ parent ip ] * [ FP ] * SP + retaddr_off [ self ip ] * [ FP ] * * [ padding ] align SP to multiples of 16 * * [ x20 ] callee saved reg x20 * SP + regs_off [ x19 ] callee saved reg x19 * * SP + retval_off [ return value ] BPF_TRAMP_F_CALL_ORIG or * BPF_TRAMP_F_RET_FENTRY_RET * * [ arg reg N ] * [ ... ] * SP + args_off [ arg reg 1 ] * * SP + nregs_off [ arg regs count ] * * SP + ip_off [ traced function ] BPF_TRAMP_F_IP_ARG flag * * SP + run_ctx_off [ bpf_tramp_run_ctx ] */ stack_size = 0; run_ctx_off = stack_size; /* room for bpf_tramp_run_ctx */ stack_size += round_up(sizeof(struct bpf_tramp_run_ctx), 8); ip_off = stack_size; /* room for IP address argument */ if (flags & BPF_TRAMP_F_IP_ARG) stack_size += 8; nregs_off = stack_size; /* room for args count */ stack_size += 8; args_off = stack_size; /* room for args */ stack_size += nregs * 8; /* room for return value */ retval_off = stack_size; save_ret = flags & (BPF_TRAMP_F_CALL_ORIG | BPF_TRAMP_F_RET_FENTRY_RET); if (save_ret) stack_size += 8; /* room for callee saved registers, currently x19 and x20 are used */ regs_off = stack_size; stack_size += 16; /* round up to multiples of 16 to avoid SPAlignmentFault */ stack_size = round_up(stack_size, 16); /* return address locates above FP */ retaddr_off = stack_size + 8; /* bpf trampoline may be invoked by 3 instruction types: * 1. bl, attached to bpf prog or kernel function via short jump * 2. br, attached to bpf prog or kernel function via long jump * 3. blr, working as a function pointer, used by struct_ops. * So BTI_JC should used here to support both br and blr. */ emit_bti(A64_BTI_JC, ctx); /* frame for parent function */ emit(A64_PUSH(A64_FP, A64_R(9), A64_SP), ctx); emit(A64_MOV(1, A64_FP, A64_SP), ctx); /* frame for patched function */ emit(A64_PUSH(A64_FP, A64_LR, A64_SP), ctx); emit(A64_MOV(1, A64_FP, A64_SP), ctx); /* allocate stack space */ emit(A64_SUB_I(1, A64_SP, A64_SP, stack_size), ctx); if (flags & BPF_TRAMP_F_IP_ARG) { /* save ip address of the traced function */ emit_addr_mov_i64(A64_R(10), (const u64)func_addr, ctx); emit(A64_STR64I(A64_R(10), A64_SP, ip_off), ctx); } /* save arg regs count*/ emit(A64_MOVZ(1, A64_R(10), nregs, 0), ctx); emit(A64_STR64I(A64_R(10), A64_SP, nregs_off), ctx); /* save arg regs */ save_args(ctx, args_off, nregs); /* save callee saved registers */ emit(A64_STR64I(A64_R(19), A64_SP, regs_off), ctx); emit(A64_STR64I(A64_R(20), A64_SP, regs_off + 8), ctx); if (flags & BPF_TRAMP_F_CALL_ORIG) { emit_a64_mov_i64(A64_R(0), (const u64)im, ctx); emit_call((const u64)__bpf_tramp_enter, ctx); } for (i = 0; i < fentry->nr_links; i++) invoke_bpf_prog(ctx, fentry->links[i], args_off, retval_off, run_ctx_off, flags & BPF_TRAMP_F_RET_FENTRY_RET); if (fmod_ret->nr_links) { branches = kcalloc(fmod_ret->nr_links, sizeof(__le32 *), GFP_KERNEL); if (!branches) return -ENOMEM; invoke_bpf_mod_ret(ctx, fmod_ret, args_off, retval_off, run_ctx_off, branches); } if (flags & BPF_TRAMP_F_CALL_ORIG) { restore_args(ctx, args_off, nregs); /* call original func */ emit(A64_LDR64I(A64_R(10), A64_SP, retaddr_off), ctx); emit(A64_ADR(A64_LR, AARCH64_INSN_SIZE * 2), ctx); emit(A64_RET(A64_R(10)), ctx); /* store return value */ emit(A64_STR64I(A64_R(0), A64_SP, retval_off), ctx); /* reserve a nop for bpf_tramp_image_put */ im->ip_after_call = ctx->ro_image + ctx->idx; emit(A64_NOP, ctx); } /* update the branches saved in invoke_bpf_mod_ret with cbnz */ for (i = 0; i < fmod_ret->nr_links && ctx->image != NULL; i++) { int offset = &ctx->image[ctx->idx] - branches[i]; *branches[i] = cpu_to_le32(A64_CBNZ(1, A64_R(10), offset)); } for (i = 0; i < fexit->nr_links; i++) invoke_bpf_prog(ctx, fexit->links[i], args_off, retval_off, run_ctx_off, false); if (flags & BPF_TRAMP_F_CALL_ORIG) { im->ip_epilogue = ctx->ro_image + ctx->idx; emit_a64_mov_i64(A64_R(0), (const u64)im, ctx); emit_call((const u64)__bpf_tramp_exit, ctx); } if (flags & BPF_TRAMP_F_RESTORE_REGS) restore_args(ctx, args_off, nregs); /* restore callee saved register x19 and x20 */ emit(A64_LDR64I(A64_R(19), A64_SP, regs_off), ctx); emit(A64_LDR64I(A64_R(20), A64_SP, regs_off + 8), ctx); if (save_ret) emit(A64_LDR64I(A64_R(0), A64_SP, retval_off), ctx); /* reset SP */ emit(A64_MOV(1, A64_SP, A64_FP), ctx); /* pop frames */ emit(A64_POP(A64_FP, A64_LR, A64_SP), ctx); emit(A64_POP(A64_FP, A64_R(9), A64_SP), ctx); if (flags & BPF_TRAMP_F_SKIP_FRAME) { /* skip patched function, return to parent */ emit(A64_MOV(1, A64_LR, A64_R(9)), ctx); emit(A64_RET(A64_R(9)), ctx); } else { /* return to patched function */ emit(A64_MOV(1, A64_R(10), A64_LR), ctx); emit(A64_MOV(1, A64_LR, A64_R(9)), ctx); emit(A64_RET(A64_R(10)), ctx); } kfree(branches); return ctx->idx; } static int btf_func_model_nregs(const struct btf_func_model *m) { int nregs = m->nr_args; int i; /* extra registers needed for struct argument */ for (i = 0; i < MAX_BPF_FUNC_ARGS; i++) { /* The arg_size is at most 16 bytes, enforced by the verifier. */ if (m->arg_flags[i] & BTF_FMODEL_STRUCT_ARG) nregs += (m->arg_size[i] + 7) / 8 - 1; } return nregs; } int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags, struct bpf_tramp_links *tlinks, void *func_addr) { struct jit_ctx ctx = { .image = NULL, .idx = 0, }; struct bpf_tramp_image im; int nregs, ret; nregs = btf_func_model_nregs(m); /* the first 8 registers are used for arguments */ if (nregs > 8) return -ENOTSUPP; ret = prepare_trampoline(&ctx, &im, tlinks, func_addr, nregs, flags); if (ret < 0) return ret; return ret < 0 ? ret : ret * AARCH64_INSN_SIZE; } void *arch_alloc_bpf_trampoline(unsigned int size) { return bpf_prog_pack_alloc(size, jit_fill_hole); } void arch_free_bpf_trampoline(void *image, unsigned int size) { bpf_prog_pack_free(image, size); } int arch_protect_bpf_trampoline(void *image, unsigned int size) { return 0; } int arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *ro_image, void *ro_image_end, const struct btf_func_model *m, u32 flags, struct bpf_tramp_links *tlinks, void *func_addr) { int ret, nregs; void *image, *tmp; u32 size = ro_image_end - ro_image; /* image doesn't need to be in module memory range, so we can * use kvmalloc. */ image = kvmalloc(size, GFP_KERNEL); if (!image) return -ENOMEM; struct jit_ctx ctx = { .image = image, .ro_image = ro_image, .idx = 0, }; nregs = btf_func_model_nregs(m); /* the first 8 registers are used for arguments */ if (nregs > 8) return -ENOTSUPP; jit_fill_hole(image, (unsigned int)(ro_image_end - ro_image)); ret = prepare_trampoline(&ctx, im, tlinks, func_addr, nregs, flags); if (ret > 0 && validate_code(&ctx) < 0) { ret = -EINVAL; goto out; } if (ret > 0) ret *= AARCH64_INSN_SIZE; tmp = bpf_arch_text_copy(ro_image, image, size); if (IS_ERR(tmp)) { ret = PTR_ERR(tmp); goto out; } bpf_flush_icache(ro_image, ro_image + size); out: kvfree(image); return ret; } static bool is_long_jump(void *ip, void *target) { long offset; /* NULL target means this is a NOP */ if (!target) return false; offset = (long)target - (long)ip; return offset < -SZ_128M || offset >= SZ_128M; } static int gen_branch_or_nop(enum aarch64_insn_branch_type type, void *ip, void *addr, void *plt, u32 *insn) { void *target; if (!addr) { *insn = aarch64_insn_gen_nop(); return 0; } if (is_long_jump(ip, addr)) target = plt; else target = addr; *insn = aarch64_insn_gen_branch_imm((unsigned long)ip, (unsigned long)target, type); return *insn != AARCH64_BREAK_FAULT ? 0 : -EFAULT; } /* Replace the branch instruction from @ip to @old_addr in a bpf prog or a bpf * trampoline with the branch instruction from @ip to @new_addr. If @old_addr * or @new_addr is NULL, the old or new instruction is NOP. * * When @ip is the bpf prog entry, a bpf trampoline is being attached or * detached. Since bpf trampoline and bpf prog are allocated separately with * vmalloc, the address distance may exceed 128MB, the maximum branch range. * So long jump should be handled. * * When a bpf prog is constructed, a plt pointing to empty trampoline * dummy_tramp is placed at the end: * * bpf_prog: * mov x9, lr * nop // patchsite * ... * ret * * plt: * ldr x10, target * br x10 * target: * .quad dummy_tramp // plt target * * This is also the state when no trampoline is attached. * * When a short-jump bpf trampoline is attached, the patchsite is patched * to a bl instruction to the trampoline directly: * * bpf_prog: * mov x9, lr * bl <short-jump bpf trampoline address> // patchsite * ... * ret * * plt: * ldr x10, target * br x10 * target: * .quad dummy_tramp // plt target * * When a long-jump bpf trampoline is attached, the plt target is filled with * the trampoline address and the patchsite is patched to a bl instruction to * the plt: * * bpf_prog: * mov x9, lr * bl plt // patchsite * ... * ret * * plt: * ldr x10, target * br x10 * target: * .quad <long-jump bpf trampoline address> // plt target * * The dummy_tramp is used to prevent another CPU from jumping to unknown * locations during the patching process, making the patching process easier. */ int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type poke_type, void *old_addr, void *new_addr) { int ret; u32 old_insn; u32 new_insn; u32 replaced; struct bpf_plt *plt = NULL; unsigned long size = 0UL; unsigned long offset = ~0UL; enum aarch64_insn_branch_type branch_type; char namebuf[KSYM_NAME_LEN]; void *image = NULL; u64 plt_target = 0ULL; bool poking_bpf_entry; if (!__bpf_address_lookup((unsigned long)ip, &size, &offset, namebuf)) /* Only poking bpf text is supported. Since kernel function * entry is set up by ftrace, we reply on ftrace to poke kernel * functions. */ return -ENOTSUPP; image = ip - offset; /* zero offset means we're poking bpf prog entry */ poking_bpf_entry = (offset == 0UL); /* bpf prog entry, find plt and the real patchsite */ if (poking_bpf_entry) { /* plt locates at the end of bpf prog */ plt = image + size - PLT_TARGET_OFFSET; /* skip to the nop instruction in bpf prog entry: * bti c // if BTI enabled * mov x9, x30 * nop */ ip = image + POKE_OFFSET * AARCH64_INSN_SIZE; } /* long jump is only possible at bpf prog entry */ if (WARN_ON((is_long_jump(ip, new_addr) || is_long_jump(ip, old_addr)) && !poking_bpf_entry)) return -EINVAL; if (poke_type == BPF_MOD_CALL) branch_type = AARCH64_INSN_BRANCH_LINK; else branch_type = AARCH64_INSN_BRANCH_NOLINK; if (gen_branch_or_nop(branch_type, ip, old_addr, plt, &old_insn) < 0) return -EFAULT; if (gen_branch_or_nop(branch_type, ip, new_addr, plt, &new_insn) < 0) return -EFAULT; if (is_long_jump(ip, new_addr)) plt_target = (u64)new_addr; else if (is_long_jump(ip, old_addr)) /* if the old target is a long jump and the new target is not, * restore the plt target to dummy_tramp, so there is always a * legal and harmless address stored in plt target, and we'll * never jump from plt to an unknown place. */ plt_target = (u64)&dummy_tramp; if (plt_target) { /* non-zero plt_target indicates we're patching a bpf prog, * which is read only. */ if (set_memory_rw(PAGE_MASK & ((uintptr_t)&plt->target), 1)) return -EFAULT; WRITE_ONCE(plt->target, plt_target); set_memory_ro(PAGE_MASK & ((uintptr_t)&plt->target), 1); /* since plt target points to either the new trampoline * or dummy_tramp, even if another CPU reads the old plt * target value before fetching the bl instruction to plt, * it will be brought back by dummy_tramp, so no barrier is * required here. */ } /* if the old target and the new target are both long jumps, no * patching is required */ if (old_insn == new_insn) return 0; mutex_lock(&text_mutex); if (aarch64_insn_read(ip, &replaced)) { ret = -EFAULT; goto out; } if (replaced != old_insn) { ret = -EFAULT; goto out; } /* We call aarch64_insn_patch_text_nosync() to replace instruction * atomically, so no other CPUs will fetch a half-new and half-old * instruction. But there is chance that another CPU executes the * old instruction after the patching operation finishes (e.g., * pipeline not flushed, or icache not synchronized yet). * * 1. when a new trampoline is attached, it is not a problem for * different CPUs to jump to different trampolines temporarily. * * 2. when an old trampoline is freed, we should wait for all other * CPUs to exit the trampoline and make sure the trampoline is no * longer reachable, since bpf_tramp_image_put() function already * uses percpu_ref and task-based rcu to do the sync, no need to call * the sync version here, see bpf_tramp_image_put() for details. */ ret = aarch64_insn_patch_text_nosync(ip, new_insn); out: mutex_unlock(&text_mutex); return ret; } bool bpf_jit_supports_ptr_xchg(void) { return true; } bool bpf_jit_supports_exceptions(void) { /* We unwind through both kernel frames starting from within bpf_throw * call and BPF frames. Therefore we require FP unwinder to be enabled * to walk kernel frames and reach BPF frames in the stack trace. * ARM64 kernel is aways compiled with CONFIG_FRAME_POINTER=y */ return true; } bool bpf_jit_supports_arena(void) { return true; } bool bpf_jit_supports_insn(struct bpf_insn *insn, bool in_arena) { if (!in_arena) return true; switch (insn->code) { case BPF_STX | BPF_ATOMIC | BPF_W: case BPF_STX | BPF_ATOMIC | BPF_DW: if (!cpus_have_cap(ARM64_HAS_LSE_ATOMICS)) return false; } return true; } bool bpf_jit_supports_percpu_insn(void) { return true; } bool bpf_jit_inlines_helper_call(s32 imm) { switch (imm) { case BPF_FUNC_get_smp_processor_id: case BPF_FUNC_get_current_task: case BPF_FUNC_get_current_task_btf: return true; default: return false; } } void bpf_jit_free(struct bpf_prog *prog) { if (prog->jited) { struct arm64_jit_data *jit_data = prog->aux->jit_data; struct bpf_binary_header *hdr; /* * If we fail the final pass of JIT (from jit_subprogs), * the program may not be finalized yet. Call finalize here * before freeing it. */ if (jit_data) { bpf_arch_text_copy(&jit_data->ro_header->size, &jit_data->header->size, sizeof(jit_data->header->size)); kfree(jit_data); } hdr = bpf_jit_binary_pack_hdr(prog); bpf_jit_binary_pack_free(hdr, NULL); WARN_ON_ONCE(!bpf_prog_kallsyms_verify_off(prog)); } bpf_prog_unlock_free(prog); }
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1