x86/shstk: Introduce map_shadow_stack syscall
authorRick Edgecombe <rick.p.edgecombe@intel.com>
Tue, 13 Jun 2023 00:11:00 +0000 (17:11 -0700)
committerDave Hansen <dave.hansen@linux.intel.com>
Wed, 2 Aug 2023 22:01:51 +0000 (15:01 -0700)
When operating with shadow stacks enabled, the kernel will automatically
allocate shadow stacks for new threads, however in some cases userspace
will need additional shadow stacks. The main example of this is the
ucontext family of functions, which require userspace allocating and
pivoting to userspace managed stacks.

Unlike most other user memory permissions, shadow stacks need to be
provisioned with special data in order to be useful. They need to be setup
with a restore token so that userspace can pivot to them via the RSTORSSP
instruction. But, the security design of shadow stacks is that they
should not be written to except in limited circumstances. This presents a
problem for userspace, as to how userspace can provision this special
data, without allowing for the shadow stack to be generally writable.

Previously, a new PROT_SHADOW_STACK was attempted, which could be
mprotect()ed from RW permissions after the data was provisioned. This was
found to not be secure enough, as other threads could write to the
shadow stack during the writable window.

The kernel can use a special instruction, WRUSS, to write directly to
userspace shadow stacks. So the solution can be that memory can be mapped
as shadow stack permissions from the beginning (never generally writable
in userspace), and the kernel itself can write the restore token.

First, a new madvise() flag was explored, which could operate on the
PROT_SHADOW_STACK memory. This had a couple of downsides:
1. Extra checks were needed in mprotect() to prevent writable memory from
   ever becoming PROT_SHADOW_STACK.
2. Extra checks/vma state were needed in the new madvise() to prevent
   restore tokens being written into the middle of pre-used shadow stacks.
   It is ideal to prevent restore tokens being added at arbitrary
   locations, so the check was to make sure the shadow stack had never been
   written to.
3. It stood out from the rest of the madvise flags, as more of direct
   action than a hint at future desired behavior.

So rather than repurpose two existing syscalls (mmap, madvise) that don't
quite fit, just implement a new map_shadow_stack syscall to allow
userspace to map and setup new shadow stacks in one step. While ucontext
is the primary motivator, userspace may have other unforeseen reasons to
setup its own shadow stacks using the WRSS instruction. Towards this
provide a flag so that stacks can be optionally setup securely for the
common case of ucontext without enabling WRSS. Or potentially have the
kernel set up the shadow stack in some new way.

The following example demonstrates how to create a new shadow stack with
map_shadow_stack:
void *shstk = map_shadow_stack(addr, stack_size, SHADOW_STACK_SET_TOKEN);

Signed-off-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
Signed-off-by: Dave Hansen <dave.hansen@linux.intel.com>
Reviewed-by: Borislav Petkov (AMD) <bp@alien8.de>
Reviewed-by: Kees Cook <keescook@chromium.org>
Acked-by: Mike Rapoport (IBM) <rppt@kernel.org>
Tested-by: Pengfei Xu <pengfei.xu@intel.com>
Tested-by: John Allen <john.allen@amd.com>
Tested-by: Kees Cook <keescook@chromium.org>
Link: https://lore.kernel.org/all/20230613001108.3040476-35-rick.p.edgecombe%40intel.com
arch/x86/entry/syscalls/syscall_64.tbl
arch/x86/include/uapi/asm/mman.h
arch/x86/kernel/shstk.c
include/linux/syscalls.h
kernel/sys_ni.c

index 227538b0ce801eeba96e10d8214e7847b77cee88..38db4b1c291a046a1a56b6d8152a2400a26386f9 100644 (file)
 449    common  futex_waitv             sys_futex_waitv
 450    common  set_mempolicy_home_node sys_set_mempolicy_home_node
 451    common  cachestat               sys_cachestat
+452    64      map_shadow_stack        sys_map_shadow_stack
 
 #
 # Due to a historical design error, certain syscalls are numbered differently
index 5a0256e73f1e48fdfc314d7ff899e0ae41b3d286..8148bdddbd2ca5059d36642f74d856f070d07150 100644 (file)
@@ -13,6 +13,9 @@
                ((key) & 0x8 ? VM_PKEY_BIT3 : 0))
 #endif
 
+/* Flags for map_shadow_stack(2) */
+#define SHADOW_STACK_SET_TOKEN (1ULL << 0)     /* Set up a restore token in the shadow stack */
+
 #include <asm-generic/mman.h>
 
 #endif /* _ASM_X86_MMAN_H */
index 50733a51044658de3125c02233bbe145e9672c24..04c37b33a62573d0099b0ac71ed8dba3a8c64837 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/compat.h>
 #include <linux/sizes.h>
 #include <linux/user.h>
+#include <linux/syscalls.h>
 #include <asm/msr.h>
 #include <asm/fpu/xstate.h>
 #include <asm/fpu/types.h>
@@ -71,19 +72,31 @@ static int create_rstor_token(unsigned long ssp, unsigned long *token_addr)
        return 0;
 }
 
-static unsigned long alloc_shstk(unsigned long size)
+static unsigned long alloc_shstk(unsigned long addr, unsigned long size,
+                                unsigned long token_offset, bool set_res_tok)
 {
        int flags = MAP_ANONYMOUS | MAP_PRIVATE | MAP_ABOVE4G;
        struct mm_struct *mm = current->mm;
-       unsigned long addr, unused;
+       unsigned long mapped_addr, unused;
 
-       mmap_write_lock(mm);
-       addr = do_mmap(NULL, 0, size, PROT_READ, flags,
-                      VM_SHADOW_STACK | VM_WRITE, 0, &unused, NULL);
+       if (addr)
+               flags |= MAP_FIXED_NOREPLACE;
 
+       mmap_write_lock(mm);
+       mapped_addr = do_mmap(NULL, addr, size, PROT_READ, flags,
+                             VM_SHADOW_STACK | VM_WRITE, 0, &unused, NULL);
        mmap_write_unlock(mm);
 
-       return addr;
+       if (!set_res_tok || IS_ERR_VALUE(mapped_addr))
+               goto out;
+
+       if (create_rstor_token(mapped_addr + token_offset, NULL)) {
+               vm_munmap(mapped_addr, size);
+               return -EINVAL;
+       }
+
+out:
+       return mapped_addr;
 }
 
 static unsigned long adjust_shstk_size(unsigned long size)
@@ -134,7 +147,7 @@ static int shstk_setup(void)
                return -EOPNOTSUPP;
 
        size = adjust_shstk_size(0);
-       addr = alloc_shstk(size);
+       addr = alloc_shstk(0, size, 0, false);
        if (IS_ERR_VALUE(addr))
                return PTR_ERR((void *)addr);
 
@@ -178,7 +191,7 @@ unsigned long shstk_alloc_thread_stack(struct task_struct *tsk, unsigned long cl
                return 0;
 
        size = adjust_shstk_size(stack_size);
-       addr = alloc_shstk(size);
+       addr = alloc_shstk(0, size, 0, false);
        if (IS_ERR_VALUE(addr))
                return addr;
 
@@ -398,6 +411,36 @@ static int shstk_disable(void)
        return 0;
 }
 
+SYSCALL_DEFINE3(map_shadow_stack, unsigned long, addr, unsigned long, size, unsigned int, flags)
+{
+       bool set_tok = flags & SHADOW_STACK_SET_TOKEN;
+       unsigned long aligned_size;
+
+       if (!cpu_feature_enabled(X86_FEATURE_USER_SHSTK))
+               return -EOPNOTSUPP;
+
+       if (flags & ~SHADOW_STACK_SET_TOKEN)
+               return -EINVAL;
+
+       /* If there isn't space for a token */
+       if (set_tok && size < 8)
+               return -ENOSPC;
+
+       if (addr && addr < SZ_4G)
+               return -ERANGE;
+
+       /*
+        * An overflow would result in attempting to write the restore token
+        * to the wrong location. Not catastrophic, but just return the right
+        * error code and block it.
+        */
+       aligned_size = PAGE_ALIGN(size);
+       if (aligned_size < size)
+               return -EOVERFLOW;
+
+       return alloc_shstk(addr, aligned_size, size, set_tok);
+}
+
 long shstk_prctl(struct task_struct *task, int option, unsigned long features)
 {
        if (option == ARCH_SHSTK_LOCK) {
index 03e3d0121d5e3eb7498a245265d3ce8d76667a53..7f6dc098819781fb63f24803e9cdbd296382c60b 100644 (file)
@@ -953,6 +953,7 @@ asmlinkage long sys_set_mempolicy_home_node(unsigned long start, unsigned long l
 asmlinkage long sys_cachestat(unsigned int fd,
                struct cachestat_range __user *cstat_range,
                struct cachestat __user *cstat, unsigned int flags);
+asmlinkage long sys_map_shadow_stack(unsigned long addr, unsigned long size, unsigned int flags);
 
 /*
  * Architecture-specific system calls
index 781de7cc6a4e19b2829983c9db9dd3ff6eb80fe4..e137c1385c569ee573d5234dbe2fc59d036a715b 100644 (file)
@@ -274,6 +274,7 @@ COND_SYSCALL(vm86old);
 COND_SYSCALL(modify_ldt);
 COND_SYSCALL(vm86);
 COND_SYSCALL(kexec_file_load);
+COND_SYSCALL(map_shadow_stack);
 
 /* s390 */
 COND_SYSCALL(s390_pci_mmio_read);