xref: /linux/kernel/cfi.c (revision 89245600)
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