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