Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ARM: smp: add support for per-task stack canaries

On ARM, we currently only change the value of the stack canary when
switching tasks if the kernel was built for UP. On SMP kernels, this
is impossible since the stack canary value is obtained via a global
symbol reference, which means
a) all running tasks on all CPUs must use the same value
b) we can only modify the value when no kernel stack frames are live
on any CPU, which is effectively never.

So instead, use a GCC plugin to add a RTL pass that replaces each
reference to the address of the __stack_chk_guard symbol with an
expression that produces the address of the 'stack_canary' field
that is added to struct thread_info. This way, each task will use
its own randomized value.

Cc: Russell King <linux@armlinux.org.uk>
Cc: Kees Cook <keescook@chromium.org>
Cc: Emese Revfy <re.emese@gmail.com>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Laura Abbott <labbott@redhat.com>
Cc: kernel-hardening@lists.openwall.com
Acked-by: Nicolas Pitre <nico@linaro.org>
Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Signed-off-by: Kees Cook <keescook@chromium.org>

authored by

Ard Biesheuvel and committed by
Kees Cook
189af465 ccda4af0

+163 -3
+15
arch/arm/Kconfig
··· 1810 1810 help 1811 1811 Say Y if you want to run Linux in a Virtual Machine on Xen on ARM. 1812 1812 1813 + config STACKPROTECTOR_PER_TASK 1814 + bool "Use a unique stack canary value for each task" 1815 + depends on GCC_PLUGINS && STACKPROTECTOR && SMP && !XIP_DEFLATED_DATA 1816 + select GCC_PLUGIN_ARM_SSP_PER_TASK 1817 + default y 1818 + help 1819 + Due to the fact that GCC uses an ordinary symbol reference from 1820 + which to load the value of the stack canary, this value can only 1821 + change at reboot time on SMP systems, and all tasks running in the 1822 + kernel's address space are forced to use the same canary value for 1823 + the entire duration that the system is up. 1824 + 1825 + Enable this option to switch to a different method that uses a 1826 + different canary value for each task. 1827 + 1813 1828 endmenu 1814 1829 1815 1830 menu "Boot options"
+12
arch/arm/Makefile
··· 303 303 KBUILD_IMAGE := $(boot)/zImage 304 304 endif 305 305 306 + ifeq ($(CONFIG_STACKPROTECTOR_PER_TASK),y) 307 + prepare: stack_protector_prepare 308 + stack_protector_prepare: prepare0 309 + $(eval KBUILD_CFLAGS += \ 310 + -fplugin-arg-arm_ssp_per_task_plugin-tso=$(shell \ 311 + awk '{if ($$2 == "THREAD_SZ_ORDER") print $$3;}'\ 312 + include/generated/asm-offsets.h) \ 313 + -fplugin-arg-arm_ssp_per_task_plugin-offset=$(shell \ 314 + awk '{if ($$2 == "TI_STACK_CANARY") print $$3;}'\ 315 + include/generated/asm-offsets.h)) 316 + endif 317 + 306 318 all: $(notdir $(KBUILD_IMAGE)) 307 319 308 320
+1
arch/arm/boot/compressed/Makefile
··· 101 101 $(libfdt) $(libfdt_hdrs) hyp-stub.S 102 102 103 103 KBUILD_CFLAGS += -DDISABLE_BRANCH_PROFILING 104 + KBUILD_CFLAGS += $(DISABLE_ARM_SSP_PER_TASK_PLUGIN) 104 105 105 106 ifeq ($(CONFIG_FUNCTION_TRACER),y) 106 107 ORIG_CFLAGS := $(KBUILD_CFLAGS)
+10 -2
arch/arm/include/asm/stackprotector.h
··· 6 6 * the stack frame and verifying that it hasn't been overwritten when 7 7 * returning from the function. The pattern is called stack canary 8 8 * and gcc expects it to be defined by a global variable called 9 - * "__stack_chk_guard" on ARM. This unfortunately means that on SMP 10 - * we cannot have a different canary value per task. 9 + * "__stack_chk_guard" on ARM. This prevents SMP systems from using a 10 + * different value for each task unless we enable a GCC plugin that 11 + * replaces these symbol references with references to each task's own 12 + * value. 11 13 */ 12 14 13 15 #ifndef _ASM_STACKPROTECTOR_H ··· 17 15 18 16 #include <linux/random.h> 19 17 #include <linux/version.h> 18 + 19 + #include <asm/thread_info.h> 20 20 21 21 extern unsigned long __stack_chk_guard; 22 22 ··· 37 33 canary ^= LINUX_VERSION_CODE; 38 34 39 35 current->stack_canary = canary; 36 + #ifndef CONFIG_STACKPROTECTOR_PER_TASK 40 37 __stack_chk_guard = current->stack_canary; 38 + #else 39 + current_thread_info()->stack_canary = current->stack_canary; 40 + #endif 41 41 } 42 42 43 43 #endif /* _ASM_STACKPROTECTOR_H */
+3
arch/arm/include/asm/thread_info.h
··· 53 53 struct task_struct *task; /* main task structure */ 54 54 __u32 cpu; /* cpu */ 55 55 __u32 cpu_domain; /* cpu domain */ 56 + #ifdef CONFIG_STACKPROTECTOR_PER_TASK 57 + unsigned long stack_canary; 58 + #endif 56 59 struct cpu_context_save cpu_context; /* cpu context */ 57 60 __u32 syscall; /* syscall number */ 58 61 __u8 used_cp[16]; /* thread used copro */
+4
arch/arm/kernel/asm-offsets.c
··· 79 79 #ifdef CONFIG_CRUNCH 80 80 DEFINE(TI_CRUNCH_STATE, offsetof(struct thread_info, crunchstate)); 81 81 #endif 82 + #ifdef CONFIG_STACKPROTECTOR_PER_TASK 83 + DEFINE(TI_STACK_CANARY, offsetof(struct thread_info, stack_canary)); 84 + #endif 85 + DEFINE(THREAD_SZ_ORDER, THREAD_SIZE_ORDER); 82 86 BLANK(); 83 87 DEFINE(S_R0, offsetof(struct pt_regs, ARM_r0)); 84 88 DEFINE(S_R1, offsetof(struct pt_regs, ARM_r1));
+5 -1
arch/arm/kernel/process.c
··· 39 39 #include <asm/tls.h> 40 40 #include <asm/vdso.h> 41 41 42 - #ifdef CONFIG_STACKPROTECTOR 42 + #if defined(CONFIG_STACKPROTECTOR) && !defined(CONFIG_STACKPROTECTOR_PER_TASK) 43 43 #include <linux/stackprotector.h> 44 44 unsigned long __stack_chk_guard __read_mostly; 45 45 EXPORT_SYMBOL(__stack_chk_guard); ··· 266 266 thread->tp_value[1] = get_tpuser(); 267 267 268 268 thread_notify(THREAD_NOTIFY_COPY, thread); 269 + 270 + #ifdef CONFIG_STACKPROTECTOR_PER_TASK 271 + thread->stack_canary = p->stack_canary; 272 + #endif 269 273 270 274 return 0; 271 275 }
+6
scripts/Makefile.gcc-plugins
··· 36 36 endif 37 37 export DISABLE_STACKLEAK_PLUGIN 38 38 39 + gcc-plugin-$(CONFIG_GCC_PLUGIN_ARM_SSP_PER_TASK) += arm_ssp_per_task_plugin.so 40 + ifdef CONFIG_GCC_PLUGIN_ARM_SSP_PER_TASK 41 + DISABLE_ARM_SSP_PER_TASK_PLUGIN += -fplugin-arg-arm_ssp_per_task_plugin-disable 42 + endif 43 + export DISABLE_ARM_SSP_PER_TASK_PLUGIN 44 + 39 45 # All the plugin CFLAGS are collected here in case a build target needs to 40 46 # filter them out of the KBUILD_CFLAGS. 41 47 GCC_PLUGINS_CFLAGS := $(strip $(addprefix -fplugin=$(objtree)/scripts/gcc-plugins/, $(gcc-plugin-y)) $(gcc-plugin-cflags-y))
+4
scripts/gcc-plugins/Kconfig
··· 190 190 runtime to control kernel stack erasing for kernels built with 191 191 CONFIG_GCC_PLUGIN_STACKLEAK. 192 192 193 + config GCC_PLUGIN_ARM_SSP_PER_TASK 194 + bool 195 + depends on GCC_PLUGINS && ARM 196 + 193 197 endif
+103
scripts/gcc-plugins/arm_ssp_per_task_plugin.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + 3 + #include "gcc-common.h" 4 + 5 + __visible int plugin_is_GPL_compatible; 6 + 7 + static unsigned int sp_mask, canary_offset; 8 + 9 + static unsigned int arm_pertask_ssp_rtl_execute(void) 10 + { 11 + rtx_insn *insn; 12 + 13 + for (insn = get_insns(); insn; insn = NEXT_INSN(insn)) { 14 + const char *sym; 15 + rtx body; 16 + rtx masked_sp; 17 + 18 + /* 19 + * Find a SET insn involving a SYMBOL_REF to __stack_chk_guard 20 + */ 21 + if (!INSN_P(insn)) 22 + continue; 23 + body = PATTERN(insn); 24 + if (GET_CODE(body) != SET || 25 + GET_CODE(SET_SRC(body)) != SYMBOL_REF) 26 + continue; 27 + sym = XSTR(SET_SRC(body), 0); 28 + if (strcmp(sym, "__stack_chk_guard")) 29 + continue; 30 + 31 + /* 32 + * Replace the source of the SET insn with an expression that 33 + * produces the address of the copy of the stack canary value 34 + * stored in struct thread_info 35 + */ 36 + masked_sp = gen_reg_rtx(Pmode); 37 + 38 + emit_insn_before(gen_rtx_SET(masked_sp, 39 + gen_rtx_AND(Pmode, 40 + stack_pointer_rtx, 41 + GEN_INT(sp_mask))), 42 + insn); 43 + 44 + SET_SRC(body) = gen_rtx_PLUS(Pmode, masked_sp, 45 + GEN_INT(canary_offset)); 46 + } 47 + return 0; 48 + } 49 + 50 + #define PASS_NAME arm_pertask_ssp_rtl 51 + 52 + #define NO_GATE 53 + #include "gcc-generate-rtl-pass.h" 54 + 55 + __visible int plugin_init(struct plugin_name_args *plugin_info, 56 + struct plugin_gcc_version *version) 57 + { 58 + const char * const plugin_name = plugin_info->base_name; 59 + const int argc = plugin_info->argc; 60 + const struct plugin_argument *argv = plugin_info->argv; 61 + int tso = 0; 62 + int i; 63 + 64 + if (!plugin_default_version_check(version, &gcc_version)) { 65 + error(G_("incompatible gcc/plugin versions")); 66 + return 1; 67 + } 68 + 69 + for (i = 0; i < argc; ++i) { 70 + if (!strcmp(argv[i].key, "disable")) 71 + return 0; 72 + 73 + /* all remaining options require a value */ 74 + if (!argv[i].value) { 75 + error(G_("no value supplied for option '-fplugin-arg-%s-%s'"), 76 + plugin_name, argv[i].key); 77 + return 1; 78 + } 79 + 80 + if (!strcmp(argv[i].key, "tso")) { 81 + tso = atoi(argv[i].value); 82 + continue; 83 + } 84 + 85 + if (!strcmp(argv[i].key, "offset")) { 86 + canary_offset = atoi(argv[i].value); 87 + continue; 88 + } 89 + error(G_("unknown option '-fplugin-arg-%s-%s'"), 90 + plugin_name, argv[i].key); 91 + return 1; 92 + } 93 + 94 + /* create the mask that produces the base of the stack */ 95 + sp_mask = ~((1U << (12 + tso)) - 1); 96 + 97 + PASS_INFO(arm_pertask_ssp_rtl, "expand", 1, PASS_POS_INSERT_AFTER); 98 + 99 + register_callback(plugin_info->base_name, PLUGIN_PASS_MANAGER_SETUP, 100 + NULL, &arm_pertask_ssp_rtl_pass_info); 101 + 102 + return 0; 103 + }