scs: Add support for Clang's Shadow Call Stack (SCS)
authorSami Tolvanen <samitolvanen@google.com>
Mon, 27 Apr 2020 16:00:07 +0000 (09:00 -0700)
committerWill Deacon <will@kernel.org>
Fri, 15 May 2020 15:35:45 +0000 (16:35 +0100)
This change adds generic support for Clang's Shadow Call Stack,
which uses a shadow stack to protect return addresses from being
overwritten by an attacker. Details are available here:

  https://clang.llvm.org/docs/ShadowCallStack.html

Note that security guarantees in the kernel differ from the ones
documented for user space. The kernel must store addresses of
shadow stacks in memory, which means an attacker capable reading
and writing arbitrary memory may be able to locate them and hijack
control flow by modifying the stacks.

Signed-off-by: Sami Tolvanen <samitolvanen@google.com>
Reviewed-by: Kees Cook <keescook@chromium.org>
Reviewed-by: Miguel Ojeda <miguel.ojeda.sandonis@gmail.com>
[will: Numerous cosmetic changes]
Signed-off-by: Will Deacon <will@kernel.org>
Makefile
arch/Kconfig
include/linux/compiler-clang.h
include/linux/compiler_types.h
include/linux/scs.h [new file with mode: 0644]
init/init_task.c
kernel/Makefile
kernel/fork.c
kernel/sched/core.c
kernel/scs.c [new file with mode: 0644]

index 679f302a8b8bd47288753e065af1273bbf149bfa..33dc0d0cdd088f480bdf99ec6044bb5d1cb8c11d 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -866,6 +866,12 @@ ifdef CONFIG_LIVEPATCH
 KBUILD_CFLAGS += $(call cc-option, -flive-patching=inline-clone)
 endif
 
+ifdef CONFIG_SHADOW_CALL_STACK
+CC_FLAGS_SCS   := -fsanitize=shadow-call-stack
+KBUILD_CFLAGS  += $(CC_FLAGS_SCS)
+export CC_FLAGS_SCS
+endif
+
 # arch Makefile may override CC so keep this after arch Makefile is included
 NOSTDINC_FLAGS += -nostdinc -isystem $(shell $(CC) -print-file-name=include)
 
index 786a85d4ad40d2ce9b991dc4b16ee64dbe5ff559..334a3d9b19df7eb155a2990d0925933a1e591949 100644 (file)
@@ -533,6 +533,30 @@ config STACKPROTECTOR_STRONG
          about 20% of all kernel functions, which increases the kernel code
          size by about 2%.
 
+config ARCH_SUPPORTS_SHADOW_CALL_STACK
+       bool
+       help
+         An architecture should select this if it supports Clang's Shadow
+         Call Stack, has asm/scs.h, and implements runtime support for shadow
+         stack switching.
+
+config SHADOW_CALL_STACK
+       bool "Clang Shadow Call Stack"
+       depends on CC_IS_CLANG && ARCH_SUPPORTS_SHADOW_CALL_STACK
+       help
+         This option enables Clang's Shadow Call Stack, which uses a
+         shadow stack to protect function return addresses from being
+         overwritten by an attacker. More information can be found in
+         Clang's documentation:
+
+           https://clang.llvm.org/docs/ShadowCallStack.html
+
+         Note that security guarantees in the kernel differ from the
+         ones documented for user space. The kernel must store addresses
+         of shadow stacks in memory, which means an attacker capable of
+         reading and writing arbitrary memory may be able to locate them
+         and hijack control flow by modifying the stacks.
+
 config HAVE_ARCH_WITHIN_STACK_FRAMES
        bool
        help
index 333a6695a918c3dcea8402c054bb7a6069640cb0..790c0c6b8552bd70dea93d897c47f008e7243ab1 100644 (file)
@@ -42,3 +42,7 @@
  * compilers, like ICC.
  */
 #define barrier() __asm__ __volatile__("" : : : "memory")
+
+#if __has_feature(shadow_call_stack)
+# define __noscs       __attribute__((__no_sanitize__("shadow-call-stack")))
+#endif
index e970f97a7fcb1c60f993f6e4eb13e6d947d85e60..97b62f47a80d0927c92fd745123a0416aca7cf0b 100644 (file)
@@ -193,6 +193,10 @@ struct ftrace_likely_data {
 # define randomized_struct_fields_end
 #endif
 
+#ifndef __noscs
+# define __noscs
+#endif
+
 #ifndef asm_volatile_goto
 #define asm_volatile_goto(x...) asm goto(x)
 #endif
diff --git a/include/linux/scs.h b/include/linux/scs.h
new file mode 100644 (file)
index 0000000..3f36626
--- /dev/null
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Shadow Call Stack support.
+ *
+ * Copyright (C) 2019 Google LLC
+ */
+
+#ifndef _LINUX_SCS_H
+#define _LINUX_SCS_H
+
+#include <linux/gfp.h>
+#include <linux/poison.h>
+#include <linux/sched.h>
+#include <linux/sizes.h>
+
+#ifdef CONFIG_SHADOW_CALL_STACK
+
+/*
+ * In testing, 1 KiB shadow stack size (i.e. 128 stack frames on a 64-bit
+ * architecture) provided ~40% safety margin on stack usage while keeping
+ * memory allocation overhead reasonable.
+ */
+#define SCS_SIZE               SZ_1K
+#define GFP_SCS                        (GFP_KERNEL | __GFP_ZERO)
+
+/* An illegal pointer value to mark the end of the shadow stack. */
+#define SCS_END_MAGIC          (0x5f6UL + POISON_POINTER_DELTA)
+
+#define task_scs(tsk)          (task_thread_info(tsk)->scs_base)
+#define task_scs_offset(tsk)   (task_thread_info(tsk)->scs_offset)
+
+void scs_init(void);
+int scs_prepare(struct task_struct *tsk, int node);
+void scs_release(struct task_struct *tsk);
+
+static inline void scs_task_reset(struct task_struct *tsk)
+{
+       /*
+        * Reset the shadow stack to the base address in case the task
+        * is reused.
+        */
+       task_scs_offset(tsk) = 0;
+}
+
+static inline unsigned long *__scs_magic(void *s)
+{
+       return (unsigned long *)(s + SCS_SIZE) - 1;
+}
+
+static inline bool scs_corrupted(struct task_struct *tsk)
+{
+       unsigned long *magic = __scs_magic(task_scs(tsk));
+
+       return (task_scs_offset(tsk) >= SCS_SIZE - 1 ||
+               READ_ONCE_NOCHECK(*magic) != SCS_END_MAGIC);
+}
+
+#else /* CONFIG_SHADOW_CALL_STACK */
+
+static inline void scs_init(void) {}
+static inline void scs_task_reset(struct task_struct *tsk) {}
+static inline int scs_prepare(struct task_struct *tsk, int node) { return 0; }
+static inline bool scs_corrupted(struct task_struct *tsk) { return false; }
+static inline void scs_release(struct task_struct *tsk) {}
+
+#endif /* CONFIG_SHADOW_CALL_STACK */
+
+#endif /* _LINUX_SCS_H */
index bd403ed3e4184041a32906e7b03245787778f39f..169e34066d351aabe3a23af9a46bc5e398841ef4 100644 (file)
@@ -11,6 +11,7 @@
 #include <linux/mm.h>
 #include <linux/audit.h>
 #include <linux/numa.h>
+#include <linux/scs.h>
 
 #include <asm/pgtable.h>
 #include <linux/uaccess.h>
@@ -50,6 +51,13 @@ static struct sighand_struct init_sighand = {
        .signalfd_wqh   = __WAIT_QUEUE_HEAD_INITIALIZER(init_sighand.signalfd_wqh),
 };
 
+#ifdef CONFIG_SHADOW_CALL_STACK
+unsigned long init_shadow_call_stack[SCS_SIZE / sizeof(long)]
+               __init_task_data = {
+       [(SCS_SIZE / sizeof(long)) - 1] = SCS_END_MAGIC
+};
+#endif
+
 /*
  * Set up the first task table, touch at your own risk!. Base=0,
  * limit=0x1fffff (=2MB)
index 4cb4130ced32936b5060e6b05daa23e03fd6aa1e..c332eb9d4841add15ea0220033f6b915cb8db263 100644 (file)
@@ -103,6 +103,7 @@ obj-$(CONFIG_TRACEPOINTS) += trace/
 obj-$(CONFIG_IRQ_WORK) += irq_work.o
 obj-$(CONFIG_CPU_PM) += cpu_pm.o
 obj-$(CONFIG_BPF) += bpf/
+obj-$(CONFIG_SHADOW_CALL_STACK) += scs.o
 
 obj-$(CONFIG_PERF_EVENTS) += events/
 
index 8c700f881d920dfccaf531b7df23314f7bbf4120..f6339f9d232dcac35599a0418a3039da74aa930e 100644 (file)
@@ -94,6 +94,7 @@
 #include <linux/thread_info.h>
 #include <linux/stackleak.h>
 #include <linux/kasan.h>
+#include <linux/scs.h>
 
 #include <asm/pgtable.h>
 #include <asm/pgalloc.h>
@@ -456,6 +457,8 @@ void put_task_stack(struct task_struct *tsk)
 
 void free_task(struct task_struct *tsk)
 {
+       scs_release(tsk);
+
 #ifndef CONFIG_THREAD_INFO_IN_TASK
        /*
         * The task is finally done with both the stack and thread_info,
@@ -840,6 +843,8 @@ void __init fork_init(void)
                          NULL, free_vm_stack_cache);
 #endif
 
+       scs_init();
+
        lockdep_init_task(&init_task);
        uprobes_init();
 }
@@ -899,6 +904,10 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
        if (err)
                goto free_stack;
 
+       err = scs_prepare(tsk, node);
+       if (err)
+               goto free_stack;
+
 #ifdef CONFIG_SECCOMP
        /*
         * We must handle setting up seccomp filters once we're under
index 9a2fbf98fd6fa2ea3c420d02ac4cb20d2ba73958..934e03cfaec7559e18eec57b336d2930334fefce 100644 (file)
@@ -11,6 +11,7 @@
 #include <linux/nospec.h>
 
 #include <linux/kcov.h>
+#include <linux/scs.h>
 
 #include <asm/switch_to.h>
 #include <asm/tlb.h>
@@ -6040,6 +6041,7 @@ void init_idle(struct task_struct *idle, int cpu)
        idle->se.exec_start = sched_clock();
        idle->flags |= PF_IDLE;
 
+       scs_task_reset(idle);
        kasan_unpoison_task_stack(idle);
 
 #ifdef CONFIG_SMP
diff --git a/kernel/scs.c b/kernel/scs.c
new file mode 100644 (file)
index 0000000..38f8f31
--- /dev/null
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Shadow Call Stack support.
+ *
+ * Copyright (C) 2019 Google LLC
+ */
+
+#include <linux/kasan.h>
+#include <linux/scs.h>
+#include <linux/slab.h>
+#include <asm/scs.h>
+
+static struct kmem_cache *scs_cache;
+
+static void *scs_alloc(int node)
+{
+       void *s;
+
+       s = kmem_cache_alloc_node(scs_cache, GFP_SCS, node);
+       if (s) {
+               *__scs_magic(s) = SCS_END_MAGIC;
+               /*
+                * Poison the allocation to catch unintentional accesses to
+                * the shadow stack when KASAN is enabled.
+                */
+               kasan_poison_object_data(scs_cache, s);
+       }
+
+       return s;
+}
+
+static void scs_free(void *s)
+{
+       kasan_unpoison_object_data(scs_cache, s);
+       kmem_cache_free(scs_cache, s);
+}
+
+void __init scs_init(void)
+{
+       scs_cache = kmem_cache_create("scs_cache", SCS_SIZE, 0, 0, NULL);
+}
+
+int scs_prepare(struct task_struct *tsk, int node)
+{
+       void *s = scs_alloc(node);
+
+       if (!s)
+               return -ENOMEM;
+
+       task_scs(tsk) = s;
+       task_scs_offset(tsk) = 0;
+
+       return 0;
+}
+
+void scs_release(struct task_struct *tsk)
+{
+       void *s = task_scs(tsk);
+
+       if (!s)
+               return;
+
+       WARN(scs_corrupted(tsk), "corrupted shadow stack detected when freeing task\n");
+       scs_free(s);
+}