Skip to content

Commit cf68fff

Browse files
samitolvanenkees
authored andcommitted
add support for Clang CFI
This change adds support for Clang’s forward-edge Control Flow Integrity (CFI) checking. With CONFIG_CFI_CLANG, the compiler injects a runtime check before each indirect function call to ensure the target is a valid function with the correct static type. This restricts possible call targets and makes it more difficult for an attacker to exploit bugs that allow the modification of stored function pointers. For more details, see: https://clang.llvm.org/docs/ControlFlowIntegrity.html Clang requires CONFIG_LTO_CLANG to be enabled with CFI to gain visibility to possible call targets. Kernel modules are supported with Clang’s cross-DSO CFI mode, which allows checking between independently compiled components. With CFI enabled, the compiler injects a __cfi_check() function into the kernel and each module for validating local call targets. For cross-module calls that cannot be validated locally, the compiler calls the global __cfi_slowpath_diag() function, which determines the target module and calls the correct __cfi_check() function. This patch includes a slowpath implementation that uses __module_address() to resolve call targets, and with CONFIG_CFI_CLANG_SHADOW enabled, a shadow map that speeds up module look-ups by ~3x. Clang implements indirect call checking using jump tables and offers two methods of generating them. With canonical jump tables, the compiler renames each address-taken function to <function>.cfi and points the original symbol to a jump table entry, which passes __cfi_check() validation. This isn’t compatible with stand-alone assembly code, which the compiler doesn’t instrument, and would result in indirect calls to assembly code to fail. Therefore, we default to using non-canonical jump tables instead, where the compiler generates a local jump table entry <function>.cfi_jt for each address-taken function, and replaces all references to the function with the address of the jump table entry. Note that because non-canonical jump table addresses are local to each component, they break cross-module function address equality. Specifically, the address of a global function will be different in each module, as it's replaced with the address of a local jump table entry. If this address is passed to a different module, it won’t match the address of the same function taken there. This may break code that relies on comparing addresses passed from other components. CFI checking can be disabled in a function with the __nocfi attribute. Additionally, CFI can be disabled for an entire compilation unit by filtering out CC_FLAGS_CFI. By default, CFI failures result in a kernel panic to stop a potential exploit. CONFIG_CFI_PERMISSIVE enables a permissive mode, where the kernel prints out a rate-limited warning instead, and allows execution to continue. This option is helpful for locating type mismatches, but should only be enabled during development. Signed-off-by: Sami Tolvanen <[email protected]> Reviewed-by: Kees Cook <[email protected]> Tested-by: Nathan Chancellor <[email protected]> Signed-off-by: Kees Cook <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent e49d033 commit cf68fff

File tree

14 files changed

+534
-6
lines changed

14 files changed

+534
-6
lines changed

Makefile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,23 @@ KBUILD_AFLAGS += -fno-lto
920920
export CC_FLAGS_LTO
921921
endif
922922

923+
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)
936+
KBUILD_CFLAGS += $(CC_FLAGS_CFI)
937+
export CC_FLAGS_CFI
938+
endif
939+
923940
ifdef CONFIG_DEBUG_FORCE_FUNCTION_ALIGN_32B
924941
KBUILD_CFLAGS += -falign-functions=32
925942
endif

arch/Kconfig

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,51 @@ config LTO_CLANG_THIN
692692
If unsure, say Y.
693693
endchoice
694694

695+
config ARCH_SUPPORTS_CFI_CLANG
696+
bool
697+
help
698+
An architecture should select this option if it can support Clang's
699+
Control-Flow Integrity (CFI) checking.
700+
701+
config CFI_CLANG
702+
bool "Use Clang's Control Flow Integrity (CFI)"
703+
depends on LTO_CLANG && ARCH_SUPPORTS_CFI_CLANG
704+
# Clang >= 12:
705+
# - https://bugs.llvm.org/show_bug.cgi?id=46258
706+
# - https://bugs.llvm.org/show_bug.cgi?id=47479
707+
depends on CLANG_VERSION >= 120000
708+
select KALLSYMS
709+
help
710+
This option enables Clang’s forward-edge Control Flow Integrity
711+
(CFI) checking, where the compiler injects a runtime check to each
712+
indirect function call to ensure the target is a valid function with
713+
the correct static type. This restricts possible call targets and
714+
makes it more difficult for an attacker to exploit bugs that allow
715+
the modification of stored function pointers. More information can be
716+
found from Clang's documentation:
717+
718+
https://clang.llvm.org/docs/ControlFlowIntegrity.html
719+
720+
config CFI_CLANG_SHADOW
721+
bool "Use CFI shadow to speed up cross-module checks"
722+
default y
723+
depends on CFI_CLANG && MODULES
724+
help
725+
If you select this option, the kernel builds a fast look-up table of
726+
CFI check functions in loaded modules to reduce performance overhead.
727+
728+
If unsure, say Y.
729+
730+
config CFI_PERMISSIVE
731+
bool "Use CFI in permissive mode"
732+
depends on CFI_CLANG
733+
help
734+
When selected, Control Flow Integrity (CFI) violations result in a
735+
warning instead of a kernel panic. This option should only be used
736+
for finding indirect call type mismatches during development.
737+
738+
If unsure, say N.
739+
695740
config HAVE_ARCH_WITHIN_STACK_FRAMES
696741
bool
697742
help

include/asm-generic/bug.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,22 @@ void __warn(const char *file, int line, void *caller, unsigned taint,
241241
# define WARN_ON_SMP(x) ({0;})
242242
#endif
243243

244+
/*
245+
* WARN_ON_FUNCTION_MISMATCH() warns if a value doesn't match a
246+
* function address, and can be useful for catching issues with
247+
* callback functions, for example.
248+
*
249+
* With CONFIG_CFI_CLANG, the warning is disabled because the
250+
* compiler replaces function addresses taken in C code with
251+
* local jump table addresses, which breaks cross-module function
252+
* address equality.
253+
*/
254+
#if defined(CONFIG_CFI_CLANG) && defined(CONFIG_MODULES)
255+
# define WARN_ON_FUNCTION_MISMATCH(x, fn) ({ 0; })
256+
#else
257+
# define WARN_ON_FUNCTION_MISMATCH(x, fn) WARN_ON_ONCE((x) != (fn))
258+
#endif
259+
244260
#endif /* __ASSEMBLY__ */
245261

246262
#endif

include/asm-generic/vmlinux.lds.h

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,22 @@
544544
. = ALIGN((align)); \
545545
__end_rodata = .;
546546

547+
548+
/*
549+
* .text..L.cfi.jumptable.* contain Control-Flow Integrity (CFI)
550+
* jump table entries.
551+
*/
552+
#ifdef CONFIG_CFI_CLANG
553+
#define TEXT_CFI_JT \
554+
. = ALIGN(PMD_SIZE); \
555+
__cfi_jt_start = .; \
556+
*(.text..L.cfi.jumptable .text..L.cfi.jumptable.*) \
557+
. = ALIGN(PMD_SIZE); \
558+
__cfi_jt_end = .;
559+
#else
560+
#define TEXT_CFI_JT
561+
#endif
562+
547563
/*
548564
* Non-instrumentable text section
549565
*/
@@ -570,6 +586,7 @@
570586
NOINSTR_TEXT \
571587
*(.text..refcount) \
572588
*(.ref.text) \
589+
TEXT_CFI_JT \
573590
MEM_KEEP(init.text*) \
574591
MEM_KEEP(exit.text*) \
575592

@@ -974,7 +991,8 @@
974991
* keep any .init_array.* sections.
975992
* https://bugs.llvm.org/show_bug.cgi?id=46478
976993
*/
977-
#if defined(CONFIG_GCOV_KERNEL) || defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KCSAN)
994+
#if defined(CONFIG_GCOV_KERNEL) || defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KCSAN) || \
995+
defined(CONFIG_CFI_CLANG)
978996
# ifdef CONFIG_CONSTRUCTORS
979997
# define SANITIZER_DISCARDS \
980998
*(.eh_frame)

include/linux/cfi.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/* SPDX-License-Identifier: GPL-2.0 */
2+
/*
3+
* Clang Control Flow Integrity (CFI) support.
4+
*
5+
* Copyright (C) 2021 Google LLC
6+
*/
7+
#ifndef _LINUX_CFI_H
8+
#define _LINUX_CFI_H
9+
10+
#ifdef CONFIG_CFI_CLANG
11+
typedef void (*cfi_check_fn)(uint64_t id, void *ptr, void *diag);
12+
13+
/* Compiler-generated function in each module, and the kernel */
14+
extern void __cfi_check(uint64_t id, void *ptr, void *diag);
15+
16+
/*
17+
* Force the compiler to generate a CFI jump table entry for a function
18+
* and store the jump table address to __cfi_jt_<function>.
19+
*/
20+
#define __CFI_ADDRESSABLE(fn, __attr) \
21+
const void *__cfi_jt_ ## fn __visible __attr = (void *)&fn
22+
23+
#ifdef CONFIG_CFI_CLANG_SHADOW
24+
25+
extern void cfi_module_add(struct module *mod, unsigned long base_addr);
26+
extern void cfi_module_remove(struct module *mod, unsigned long base_addr);
27+
28+
#else
29+
30+
static inline void cfi_module_add(struct module *mod, unsigned long base_addr) {}
31+
static inline void cfi_module_remove(struct module *mod, unsigned long base_addr) {}
32+
33+
#endif /* CONFIG_CFI_CLANG_SHADOW */
34+
35+
#else /* !CONFIG_CFI_CLANG */
36+
37+
#define __CFI_ADDRESSABLE(fn, __attr)
38+
39+
#endif /* CONFIG_CFI_CLANG */
40+
41+
#endif /* _LINUX_CFI_H */

include/linux/compiler-clang.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,5 @@
6161
#if __has_feature(shadow_call_stack)
6262
# define __noscs __attribute__((__no_sanitize__("shadow-call-stack")))
6363
#endif
64+
65+
#define __nocfi __attribute__((__no_sanitize__("cfi")))

include/linux/compiler_types.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,10 @@ struct ftrace_likely_data {
242242
# define __noscs
243243
#endif
244244

245+
#ifndef __nocfi
246+
# define __nocfi
247+
#endif
248+
245249
#ifndef asm_volatile_goto
246250
#define asm_volatile_goto(x...) asm goto(x)
247251
#endif

include/linux/init.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747

4848
/* These are for everybody (although not all archs will actually
4949
discard it in modules) */
50-
#define __init __section(".init.text") __cold __latent_entropy __noinitretpoline
50+
#define __init __section(".init.text") __cold __latent_entropy __noinitretpoline __nocfi
5151
#define __initdata __section(".init.data")
5252
#define __initconst __section(".init.rodata")
5353
#define __exitdata __section(".exit.data")

include/linux/module.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include <linux/tracepoint-defs.h>
2727
#include <linux/srcu.h>
2828
#include <linux/static_call_types.h>
29+
#include <linux/cfi.h>
2930

3031
#include <linux/percpu.h>
3132
#include <asm/module.h>
@@ -128,13 +129,17 @@ extern void cleanup_module(void);
128129
#define module_init(initfn) \
129130
static inline initcall_t __maybe_unused __inittest(void) \
130131
{ return initfn; } \
131-
int init_module(void) __copy(initfn) __attribute__((alias(#initfn)));
132+
int init_module(void) __copy(initfn) \
133+
__attribute__((alias(#initfn))); \
134+
__CFI_ADDRESSABLE(init_module, __initdata);
132135

133136
/* This is only required if you want to be unloadable. */
134137
#define module_exit(exitfn) \
135138
static inline exitcall_t __maybe_unused __exittest(void) \
136139
{ return exitfn; } \
137-
void cleanup_module(void) __copy(exitfn) __attribute__((alias(#exitfn)));
140+
void cleanup_module(void) __copy(exitfn) \
141+
__attribute__((alias(#exitfn))); \
142+
__CFI_ADDRESSABLE(cleanup_module, __exitdata);
138143

139144
#endif
140145

@@ -376,6 +381,10 @@ struct module {
376381
const s32 *crcs;
377382
unsigned int num_syms;
378383

384+
#ifdef CONFIG_CFI_CLANG
385+
cfi_check_fn cfi_check;
386+
#endif
387+
379388
/* Kernel parameters. */
380389
#ifdef CONFIG_SYSFS
381390
struct mutex param_lock;

init/Kconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2296,7 +2296,7 @@ endif # MODULES
22962296

22972297
config MODULES_TREE_LOOKUP
22982298
def_bool y
2299-
depends on PERF_EVENTS || TRACING
2299+
depends on PERF_EVENTS || TRACING || CFI_CLANG
23002300

23012301
config INIT_ALL_POSSIBLE
23022302
bool

kernel/Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ KCSAN_SANITIZE_kcov.o := n
4141
UBSAN_SANITIZE_kcov.o := n
4242
CFLAGS_kcov.o := $(call cc-option, -fno-conserve-stack) -fno-stack-protector
4343

44+
# Don't instrument error handlers
45+
CFLAGS_REMOVE_cfi.o := $(CC_FLAGS_CFI)
46+
4447
obj-y += sched/
4548
obj-y += locking/
4649
obj-y += power/
@@ -111,6 +114,7 @@ obj-$(CONFIG_BPF) += bpf/
111114
obj-$(CONFIG_KCSAN) += kcsan/
112115
obj-$(CONFIG_SHADOW_CALL_STACK) += scs.o
113116
obj-$(CONFIG_HAVE_STATIC_CALL_INLINE) += static_call.o
117+
obj-$(CONFIG_CFI_CLANG) += cfi.o
114118

115119
obj-$(CONFIG_PERF_EVENTS) += events/
116120

0 commit comments

Comments
 (0)