| // 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); | |
| } |