Skip to content

Commit 8924560

Browse files
samitolvanenkees
authored andcommitted
cfi: Switch to -fsanitize=kcfi
Switch from Clang's original forward-edge control-flow integrity implementation to -fsanitize=kcfi, which is better suited for the kernel, as it doesn't require LTO, doesn't use a jump table that requires altering function references, and won't break cross-module function address equality. Signed-off-by: Sami Tolvanen <[email protected]> Reviewed-by: Kees Cook <[email protected]> Tested-by: Kees Cook <[email protected]> Tested-by: Nathan Chancellor <[email protected]> Acked-by: Peter Zijlstra (Intel) <[email protected]> Tested-by: Peter Zijlstra (Intel) <[email protected]> Signed-off-by: Kees Cook <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent 92efda8 commit 8924560

File tree

9 files changed

+133
-176
lines changed

9 files changed

+133
-176
lines changed

Makefile

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -921,18 +921,7 @@ export CC_FLAGS_LTO
921921
endif
922922

923923
ifdef CONFIG_CFI_CLANG
924-
CC_FLAGS_CFI := -fsanitize=cfi \
925-
-fsanitize-cfi-cross-dso \
926-
-fno-sanitize-cfi-canonical-jump-tables \
927-
-fno-sanitize-trap=cfi \
928-
-fno-sanitize-blacklist
929-
930-
ifdef CONFIG_CFI_PERMISSIVE
931-
CC_FLAGS_CFI += -fsanitize-recover=cfi
932-
endif
933-
934-
# If LTO flags are filtered out, we must also filter out CFI.
935-
CC_FLAGS_LTO += $(CC_FLAGS_CFI)
924+
CC_FLAGS_CFI := -fsanitize=kcfi
936925
KBUILD_CFLAGS += $(CC_FLAGS_CFI)
937926
export CC_FLAGS_CFI
938927
endif

arch/Kconfig

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -738,11 +738,13 @@ config ARCH_SUPPORTS_CFI_CLANG
738738
An architecture should select this option if it can support Clang's
739739
Control-Flow Integrity (CFI) checking.
740740

741+
config ARCH_USES_CFI_TRAPS
742+
bool
743+
741744
config CFI_CLANG
742745
bool "Use Clang's Control Flow Integrity (CFI)"
743-
depends on LTO_CLANG && ARCH_SUPPORTS_CFI_CLANG
744-
depends on CLANG_VERSION >= 140000
745-
select KALLSYMS
746+
depends on ARCH_SUPPORTS_CFI_CLANG
747+
depends on $(cc-option,-fsanitize=kcfi)
746748
help
747749
This option enables Clang’s forward-edge Control Flow Integrity
748750
(CFI) checking, where the compiler injects a runtime check to each

include/asm-generic/vmlinux.lds.h

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,22 @@
421421
__end_ro_after_init = .;
422422
#endif
423423

424+
/*
425+
* .kcfi_traps contains a list KCFI trap locations.
426+
*/
427+
#ifndef KCFI_TRAPS
428+
#ifdef CONFIG_ARCH_USES_CFI_TRAPS
429+
#define KCFI_TRAPS \
430+
__kcfi_traps : AT(ADDR(__kcfi_traps) - LOAD_OFFSET) { \
431+
__start___kcfi_traps = .; \
432+
KEEP(*(.kcfi_traps)) \
433+
__stop___kcfi_traps = .; \
434+
}
435+
#else
436+
#define KCFI_TRAPS
437+
#endif
438+
#endif
439+
424440
/*
425441
* Read only Data
426442
*/
@@ -529,6 +545,8 @@
529545
__stop___modver = .; \
530546
} \
531547
\
548+
KCFI_TRAPS \
549+
\
532550
RO_EXCEPTION_TABLE \
533551
NOTES \
534552
BTF \
@@ -537,21 +555,6 @@
537555
__end_rodata = .;
538556

539557

540-
/*
541-
* .text..L.cfi.jumptable.* contain Control-Flow Integrity (CFI)
542-
* jump table entries.
543-
*/
544-
#ifdef CONFIG_CFI_CLANG
545-
#define TEXT_CFI_JT \
546-
. = ALIGN(PMD_SIZE); \
547-
__cfi_jt_start = .; \
548-
*(.text..L.cfi.jumptable .text..L.cfi.jumptable.*) \
549-
. = ALIGN(PMD_SIZE); \
550-
__cfi_jt_end = .;
551-
#else
552-
#define TEXT_CFI_JT
553-
#endif
554-
555558
/*
556559
* Non-instrumentable text section
557560
*/
@@ -579,7 +582,6 @@
579582
*(.text..refcount) \
580583
*(.ref.text) \
581584
*(.text.asan.* .text.tsan.*) \
582-
TEXT_CFI_JT \
583585
MEM_KEEP(init.text*) \
584586
MEM_KEEP(exit.text*) \
585587

@@ -1008,8 +1010,7 @@
10081010
* keep any .init_array.* sections.
10091011
* https://bugs.llvm.org/show_bug.cgi?id=46478
10101012
*/
1011-
#if defined(CONFIG_GCOV_KERNEL) || defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KCSAN) || \
1012-
defined(CONFIG_CFI_CLANG)
1013+
#if defined(CONFIG_GCOV_KERNEL) || defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KCSAN)
10131014
# ifdef CONFIG_CONSTRUCTORS
10141015
# define SANITIZER_DISCARDS \
10151016
*(.eh_frame)

include/linux/cfi.h

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,38 @@
22
/*
33
* Clang Control Flow Integrity (CFI) support.
44
*
5-
* Copyright (C) 2021 Google LLC
5+
* Copyright (C) 2022 Google LLC
66
*/
77
#ifndef _LINUX_CFI_H
88
#define _LINUX_CFI_H
99

10+
#include <linux/bug.h>
11+
#include <linux/module.h>
12+
1013
#ifdef CONFIG_CFI_CLANG
11-
typedef void (*cfi_check_fn)(uint64_t id, void *ptr, void *diag);
14+
enum bug_trap_type report_cfi_failure(struct pt_regs *regs, unsigned long addr,
15+
unsigned long *target, u32 type);
1216

13-
/* Compiler-generated function in each module, and the kernel */
14-
extern void __cfi_check(uint64_t id, void *ptr, void *diag);
17+
static inline enum bug_trap_type report_cfi_failure_noaddr(struct pt_regs *regs,
18+
unsigned long addr)
19+
{
20+
return report_cfi_failure(regs, addr, NULL, 0);
21+
}
1522

23+
#ifdef CONFIG_ARCH_USES_CFI_TRAPS
24+
bool is_cfi_trap(unsigned long addr);
25+
#endif
1626
#endif /* CONFIG_CFI_CLANG */
1727

28+
#ifdef CONFIG_MODULES
29+
#ifdef CONFIG_ARCH_USES_CFI_TRAPS
30+
void module_cfi_finalize(const Elf_Ehdr *hdr, const Elf_Shdr *sechdrs,
31+
struct module *mod);
32+
#else
33+
static inline void module_cfi_finalize(const Elf_Ehdr *hdr,
34+
const Elf_Shdr *sechdrs,
35+
struct module *mod) {}
36+
#endif /* CONFIG_ARCH_USES_CFI_TRAPS */
37+
#endif /* CONFIG_MODULES */
38+
1839
#endif /* _LINUX_CFI_H */

include/linux/compiler-clang.h

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,9 @@
6666
# define __noscs __attribute__((__no_sanitize__("shadow-call-stack")))
6767
#endif
6868

69-
#define __nocfi __attribute__((__no_sanitize__("cfi")))
70-
#define __cficanonical __attribute__((__cfi_canonical_jump_table__))
71-
72-
#if defined(CONFIG_CFI_CLANG)
73-
/*
74-
* With CONFIG_CFI_CLANG, the compiler replaces function address
75-
* references with the address of the function's CFI jump table
76-
* entry. The function_nocfi macro always returns the address of the
77-
* actual function instead.
78-
*/
79-
#define function_nocfi(x) __builtin_function_start(x)
69+
#if __has_feature(kcfi)
70+
/* Disable CFI checking inside a function. */
71+
#define __nocfi __attribute__((__no_sanitize__("kcfi")))
8072
#endif
8173

8274
/*

include/linux/module.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
#include <linux/tracepoint-defs.h>
2828
#include <linux/srcu.h>
2929
#include <linux/static_call_types.h>
30-
#include <linux/cfi.h>
3130

3231
#include <linux/percpu.h>
3332
#include <asm/module.h>
@@ -387,8 +386,9 @@ struct module {
387386
const s32 *crcs;
388387
unsigned int num_syms;
389388

390-
#ifdef CONFIG_CFI_CLANG
391-
cfi_check_fn cfi_check;
389+
#ifdef CONFIG_ARCH_USES_CFI_TRAPS
390+
s32 *kcfi_traps;
391+
s32 *kcfi_traps_end;
392392
#endif
393393

394394
/* Kernel parameters. */

kernel/cfi.c

Lines changed: 70 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,101 @@
11
// SPDX-License-Identifier: GPL-2.0
22
/*
3-
* Clang Control Flow Integrity (CFI) error and slowpath handling.
3+
* Clang Control Flow Integrity (CFI) error handling.
44
*
5-
* Copyright (C) 2021 Google LLC
5+
* Copyright (C) 2022 Google LLC
66
*/
77

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)
2712
{
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);
3016
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;
3226
}
3327

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+
}
3533

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)
3735
{
38-
cfi_check_fn fn = NULL;
39-
struct module *mod;
36+
s32 *p;
4037

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+
}
4642

47-
return fn;
43+
return false;
4844
}
4945

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)
5150
{
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;
6953

70-
fn = find_module_check_fn(ptr);
54+
mod->kcfi_traps = NULL;
55+
mod->kcfi_traps_end = NULL;
7156

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;
7662

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+
}
7867
}
7968

80-
void __cfi_slowpath_diag(uint64_t id, void *ptr, void *diag)
69+
static bool is_module_cfi_trap(unsigned long addr)
8170
{
82-
cfi_check_fn fn = find_check_fn((unsigned long)ptr);
71+
struct module *mod;
72+
bool found = false;
8373

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();
9075

91-
#else /* !CONFIG_MODULES */
76+
mod = __module_address(addr);
77+
if (mod)
78+
found = is_trap(addr, mod->kcfi_traps, mod->kcfi_traps_end);
9279

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)
9486
{
95-
handle_cfi_failure(ptr); /* No modules */
87+
return false;
9688
}
97-
EXPORT_SYMBOL(__cfi_slowpath_diag);
98-
9989
#endif /* CONFIG_MODULES */
10090

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)
10295
{
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);
104100
}
105-
EXPORT_SYMBOL(cfi_failure_handler);
101+
#endif /* CONFIG_ARCH_USES_CFI_TRAPS */

0 commit comments

Comments
 (0)