mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-08-26 20:18:15 +00:00

Instead of duplicating the same code for each architecture, move the CFI type hash variables for BPF function types and related helper functions to generic CFI code, and allow architectures to override the function definitions if needed. Signed-off-by: Sami Tolvanen <samitolvanen@google.com> Link: https://lore.kernel.org/r/20250801001004.1859976-7-samitolvanen@google.com Signed-off-by: Alexei Starovoitov <ast@kernel.org>
78 lines
1.8 KiB
C
78 lines
1.8 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);
|
||
}
|