mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-04-13 09:59:31 +00:00

The normal fixup in handle_bug() is simply continuing at the next instruction. However upcoming patches make this the wrong thing, so allow handlers (specifically handle_cfi_failure()) to over-ride regs->ip. The callchain is such that the fixup needs to be done before it is determined if the exception is fatal, as such, revert any changes in that case. Additionally, have handle_cfi_failure() remember the regs->ip value it starts with for reporting. Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Signed-off-by: Ingo Molnar <mingo@kernel.org> Reviewed-by: Kees Cook <kees@kernel.org> Link: https://lore.kernel.org/r/20250224124200.275223080@infradead.org
100 lines
2.3 KiB
C
100 lines
2.3 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
||
/*
|
||
* Clang Control Flow Integrity (CFI) support.
|
||
*
|
||
* Copyright (C) 2022 Google LLC
|
||
*/
|
||
#include <linux/string.h>
|
||
#include <linux/cfi.h>
|
||
#include <asm/insn.h>
|
||
#include <asm/insn-eval.h>
|
||
|
||
/*
|
||
* Returns the target address and the expected type when regs->ip points
|
||
* to a compiler-generated CFI trap.
|
||
*/
|
||
static bool decode_cfi_insn(struct pt_regs *regs, unsigned long *target,
|
||
u32 *type)
|
||
{
|
||
char buffer[MAX_INSN_SIZE];
|
||
struct insn insn;
|
||
int offset = 0;
|
||
|
||
*target = *type = 0;
|
||
|
||
/*
|
||
* The compiler generates the following instruction sequence
|
||
* for indirect call checks:
|
||
*
|
||
* movl -<id>, %r10d ; 6 bytes
|
||
* addl -4(%reg), %r10d ; 4 bytes
|
||
* je .Ltmp1 ; 2 bytes
|
||
* ud2 ; <- regs->ip
|
||
* .Ltmp1:
|
||
*
|
||
* We can decode the expected type and the target address from the
|
||
* movl/addl instructions.
|
||
*/
|
||
if (copy_from_kernel_nofault(buffer, (void *)regs->ip - 12, MAX_INSN_SIZE))
|
||
return false;
|
||
if (insn_decode_kernel(&insn, &buffer[offset]))
|
||
return false;
|
||
if (insn.opcode.value != 0xBA)
|
||
return false;
|
||
|
||
*type = -(u32)insn.immediate.value;
|
||
|
||
if (copy_from_kernel_nofault(buffer, (void *)regs->ip - 6, MAX_INSN_SIZE))
|
||
return false;
|
||
if (insn_decode_kernel(&insn, &buffer[offset]))
|
||
return false;
|
||
if (insn.opcode.value != 0x3)
|
||
return false;
|
||
|
||
/* Read the target address from the register. */
|
||
offset = insn_get_modrm_rm_off(&insn, regs);
|
||
if (offset < 0)
|
||
return false;
|
||
|
||
*target = *(unsigned long *)((void *)regs + offset);
|
||
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
* Checks if a ud2 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, addr = regs->ip;
|
||
u32 type;
|
||
|
||
switch (cfi_mode) {
|
||
case CFI_KCFI:
|
||
if (!is_cfi_trap(addr))
|
||
return BUG_TRAP_TYPE_NONE;
|
||
|
||
if (!decode_cfi_insn(regs, &target, &type))
|
||
return report_cfi_failure_noaddr(regs, addr);
|
||
|
||
break;
|
||
|
||
case CFI_FINEIBT:
|
||
if (!decode_fineibt_insn(regs, &target, &type))
|
||
return BUG_TRAP_TYPE_NONE;
|
||
|
||
break;
|
||
|
||
default:
|
||
return BUG_TRAP_TYPE_NONE;
|
||
}
|
||
|
||
return report_cfi_failure(regs, addr, &target, type);
|
||
}
|
||
|
||
/*
|
||
* Ensure that __kcfi_typeid_ symbols are emitted for functions that may
|
||
* not be indirectly called with all configurations.
|
||
*/
|
||
__ADDRESSABLE(__memcpy)
|