mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-01-09 22:50:41 +00:00
e63985ecd2
The riscv BPF JIT doesn't emit proper kCFI prologues for BPF programs and struct_ops trampolines when CONFIG_CFI_CLANG is enabled. This causes CFI failures when calling BPF programs and can even crash the kernel due to invalid memory accesses. Example crash: root@rv-selftester:~/bpf# ./test_progs -a dummy_st_ops Unable to handle kernel paging request at virtual address ffffffff78204ffc Oops [#1] Modules linked in: bpf_testmod(OE) [....] CPU: 3 PID: 356 Comm: test_progs Tainted: P OE 6.8.0-rc1 #1 Hardware name: riscv-virtio,qemu (DT) epc : bpf_struct_ops_test_run+0x28c/0x5fc ra : bpf_struct_ops_test_run+0x26c/0x5fc epc : ffffffff82958010 ra : ffffffff82957ff0 sp : ff200000007abc80 gp : ffffffff868d6218 tp : ff6000008d87b840 t0 : 000000000000000f t1 : 0000000000000000 t2 : 000000002005793e s0 : ff200000007abcf0 s1 : ff6000008a90fee0 a0 : 0000000000000000 a1 : 0000000000000000 a2 : 0000000000000000 a3 : 0000000000000000 a4 : 0000000000000000 a5 : ffffffff868dba26 a6 : 0000000000000001 a7 : 0000000052464e43 s2 : 00007ffffc0a95f0 s3 : ff6000008a90fe80 s4 : ff60000084c24c00 s5 : ffffffff78205000 s6 : ff60000088750648 s7 : ff20000000035008 s8 : fffffffffffffff4 s9 : ffffffff86200610 s10: 0000000000000000 s11: 0000000000000000 t3 : ffffffff8483dc30 t4 : ffffffff8483dc10 t5 : ffffffff8483dbf0 t6 : ffffffff8483dbd0 status: 0000000200000120 badaddr: ffffffff78204ffc cause: 000000000000000d [<ffffffff82958010>] bpf_struct_ops_test_run+0x28c/0x5fc [<ffffffff805083ee>] bpf_prog_test_run+0x170/0x548 [<ffffffff805029c8>] __sys_bpf+0x2d2/0x378 [<ffffffff804ff570>] __riscv_sys_bpf+0x5c/0x120 [<ffffffff8000e8fe>] syscall_handler+0x62/0xe4 [<ffffffff83362df6>] do_trap_ecall_u+0xc6/0x27c [<ffffffff833822c4>] ret_from_exception+0x0/0x64 Code: b603 0109 b683 0189 b703 0209 8493 0609 157d 8d65 (a303) ffca ---[ end trace 0000000000000000 ]--- Kernel panic - not syncing: Fatal exception SMP: stopping secondary CPUs Implement proper kCFI prologues for the BPF programs and callbacks and drop __nocfi for riscv64. Fix the trampoline generation code to emit kCFI prologue when a struct_ops trampoline is being prepared. Signed-off-by: Puranjay Mohan <puranjay12@gmail.com> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net> Signed-off-by: Andrii Nakryiko <andrii@kernel.org> Acked-by: Björn Töpel <bjorn@kernel.org> Link: https://lore.kernel.org/bpf/20240303170207.82201-2-puranjay12@gmail.com
131 lines
3.1 KiB
C
131 lines
3.1 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
||
/*
|
||
* Clang Control Flow Integrity (CFI) support.
|
||
*
|
||
* Copyright (C) 2023 Google LLC
|
||
*/
|
||
#include <linux/cfi.h>
|
||
#include <asm/insn.h>
|
||
|
||
/*
|
||
* Returns the target address and the expected type when regs->epc points
|
||
* to a compiler-generated CFI trap.
|
||
*/
|
||
static bool decode_cfi_insn(struct pt_regs *regs, unsigned long *target,
|
||
u32 *type)
|
||
{
|
||
unsigned long *regs_ptr = (unsigned long *)regs;
|
||
int rs1_num;
|
||
u32 insn;
|
||
|
||
*target = *type = 0;
|
||
|
||
/*
|
||
* The compiler generates the following instruction sequence
|
||
* for indirect call checks:
|
||
*
|
||
* lw t1, -4(<reg>)
|
||
* lui t2, <hi20>
|
||
* addiw t2, t2, <lo12>
|
||
* beq t1, t2, .Ltmp1
|
||
* ebreak ; <- regs->epc
|
||
* .Ltmp1:
|
||
* jalr <reg>
|
||
*
|
||
* We can read the expected type and the target address from the
|
||
* registers passed to the beq/jalr instructions.
|
||
*/
|
||
if (get_kernel_nofault(insn, (void *)regs->epc - 4))
|
||
return false;
|
||
if (!riscv_insn_is_beq(insn))
|
||
return false;
|
||
|
||
*type = (u32)regs_ptr[RV_EXTRACT_RS1_REG(insn)];
|
||
|
||
if (get_kernel_nofault(insn, (void *)regs->epc) ||
|
||
get_kernel_nofault(insn, (void *)regs->epc + GET_INSN_LENGTH(insn)))
|
||
return false;
|
||
|
||
if (riscv_insn_is_jalr(insn))
|
||
rs1_num = RV_EXTRACT_RS1_REG(insn);
|
||
else if (riscv_insn_is_c_jalr(insn))
|
||
rs1_num = RVC_EXTRACT_C2_RS1_REG(insn);
|
||
else
|
||
return false;
|
||
|
||
*target = regs_ptr[rs1_num];
|
||
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
* Checks if the ebreak trap is because of a CFI failure, and handles the trap
|
||
* if needed. Returns a bug_trap_type value similarly to report_bug.
|
||
*/
|
||
enum bug_trap_type handle_cfi_failure(struct pt_regs *regs)
|
||
{
|
||
unsigned long target;
|
||
u32 type;
|
||
|
||
if (!is_cfi_trap(regs->epc))
|
||
return BUG_TRAP_TYPE_NONE;
|
||
|
||
if (!decode_cfi_insn(regs, &target, &type))
|
||
return report_cfi_failure_noaddr(regs, regs->epc);
|
||
|
||
return report_cfi_failure(regs, regs->epc, &target, type);
|
||
}
|
||
|
||
#ifdef CONFIG_CFI_CLANG
|
||
struct bpf_insn;
|
||
|
||
/* Must match bpf_func_t / DEFINE_BPF_PROG_RUN() */
|
||
extern unsigned int __bpf_prog_runX(const void *ctx,
|
||
const struct bpf_insn *insn);
|
||
|
||
/*
|
||
* Force a reference to the external symbol so the compiler generates
|
||
* __kcfi_typid.
|
||
*/
|
||
__ADDRESSABLE(__bpf_prog_runX);
|
||
|
||
/* u32 __ro_after_init cfi_bpf_hash = __kcfi_typeid___bpf_prog_runX; */
|
||
asm (
|
||
" .pushsection .data..ro_after_init,\"aw\",@progbits \n"
|
||
" .type cfi_bpf_hash,@object \n"
|
||
" .globl cfi_bpf_hash \n"
|
||
" .p2align 2, 0x0 \n"
|
||
"cfi_bpf_hash: \n"
|
||
" .word __kcfi_typeid___bpf_prog_runX \n"
|
||
" .size cfi_bpf_hash, 4 \n"
|
||
" .popsection \n"
|
||
);
|
||
|
||
/* Must match bpf_callback_t */
|
||
extern u64 __bpf_callback_fn(u64, u64, u64, u64, u64);
|
||
|
||
__ADDRESSABLE(__bpf_callback_fn);
|
||
|
||
/* u32 __ro_after_init cfi_bpf_subprog_hash = __kcfi_typeid___bpf_callback_fn; */
|
||
asm (
|
||
" .pushsection .data..ro_after_init,\"aw\",@progbits \n"
|
||
" .type cfi_bpf_subprog_hash,@object \n"
|
||
" .globl cfi_bpf_subprog_hash \n"
|
||
" .p2align 2, 0x0 \n"
|
||
"cfi_bpf_subprog_hash: \n"
|
||
" .word __kcfi_typeid___bpf_callback_fn \n"
|
||
" .size cfi_bpf_subprog_hash, 4 \n"
|
||
" .popsection \n"
|
||
);
|
||
|
||
u32 cfi_get_func_hash(void *func)
|
||
{
|
||
u32 hash;
|
||
|
||
if (get_kernel_nofault(hash, func - cfi_get_offset()))
|
||
return 0;
|
||
|
||
return hash;
|
||
}
|
||
#endif
|