1cf68fffbSSami Tolvanen // SPDX-License-Identifier: GPL-2.0
2cf68fffbSSami Tolvanen /*
3*89245600SSami Tolvanen * Clang Control Flow Integrity (CFI) error handling.
4cf68fffbSSami Tolvanen *
5*89245600SSami Tolvanen * Copyright (C) 2022 Google LLC
6cf68fffbSSami Tolvanen */
7cf68fffbSSami Tolvanen
8*89245600SSami Tolvanen #include <linux/cfi.h>
9cf68fffbSSami Tolvanen
report_cfi_failure(struct pt_regs * regs,unsigned long addr,unsigned long * target,u32 type)10*89245600SSami Tolvanen enum bug_trap_type report_cfi_failure(struct pt_regs *regs, unsigned long addr,
11*89245600SSami Tolvanen unsigned long *target, u32 type)
12cf68fffbSSami Tolvanen {
13*89245600SSami Tolvanen if (target)
14*89245600SSami Tolvanen pr_err("CFI failure at %pS (target: %pS; expected type: 0x%08x)\n",
15*89245600SSami Tolvanen (void *)addr, (void *)*target, type);
16cf68fffbSSami Tolvanen else
17*89245600SSami Tolvanen pr_err("CFI failure at %pS (no target information)\n",
18*89245600SSami Tolvanen (void *)addr);
19*89245600SSami Tolvanen
20*89245600SSami Tolvanen if (IS_ENABLED(CONFIG_CFI_PERMISSIVE)) {
21*89245600SSami Tolvanen __warn(NULL, 0, (void *)addr, 0, regs, NULL);
22*89245600SSami Tolvanen return BUG_TRAP_TYPE_WARN;
23*89245600SSami Tolvanen }
24*89245600SSami Tolvanen
25*89245600SSami Tolvanen return BUG_TRAP_TYPE_BUG;
26*89245600SSami Tolvanen }
27*89245600SSami Tolvanen
28*89245600SSami Tolvanen #ifdef CONFIG_ARCH_USES_CFI_TRAPS
trap_address(s32 * p)29*89245600SSami Tolvanen static inline unsigned long trap_address(s32 *p)
30*89245600SSami Tolvanen {
31*89245600SSami Tolvanen return (unsigned long)((long)p + (long)*p);
32*89245600SSami Tolvanen }
33*89245600SSami Tolvanen
is_trap(unsigned long addr,s32 * start,s32 * end)34*89245600SSami Tolvanen static bool is_trap(unsigned long addr, s32 *start, s32 *end)
35*89245600SSami Tolvanen {
36*89245600SSami Tolvanen s32 *p;
37*89245600SSami Tolvanen
38*89245600SSami Tolvanen for (p = start; p < end; ++p) {
39*89245600SSami Tolvanen if (trap_address(p) == addr)
40*89245600SSami Tolvanen return true;
41*89245600SSami Tolvanen }
42*89245600SSami Tolvanen
43*89245600SSami Tolvanen return false;
44cf68fffbSSami Tolvanen }
45cf68fffbSSami Tolvanen
46cf68fffbSSami Tolvanen #ifdef CONFIG_MODULES
47*89245600SSami Tolvanen /* Populates `kcfi_trap(_end)?` fields in `struct module`. */
module_cfi_finalize(const Elf_Ehdr * hdr,const Elf_Shdr * sechdrs,struct module * mod)48*89245600SSami Tolvanen void module_cfi_finalize(const Elf_Ehdr *hdr, const Elf_Shdr *sechdrs,
49*89245600SSami Tolvanen struct module *mod)
50cf68fffbSSami Tolvanen {
51*89245600SSami Tolvanen char *secstrings;
52*89245600SSami Tolvanen unsigned int i;
53*89245600SSami Tolvanen
54*89245600SSami Tolvanen mod->kcfi_traps = NULL;
55*89245600SSami Tolvanen mod->kcfi_traps_end = NULL;
56*89245600SSami Tolvanen
57*89245600SSami Tolvanen secstrings = (char *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;
58*89245600SSami Tolvanen
59*89245600SSami Tolvanen for (i = 1; i < hdr->e_shnum; i++) {
60*89245600SSami Tolvanen if (strcmp(secstrings + sechdrs[i].sh_name, "__kcfi_traps"))
61*89245600SSami Tolvanen continue;
62*89245600SSami Tolvanen
63*89245600SSami Tolvanen mod->kcfi_traps = (s32 *)sechdrs[i].sh_addr;
64*89245600SSami Tolvanen mod->kcfi_traps_end = (s32 *)(sechdrs[i].sh_addr + sechdrs[i].sh_size);
65*89245600SSami Tolvanen break;
66*89245600SSami Tolvanen }
67*89245600SSami Tolvanen }
68*89245600SSami Tolvanen
is_module_cfi_trap(unsigned long addr)69*89245600SSami Tolvanen static bool is_module_cfi_trap(unsigned long addr)
70*89245600SSami Tolvanen {
71cf68fffbSSami Tolvanen struct module *mod;
72*89245600SSami Tolvanen bool found = false;
73cf68fffbSSami Tolvanen
7414c4c8e4SElliot Berman rcu_read_lock_sched_notrace();
75*89245600SSami Tolvanen
76*89245600SSami Tolvanen mod = __module_address(addr);
77cf68fffbSSami Tolvanen if (mod)
78*89245600SSami Tolvanen found = is_trap(addr, mod->kcfi_traps, mod->kcfi_traps_end);
79*89245600SSami Tolvanen
8014c4c8e4SElliot Berman rcu_read_unlock_sched_notrace();
81cf68fffbSSami Tolvanen
82*89245600SSami Tolvanen return found;
83cf68fffbSSami Tolvanen }
84*89245600SSami Tolvanen #else /* CONFIG_MODULES */
is_module_cfi_trap(unsigned long addr)85*89245600SSami Tolvanen static inline bool is_module_cfi_trap(unsigned long addr)
86cf68fffbSSami Tolvanen {
87*89245600SSami Tolvanen return false;
8857cd6d15SSami Tolvanen }
89cf68fffbSSami Tolvanen #endif /* CONFIG_MODULES */
90cf68fffbSSami Tolvanen
91*89245600SSami Tolvanen extern s32 __start___kcfi_traps[];
92*89245600SSami Tolvanen extern s32 __stop___kcfi_traps[];
93*89245600SSami Tolvanen
is_cfi_trap(unsigned long addr)94*89245600SSami Tolvanen bool is_cfi_trap(unsigned long addr)
95cf68fffbSSami Tolvanen {
96*89245600SSami Tolvanen if (is_trap(addr, __start___kcfi_traps, __stop___kcfi_traps))
97*89245600SSami Tolvanen return true;
98*89245600SSami Tolvanen
99*89245600SSami Tolvanen return is_module_cfi_trap(addr);
100cf68fffbSSami Tolvanen }
101*89245600SSami Tolvanen #endif /* CONFIG_ARCH_USES_CFI_TRAPS */
102