History

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.

Ard Biesheuvel committed 7 years ago

Commit 189af461 parent ccda4af10 files · +163 −3Browse files

Unified · Split

10 files
  1. arch/arm/Kconfig +15 −0
  2. arch/arm/Makefile +12 −0
  3. arch/arm/boot/compressed/Makefile +1 −0
  4. arch/arm/include/asm/stackprotector.h +10 −2
  5. arch/arm/include/asm/thread_info.h +3 −0
  6. arch/arm/kernel/asm-offsets.c +4 −0
  7. arch/arm/kernel/process.c +5 −1
  8. scripts/Makefile.gcc-plugins +6 −0
  9. scripts/gcc-plugins/Kconfig +4 −0
  10. scripts/gcc-plugins/arm_ssp_per_task_plugin.c +103 −0
arch/arm/Kconfig+15−0
@@ -1810,6 +1810,21 @@ config XEN · 1809 unmodified lines
18101810 help
18111811 Say Y if you want to run Linux in a Virtual Machine on Xen on ARM.
18121812
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+
18131828 endmenu
18141829
18151830 menu "Boot options"
arch/arm/Makefile+12−0
@@ -303,6 +303,18 @@ else · 302 unmodified lines
303303 KBUILD_IMAGE := $(boot)/zImage
304304 endif
305305
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+
306318 all: $(notdir $(KBUILD_IMAGE))
307319
308320
arch/arm/boot/compressed/Makefile+1−0
@@ -101,6 +101,7 @@ clean-files += piggy_data lib1funcs.S ashldi3.S bswapsdi2.S \ · 100 unmodified lines
101101 $(libfdt) $(libfdt_hdrs) hyp-stub.S
102102
103103 KBUILD_CFLAGS += -DDISABLE_BRANCH_PROFILING
104+KBUILD_CFLAGS += $(DISABLE_ARM_SSP_PER_TASK_PLUGIN)
104105
105106 ifeq ($(CONFIG_FUNCTION_TRACER),y)
106107 ORIG_CFLAGS := $(KBUILD_CFLAGS)
arch/arm/include/asm/stackprotector.h+10−2
@@ -6,8 +6,10 @@ · 5 unmodified lines
66 * the stack frame and verifying that it hasn't been overwritten when
77 * returning from the function. The pattern is called stack canary
88 * 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.
1113 */
1214
1315 #ifndef _ASM_STACKPROTECTOR_H
@@ -16,6 +18,8 @@ · 2 unmodified lines
1618 #include <linux/random.h>
1719 #include <linux/version.h>
1820
21+#include <asm/thread_info.h>
22+
1923 extern unsigned long __stack_chk_guard;
2024
2125 /*
@@ -33,7 +37,11 @@ static __always_inline void boot_init_stack_canary(void) · 11 unmodified lines
3337 canary ^= LINUX_VERSION_CODE;
3438
3539 current->stack_canary = canary;
40+#ifndef CONFIG_STACKPROTECTOR_PER_TASK
3641 __stack_chk_guard = current->stack_canary;
42+#else
43+ current_thread_info()->stack_canary = current->stack_canary;
44+#endif
3745 }
3846
3947 #endif /* _ASM_STACKPROTECTOR_H */
arch/arm/include/asm/thread_info.h+3−0
@@ -53,6 +53,9 @@ struct thread_info { · 52 unmodified lines
5353 struct task_struct *task; /* main task structure */
5454 __u32 cpu; /* cpu */
5555 __u32 cpu_domain; /* cpu domain */
56+#ifdef CONFIG_STACKPROTECTOR_PER_TASK
57+ unsigned long stack_canary;
58+#endif
5659 struct cpu_context_save cpu_context; /* cpu context */
5760 __u32 syscall; /* syscall number */
5861 __u8 used_cp[16]; /* thread used copro */
arch/arm/kernel/asm-offsets.c+4−0
@@ -79,6 +79,10 @@ int main(void) · 78 unmodified lines
7979 #ifdef CONFIG_CRUNCH
8080 DEFINE(TI_CRUNCH_STATE, offsetof(struct thread_info, crunchstate));
8181 #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);
8286 BLANK();
8387 DEFINE(S_R0, offsetof(struct pt_regs, ARM_r0));
8488 DEFINE(S_R1, offsetof(struct pt_regs, ARM_r1));
arch/arm/kernel/process.c+5−1
@@ -39,7 +39,7 @@ · 38 unmodified lines
3939 #include <asm/tls.h>
4040 #include <asm/vdso.h>
4141
42−#ifdef CONFIG_STACKPROTECTOR
42+#if defined(CONFIG_STACKPROTECTOR) && !defined(CONFIG_STACKPROTECTOR_PER_TASK)
4343 #include <linux/stackprotector.h>
4444 unsigned long __stack_chk_guard __read_mostly;
4545 EXPORT_SYMBOL(__stack_chk_guard);
@@ -267,6 +267,10 @@ copy_thread(unsigned long clone_flags, unsigned long stack_start, · 221 unmodified lines
267267
268268 thread_notify(THREAD_NOTIFY_COPY, thread);
269269
270+#ifdef CONFIG_STACKPROTECTOR_PER_TASK
271+ thread->stack_canary = p->stack_canary;
272+#endif
273+
270274 return 0;
271275 }
272276
scripts/Makefile.gcc-plugins+6−0
@@ -36,6 +36,12 @@ ifdef CONFIG_GCC_PLUGIN_STACKLEAK · 35 unmodified lines
3636 endif
3737 export DISABLE_STACKLEAK_PLUGIN
3838
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+
3945 # All the plugin CFLAGS are collected here in case a build target needs to
4046 # filter them out of the KBUILD_CFLAGS.
4147 GCC_PLUGINS_CFLAGS := $(strip $(addprefix -fplugin=$(objtree)/scripts/gcc-plugins/, $(gcc-plugin-y)) $(gcc-plugin-cflags-y))
scripts/gcc-plugins/Kconfig+4−0
@@ -190,4 +190,8 @@ config STACKLEAK_RUNTIME_DISABLE · 189 unmodified lines
190190 runtime to control kernel stack erasing for kernels built with
191191 CONFIG_GCC_PLUGIN_STACKLEAK.
192192
193+config GCC_PLUGIN_ARM_SSP_PER_TASK
194+ bool
195+ depends on GCC_PLUGINS && ARM
196+
193197 endif
scripts/gcc-plugins/arm_ssp_per_task_plugin.c+103−0
@@ -0,0 +1,103 @@
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+}