1cf68fffbSSami Tolvanen // SPDX-License-Identifier: GPL-2.0
2cf68fffbSSami Tolvanen /*
389245600SSami Tolvanen * Clang Control Flow Integrity (CFI) error handling.
4cf68fffbSSami Tolvanen *
589245600SSami Tolvanen * Copyright (C) 2022 Google LLC
6cf68fffbSSami Tolvanen */
7cf68fffbSSami Tolvanen
889245600SSami Tolvanen #include <linux/cfi.h>
9cf68fffbSSami Tolvanen
1089245600SSami Tolvanen bool cfi_warn __ro_after_init = IS_ENABLED(CONFIG_CFI_PERMISSIVE);
1189245600SSami Tolvanen
report_cfi_failure(struct pt_regs * regs,unsigned long addr,unsigned long * target,u32 type)12cf68fffbSSami Tolvanen enum bug_trap_type report_cfi_failure(struct pt_regs *regs, unsigned long addr,
1389245600SSami Tolvanen unsigned long *target, u32 type)
1489245600SSami Tolvanen {
1589245600SSami Tolvanen if (target)
16cf68fffbSSami Tolvanen pr_err("CFI failure at %pS (target: %pS; expected type: 0x%08x)\n",
1789245600SSami Tolvanen (void *)addr, (void *)*target, type);
1889245600SSami Tolvanen else
1989245600SSami Tolvanen pr_err("CFI failure at %pS (no target information)\n",
2089245600SSami Tolvanen (void *)addr);
2189245600SSami Tolvanen
2289245600SSami Tolvanen if (cfi_warn) {
2389245600SSami Tolvanen __warn(NULL, 0, (void *)addr, 0, regs, NULL);
2489245600SSami Tolvanen return BUG_TRAP_TYPE_WARN;
2589245600SSami Tolvanen }
2689245600SSami Tolvanen
2789245600SSami Tolvanen return BUG_TRAP_TYPE_BUG;
2889245600SSami Tolvanen }
2989245600SSami Tolvanen
3089245600SSami Tolvanen #ifdef CONFIG_ARCH_USES_CFI_TRAPS
trap_address(s32 * p)3189245600SSami Tolvanen static inline unsigned long trap_address(s32 *p)
3289245600SSami Tolvanen {
3389245600SSami Tolvanen return (unsigned long)((long)p + (long)*p);
3489245600SSami Tolvanen }
3589245600SSami Tolvanen
is_trap(unsigned long addr,s32 * start,s32 * end)3689245600SSami Tolvanen static bool is_trap(unsigned long addr, s32 *start, s32 *end)
3789245600SSami Tolvanen {
3889245600SSami Tolvanen s32 *p;
3989245600SSami Tolvanen
4089245600SSami Tolvanen for (p = start; p < end; ++p) {
4189245600SSami Tolvanen if (trap_address(p) == addr)
4289245600SSami Tolvanen return true;
4389245600SSami Tolvanen }
44cf68fffbSSami Tolvanen
45cf68fffbSSami Tolvanen return false;
46cf68fffbSSami Tolvanen }
4789245600SSami Tolvanen
4889245600SSami Tolvanen #ifdef CONFIG_MODULES
4989245600SSami Tolvanen /* Populates `kcfi_trap(_end)?` fields in `struct module`. */
module_cfi_finalize(const Elf_Ehdr * hdr,const Elf_Shdr * sechdrs,struct module * mod)50cf68fffbSSami Tolvanen void module_cfi_finalize(const Elf_Ehdr *hdr, const Elf_Shdr *sechdrs,
5189245600SSami Tolvanen struct module *mod)
5289245600SSami Tolvanen {
5389245600SSami Tolvanen char *secstrings;
5489245600SSami Tolvanen unsigned int i;
5589245600SSami Tolvanen
5689245600SSami Tolvanen mod->kcfi_traps = NULL;
5789245600SSami Tolvanen mod->kcfi_traps_end = NULL;
5889245600SSami Tolvanen
5989245600SSami Tolvanen secstrings = (char *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;
6089245600SSami Tolvanen
6189245600SSami Tolvanen for (i = 1; i < hdr->e_shnum; i++) {
6289245600SSami Tolvanen if (strcmp(secstrings + sechdrs[i].sh_name, "__kcfi_traps"))
6389245600SSami Tolvanen continue;
6489245600SSami Tolvanen
6589245600SSami Tolvanen mod->kcfi_traps = (s32 *)sechdrs[i].sh_addr;
6689245600SSami Tolvanen mod->kcfi_traps_end = (s32 *)(sechdrs[i].sh_addr + sechdrs[i].sh_size);
6789245600SSami Tolvanen break;
6889245600SSami Tolvanen }
6989245600SSami Tolvanen }
7089245600SSami Tolvanen
is_module_cfi_trap(unsigned long addr)71cf68fffbSSami Tolvanen static bool is_module_cfi_trap(unsigned long addr)
7289245600SSami Tolvanen {
73cf68fffbSSami Tolvanen struct module *mod;
74*e151955bSSebastian Andrzej Siewior bool found = false;
7589245600SSami Tolvanen
76cf68fffbSSami Tolvanen guard(rcu)();
7789245600SSami Tolvanen mod = __module_address(addr);
7889245600SSami Tolvanen if (mod)
7989245600SSami Tolvanen found = is_trap(addr, mod->kcfi_traps, mod->kcfi_traps_end);
80cf68fffbSSami Tolvanen
8189245600SSami Tolvanen return found;
8289245600SSami Tolvanen }
83cf68fffbSSami Tolvanen #else /* CONFIG_MODULES */
is_module_cfi_trap(unsigned long addr)8489245600SSami Tolvanen static inline bool is_module_cfi_trap(unsigned long addr)
8557cd6d15SSami Tolvanen {
86cf68fffbSSami Tolvanen return false;
87cf68fffbSSami Tolvanen }
8889245600SSami Tolvanen #endif /* CONFIG_MODULES */
8989245600SSami Tolvanen
9089245600SSami Tolvanen extern s32 __start___kcfi_traps[];
9189245600SSami Tolvanen extern s32 __stop___kcfi_traps[];
92cf68fffbSSami Tolvanen
is_cfi_trap(unsigned long addr)9389245600SSami Tolvanen bool is_cfi_trap(unsigned long addr)
9489245600SSami Tolvanen {
9589245600SSami Tolvanen if (is_trap(addr, __start___kcfi_traps, __stop___kcfi_traps))
9689245600SSami Tolvanen return true;
97cf68fffbSSami Tolvanen
9889245600SSami Tolvanen return is_module_cfi_trap(addr);
99 }
100 #endif /* CONFIG_ARCH_USES_CFI_TRAPS */
101