|
1 | 1 | // SPDX-License-Identifier: GPL-2.0 |
2 | 2 | /* |
3 | | - * Clang Control Flow Integrity (CFI) error and slowpath handling. |
| 3 | + * Clang Control Flow Integrity (CFI) error handling. |
4 | 4 | * |
5 | | - * Copyright (C) 2021 Google LLC |
| 5 | + * Copyright (C) 2022 Google LLC |
6 | 6 | */ |
7 | 7 |
|
8 | | -#include <linux/hardirq.h> |
9 | | -#include <linux/kallsyms.h> |
10 | | -#include <linux/module.h> |
11 | | -#include <linux/mutex.h> |
12 | | -#include <linux/printk.h> |
13 | | -#include <linux/ratelimit.h> |
14 | | -#include <linux/rcupdate.h> |
15 | | -#include <linux/vmalloc.h> |
16 | | -#include <asm/cacheflush.h> |
17 | | -#include <asm/set_memory.h> |
18 | | - |
19 | | -/* Compiler-defined handler names */ |
20 | | -#ifdef CONFIG_CFI_PERMISSIVE |
21 | | -#define cfi_failure_handler __ubsan_handle_cfi_check_fail |
22 | | -#else |
23 | | -#define cfi_failure_handler __ubsan_handle_cfi_check_fail_abort |
24 | | -#endif |
25 | | - |
26 | | -static inline void handle_cfi_failure(void *ptr) |
| 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) |
27 | 12 | { |
28 | | - if (IS_ENABLED(CONFIG_CFI_PERMISSIVE)) |
29 | | - WARN_RATELIMIT(1, "CFI failure (target: %pS):\n", ptr); |
| 13 | + if (target) |
| 14 | + pr_err("CFI failure at %pS (target: %pS; expected type: 0x%08x)\n", |
| 15 | + (void *)addr, (void *)*target, type); |
30 | 16 | else |
31 | | - panic("CFI failure (target: %pS)\n", ptr); |
| 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, 0, (void *)addr, 0, regs, NULL); |
| 22 | + return BUG_TRAP_TYPE_WARN; |
| 23 | + } |
| 24 | + |
| 25 | + return BUG_TRAP_TYPE_BUG; |
32 | 26 | } |
33 | 27 |
|
34 | | -#ifdef CONFIG_MODULES |
| 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 | +} |
35 | 33 |
|
36 | | -static inline cfi_check_fn find_module_check_fn(unsigned long ptr) |
| 34 | +static bool is_trap(unsigned long addr, s32 *start, s32 *end) |
37 | 35 | { |
38 | | - cfi_check_fn fn = NULL; |
39 | | - struct module *mod; |
| 36 | + s32 *p; |
40 | 37 |
|
41 | | - rcu_read_lock_sched_notrace(); |
42 | | - mod = __module_address(ptr); |
43 | | - if (mod) |
44 | | - fn = mod->cfi_check; |
45 | | - rcu_read_unlock_sched_notrace(); |
| 38 | + for (p = start; p < end; ++p) { |
| 39 | + if (trap_address(p) == addr) |
| 40 | + return true; |
| 41 | + } |
46 | 42 |
|
47 | | - return fn; |
| 43 | + return false; |
48 | 44 | } |
49 | 45 |
|
50 | | -static inline cfi_check_fn find_check_fn(unsigned long ptr) |
| 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) |
51 | 50 | { |
52 | | - cfi_check_fn fn = NULL; |
53 | | - unsigned long flags; |
54 | | - bool rcu_idle; |
55 | | - |
56 | | - if (is_kernel_text(ptr)) |
57 | | - return __cfi_check; |
58 | | - |
59 | | - /* |
60 | | - * Indirect call checks can happen when RCU is not watching. Both |
61 | | - * the shadow and __module_address use RCU, so we need to wake it |
62 | | - * up if necessary. |
63 | | - */ |
64 | | - rcu_idle = !rcu_is_watching(); |
65 | | - if (rcu_idle) { |
66 | | - local_irq_save(flags); |
67 | | - ct_irq_enter(); |
68 | | - } |
| 51 | + char *secstrings; |
| 52 | + unsigned int i; |
69 | 53 |
|
70 | | - fn = find_module_check_fn(ptr); |
| 54 | + mod->kcfi_traps = NULL; |
| 55 | + mod->kcfi_traps_end = NULL; |
71 | 56 |
|
72 | | - if (rcu_idle) { |
73 | | - ct_irq_exit(); |
74 | | - local_irq_restore(flags); |
75 | | - } |
| 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; |
76 | 62 |
|
77 | | - return fn; |
| 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 | + } |
78 | 67 | } |
79 | 68 |
|
80 | | -void __cfi_slowpath_diag(uint64_t id, void *ptr, void *diag) |
| 69 | +static bool is_module_cfi_trap(unsigned long addr) |
81 | 70 | { |
82 | | - cfi_check_fn fn = find_check_fn((unsigned long)ptr); |
| 71 | + struct module *mod; |
| 72 | + bool found = false; |
83 | 73 |
|
84 | | - if (likely(fn)) |
85 | | - fn(id, ptr, diag); |
86 | | - else /* Don't allow unchecked modules */ |
87 | | - handle_cfi_failure(ptr); |
88 | | -} |
89 | | -EXPORT_SYMBOL(__cfi_slowpath_diag); |
| 74 | + rcu_read_lock_sched_notrace(); |
90 | 75 |
|
91 | | -#else /* !CONFIG_MODULES */ |
| 76 | + mod = __module_address(addr); |
| 77 | + if (mod) |
| 78 | + found = is_trap(addr, mod->kcfi_traps, mod->kcfi_traps_end); |
92 | 79 |
|
93 | | -void __cfi_slowpath_diag(uint64_t id, void *ptr, void *diag) |
| 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) |
94 | 86 | { |
95 | | - handle_cfi_failure(ptr); /* No modules */ |
| 87 | + return false; |
96 | 88 | } |
97 | | -EXPORT_SYMBOL(__cfi_slowpath_diag); |
98 | | - |
99 | 89 | #endif /* CONFIG_MODULES */ |
100 | 90 |
|
101 | | -void cfi_failure_handler(void *data, void *ptr, void *vtable) |
| 91 | +extern s32 __start___kcfi_traps[]; |
| 92 | +extern s32 __stop___kcfi_traps[]; |
| 93 | + |
| 94 | +bool is_cfi_trap(unsigned long addr) |
102 | 95 | { |
103 | | - handle_cfi_failure(ptr); |
| 96 | + if (is_trap(addr, __start___kcfi_traps, __stop___kcfi_traps)) |
| 97 | + return true; |
| 98 | + |
| 99 | + return is_module_cfi_trap(addr); |
104 | 100 | } |
105 | | -EXPORT_SYMBOL(cfi_failure_handler); |
| 101 | +#endif /* CONFIG_ARCH_USES_CFI_TRAPS */ |
0 commit comments