]> asedeno.scripts.mit.edu Git - linux.git/commitdiff
ARM: smp: add support for per-task stack canaries
authorArd Biesheuvel <ard.biesheuvel@linaro.org>
Thu, 6 Dec 2018 08:32:57 +0000 (09:32 +0100)
committerKees Cook <keescook@chromium.org>
Wed, 12 Dec 2018 21:20:07 +0000 (13:20 -0800)
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>
arch/arm/Kconfig
arch/arm/Makefile
arch/arm/boot/compressed/Makefile
arch/arm/include/asm/stackprotector.h
arch/arm/include/asm/thread_info.h
arch/arm/kernel/asm-offsets.c
arch/arm/kernel/process.c
scripts/Makefile.gcc-plugins
scripts/gcc-plugins/Kconfig
scripts/gcc-plugins/arm_ssp_per_task_plugin.c [new file with mode: 0644]

index 91be74d8df658ccd5a4701fe7438053115dceeb9..5c0305585a0a24074a9af78f69e47d6efc87c3c7 100644 (file)
@@ -1810,6 +1810,21 @@ config XEN
        help
          Say Y if you want to run Linux in a Virtual Machine on Xen on ARM.
 
+config STACKPROTECTOR_PER_TASK
+       bool "Use a unique stack canary value for each task"
+       depends on GCC_PLUGINS && STACKPROTECTOR && SMP && !XIP_DEFLATED_DATA
+       select GCC_PLUGIN_ARM_SSP_PER_TASK
+       default y
+       help
+         Due to the fact that GCC uses an ordinary symbol reference from
+         which to load the value of the stack canary, this value can only
+         change at reboot time on SMP systems, and all tasks running in the
+         kernel's address space are forced to use the same canary value for
+         the entire duration that the system is up.
+
+         Enable this option to switch to a different method that uses a
+         different canary value for each task.
+
 endmenu
 
 menu "Boot options"
index 05a91d8b89f357b389921eb66aa2591a043526bf..0436002d509190c38fd6d037967b0ebf4df9a4f7 100644 (file)
@@ -303,6 +303,18 @@ else
 KBUILD_IMAGE := $(boot)/zImage
 endif
 
+ifeq ($(CONFIG_STACKPROTECTOR_PER_TASK),y)
+prepare: stack_protector_prepare
+stack_protector_prepare: prepare0
+       $(eval KBUILD_CFLAGS += \
+               -fplugin-arg-arm_ssp_per_task_plugin-tso=$(shell        \
+                       awk '{if ($$2 == "THREAD_SZ_ORDER") print $$3;}'\
+                               include/generated/asm-offsets.h)        \
+               -fplugin-arg-arm_ssp_per_task_plugin-offset=$(shell     \
+                       awk '{if ($$2 == "TI_STACK_CANARY") print $$3;}'\
+                               include/generated/asm-offsets.h))
+endif
+
 all:   $(notdir $(KBUILD_IMAGE))
 
 
index 1f5a5ffe7fcf84b5da64bc74747c50384f5bfeea..01bf2585a0fa08f0dfe4ff35167f4526d183c61f 100644 (file)
@@ -101,6 +101,7 @@ clean-files += piggy_data lib1funcs.S ashldi3.S bswapsdi2.S \
                $(libfdt) $(libfdt_hdrs) hyp-stub.S
 
 KBUILD_CFLAGS += -DDISABLE_BRANCH_PROFILING
+KBUILD_CFLAGS += $(DISABLE_ARM_SSP_PER_TASK_PLUGIN)
 
 ifeq ($(CONFIG_FUNCTION_TRACER),y)
 ORIG_CFLAGS := $(KBUILD_CFLAGS)
index ef5f7b69443ec59e752038394b4ab6a4f12fe819..72a20c3a0a90b2121952161ed3839274ebf8c44a 100644 (file)
@@ -6,8 +6,10 @@
  * the stack frame and verifying that it hasn't been overwritten when
  * returning from the function.  The pattern is called stack canary
  * and gcc expects it to be defined by a global variable called
- * "__stack_chk_guard" on ARM.  This unfortunately means that on SMP
- * we cannot have a different canary value per task.
+ * "__stack_chk_guard" on ARM.  This prevents SMP systems from using a
+ * different value for each task unless we enable a GCC plugin that
+ * replaces these symbol references with references to each task's own
+ * value.
  */
 
 #ifndef _ASM_STACKPROTECTOR_H
@@ -16,6 +18,8 @@
 #include <linux/random.h>
 #include <linux/version.h>
 
+#include <asm/thread_info.h>
+
 extern unsigned long __stack_chk_guard;
 
 /*
@@ -33,7 +37,11 @@ static __always_inline void boot_init_stack_canary(void)
        canary ^= LINUX_VERSION_CODE;
 
        current->stack_canary = canary;
+#ifndef CONFIG_STACKPROTECTOR_PER_TASK
        __stack_chk_guard = current->stack_canary;
+#else
+       current_thread_info()->stack_canary = current->stack_canary;
+#endif
 }
 
 #endif /* _ASM_STACKPROTECTOR_H */
index 8f55dc520a3e5512cd1474f4a5c3c2d7e72698e8..286eb61c632bead2fa16b8590b0915f3269f40a7 100644 (file)
@@ -53,6 +53,9 @@ struct thread_info {
        struct task_struct      *task;          /* main task structure */
        __u32                   cpu;            /* cpu */
        __u32                   cpu_domain;     /* cpu domain */
+#ifdef CONFIG_STACKPROTECTOR_PER_TASK
+       unsigned long           stack_canary;
+#endif
        struct cpu_context_save cpu_context;    /* cpu context */
        __u32                   syscall;        /* syscall number */
        __u8                    used_cp[16];    /* thread used copro */
index 3968d6c22455be5057909ea4d928f7ee0054e1a2..28b27104ac0c62fb5db1ef8469ccb8492dbee007 100644 (file)
@@ -79,6 +79,10 @@ int main(void)
 #ifdef CONFIG_CRUNCH
   DEFINE(TI_CRUNCH_STATE,      offsetof(struct thread_info, crunchstate));
 #endif
+#ifdef CONFIG_STACKPROTECTOR_PER_TASK
+  DEFINE(TI_STACK_CANARY,      offsetof(struct thread_info, stack_canary));
+#endif
+  DEFINE(THREAD_SZ_ORDER,      THREAD_SIZE_ORDER);
   BLANK();
   DEFINE(S_R0,                 offsetof(struct pt_regs, ARM_r0));
   DEFINE(S_R1,                 offsetof(struct pt_regs, ARM_r1));
index 82ab015bf42b7bf263cd13d61274e6f4e899d9db..16601d1442d1767b4498442b8454a31afb34268d 100644 (file)
@@ -39,7 +39,7 @@
 #include <asm/tls.h>
 #include <asm/vdso.h>
 
-#ifdef CONFIG_STACKPROTECTOR
+#if defined(CONFIG_STACKPROTECTOR) && !defined(CONFIG_STACKPROTECTOR_PER_TASK)
 #include <linux/stackprotector.h>
 unsigned long __stack_chk_guard __read_mostly;
 EXPORT_SYMBOL(__stack_chk_guard);
@@ -267,6 +267,10 @@ copy_thread(unsigned long clone_flags, unsigned long stack_start,
 
        thread_notify(THREAD_NOTIFY_COPY, thread);
 
+#ifdef CONFIG_STACKPROTECTOR_PER_TASK
+       thread->stack_canary = p->stack_canary;
+#endif
+
        return 0;
 }
 
index 46c5c680980657dfc8997d5127bfab198b710490..048179d8c07fc1646e446b7d4fa550b257dcb396 100644 (file)
@@ -36,6 +36,12 @@ ifdef CONFIG_GCC_PLUGIN_STACKLEAK
 endif
 export DISABLE_STACKLEAK_PLUGIN
 
+gcc-plugin-$(CONFIG_GCC_PLUGIN_ARM_SSP_PER_TASK) += arm_ssp_per_task_plugin.so
+ifdef CONFIG_GCC_PLUGIN_ARM_SSP_PER_TASK
+    DISABLE_ARM_SSP_PER_TASK_PLUGIN += -fplugin-arg-arm_ssp_per_task_plugin-disable
+endif
+export DISABLE_ARM_SSP_PER_TASK_PLUGIN
+
 # All the plugin CFLAGS are collected here in case a build target needs to
 # filter them out of the KBUILD_CFLAGS.
 GCC_PLUGINS_CFLAGS := $(strip $(addprefix -fplugin=$(objtree)/scripts/gcc-plugins/, $(gcc-plugin-y)) $(gcc-plugin-cflags-y))
index 0d5c799688f0ae31fa02f82dc8451ad1a5811ff5..d45f7f36b859a55aac1192de6072ca3870085581 100644 (file)
@@ -190,4 +190,8 @@ config STACKLEAK_RUNTIME_DISABLE
          runtime to control kernel stack erasing for kernels built with
          CONFIG_GCC_PLUGIN_STACKLEAK.
 
+config GCC_PLUGIN_ARM_SSP_PER_TASK
+       bool
+       depends on GCC_PLUGINS && ARM
+
 endif
diff --git a/scripts/gcc-plugins/arm_ssp_per_task_plugin.c b/scripts/gcc-plugins/arm_ssp_per_task_plugin.c
new file mode 100644 (file)
index 0000000..de70b84
--- /dev/null
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "gcc-common.h"
+
+__visible int plugin_is_GPL_compatible;
+
+static unsigned int sp_mask, canary_offset;
+
+static unsigned int arm_pertask_ssp_rtl_execute(void)
+{
+       rtx_insn *insn;
+
+       for (insn = get_insns(); insn; insn = NEXT_INSN(insn)) {
+               const char *sym;
+               rtx body;
+               rtx masked_sp;
+
+               /*
+                * Find a SET insn involving a SYMBOL_REF to __stack_chk_guard
+                */
+               if (!INSN_P(insn))
+                       continue;
+               body = PATTERN(insn);
+               if (GET_CODE(body) != SET ||
+                   GET_CODE(SET_SRC(body)) != SYMBOL_REF)
+                       continue;
+               sym = XSTR(SET_SRC(body), 0);
+               if (strcmp(sym, "__stack_chk_guard"))
+                       continue;
+
+               /*
+                * Replace the source of the SET insn with an expression that
+                * produces the address of the copy of the stack canary value
+                * stored in struct thread_info
+                */
+               masked_sp = gen_reg_rtx(Pmode);
+
+               emit_insn_before(gen_rtx_SET(masked_sp,
+                                            gen_rtx_AND(Pmode,
+                                                        stack_pointer_rtx,
+                                                        GEN_INT(sp_mask))),
+                                insn);
+
+               SET_SRC(body) = gen_rtx_PLUS(Pmode, masked_sp,
+                                            GEN_INT(canary_offset));
+       }
+       return 0;
+}
+
+#define PASS_NAME arm_pertask_ssp_rtl
+
+#define NO_GATE
+#include "gcc-generate-rtl-pass.h"
+
+__visible int plugin_init(struct plugin_name_args *plugin_info,
+                         struct plugin_gcc_version *version)
+{
+       const char * const plugin_name = plugin_info->base_name;
+       const int argc = plugin_info->argc;
+       const struct plugin_argument *argv = plugin_info->argv;
+       int tso = 0;
+       int i;
+
+       if (!plugin_default_version_check(version, &gcc_version)) {
+               error(G_("incompatible gcc/plugin versions"));
+               return 1;
+       }
+
+       for (i = 0; i < argc; ++i) {
+               if (!strcmp(argv[i].key, "disable"))
+                       return 0;
+
+               /* all remaining options require a value */
+               if (!argv[i].value) {
+                       error(G_("no value supplied for option '-fplugin-arg-%s-%s'"),
+                             plugin_name, argv[i].key);
+                       return 1;
+               }
+
+               if (!strcmp(argv[i].key, "tso")) {
+                       tso = atoi(argv[i].value);
+                       continue;
+               }
+
+               if (!strcmp(argv[i].key, "offset")) {
+                       canary_offset = atoi(argv[i].value);
+                       continue;
+               }
+               error(G_("unknown option '-fplugin-arg-%s-%s'"),
+                     plugin_name, argv[i].key);
+               return 1;
+       }
+
+       /* create the mask that produces the base of the stack */
+       sp_mask = ~((1U << (12 + tso)) - 1);
+
+       PASS_INFO(arm_pertask_ssp_rtl, "expand", 1, PASS_POS_INSERT_AFTER);
+
+       register_callback(plugin_info->base_name, PLUGIN_PASS_MANAGER_SETUP,
+                         NULL, &arm_pertask_ssp_rtl_pass_info);
+
+       return 0;
+}