Skip to content

Commit c35559f

Browse files
rpedgecohansendc
authored andcommitted
x86/shstk: Introduce map_shadow_stack syscall
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 <[email protected]> Signed-off-by: Dave Hansen <[email protected]> Reviewed-by: Borislav Petkov (AMD) <[email protected]> Reviewed-by: Kees Cook <[email protected]> Acked-by: Mike Rapoport (IBM) <[email protected]> Tested-by: Pengfei Xu <[email protected]> Tested-by: John Allen <[email protected]> Tested-by: Kees Cook <[email protected]> Link: https://lore.kernel.org/all/20230613001108.3040476-35-rick.p.edgecombe%40intel.com
1 parent 7fad2a4 commit c35559f

File tree

5 files changed

+57
-8
lines changed

5 files changed

+57
-8
lines changed

arch/x86/entry/syscalls/syscall_64.tbl

+1
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@
373373
449 common futex_waitv sys_futex_waitv
374374
450 common set_mempolicy_home_node sys_set_mempolicy_home_node
375375
451 common cachestat sys_cachestat
376+
452 64 map_shadow_stack sys_map_shadow_stack
376377

377378
#
378379
# Due to a historical design error, certain syscalls are numbered differently

arch/x86/include/uapi/asm/mman.h

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
((key) & 0x8 ? VM_PKEY_BIT3 : 0))
1414
#endif
1515

16+
/* Flags for map_shadow_stack(2) */
17+
#define SHADOW_STACK_SET_TOKEN (1ULL << 0) /* Set up a restore token in the shadow stack */
18+
1619
#include <asm-generic/mman.h>
1720

1821
#endif /* _ASM_X86_MMAN_H */

arch/x86/kernel/shstk.c

+51-8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <linux/compat.h>
1818
#include <linux/sizes.h>
1919
#include <linux/user.h>
20+
#include <linux/syscalls.h>
2021
#include <asm/msr.h>
2122
#include <asm/fpu/xstate.h>
2223
#include <asm/fpu/types.h>
@@ -71,19 +72,31 @@ static int create_rstor_token(unsigned long ssp, unsigned long *token_addr)
7172
return 0;
7273
}
7374

74-
static unsigned long alloc_shstk(unsigned long size)
75+
static unsigned long alloc_shstk(unsigned long addr, unsigned long size,
76+
unsigned long token_offset, bool set_res_tok)
7577
{
7678
int flags = MAP_ANONYMOUS | MAP_PRIVATE | MAP_ABOVE4G;
7779
struct mm_struct *mm = current->mm;
78-
unsigned long addr, unused;
80+
unsigned long mapped_addr, unused;
7981

80-
mmap_write_lock(mm);
81-
addr = do_mmap(NULL, 0, size, PROT_READ, flags,
82-
VM_SHADOW_STACK | VM_WRITE, 0, &unused, NULL);
82+
if (addr)
83+
flags |= MAP_FIXED_NOREPLACE;
8384

85+
mmap_write_lock(mm);
86+
mapped_addr = do_mmap(NULL, addr, size, PROT_READ, flags,
87+
VM_SHADOW_STACK | VM_WRITE, 0, &unused, NULL);
8488
mmap_write_unlock(mm);
8589

86-
return addr;
90+
if (!set_res_tok || IS_ERR_VALUE(mapped_addr))
91+
goto out;
92+
93+
if (create_rstor_token(mapped_addr + token_offset, NULL)) {
94+
vm_munmap(mapped_addr, size);
95+
return -EINVAL;
96+
}
97+
98+
out:
99+
return mapped_addr;
87100
}
88101

89102
static unsigned long adjust_shstk_size(unsigned long size)
@@ -134,7 +147,7 @@ static int shstk_setup(void)
134147
return -EOPNOTSUPP;
135148

136149
size = adjust_shstk_size(0);
137-
addr = alloc_shstk(size);
150+
addr = alloc_shstk(0, size, 0, false);
138151
if (IS_ERR_VALUE(addr))
139152
return PTR_ERR((void *)addr);
140153

@@ -178,7 +191,7 @@ unsigned long shstk_alloc_thread_stack(struct task_struct *tsk, unsigned long cl
178191
return 0;
179192

180193
size = adjust_shstk_size(stack_size);
181-
addr = alloc_shstk(size);
194+
addr = alloc_shstk(0, size, 0, false);
182195
if (IS_ERR_VALUE(addr))
183196
return addr;
184197

@@ -398,6 +411,36 @@ static int shstk_disable(void)
398411
return 0;
399412
}
400413

414+
SYSCALL_DEFINE3(map_shadow_stack, unsigned long, addr, unsigned long, size, unsigned int, flags)
415+
{
416+
bool set_tok = flags & SHADOW_STACK_SET_TOKEN;
417+
unsigned long aligned_size;
418+
419+
if (!cpu_feature_enabled(X86_FEATURE_USER_SHSTK))
420+
return -EOPNOTSUPP;
421+
422+
if (flags & ~SHADOW_STACK_SET_TOKEN)
423+
return -EINVAL;
424+
425+
/* If there isn't space for a token */
426+
if (set_tok && size < 8)
427+
return -ENOSPC;
428+
429+
if (addr && addr < SZ_4G)
430+
return -ERANGE;
431+
432+
/*
433+
* An overflow would result in attempting to write the restore token
434+
* to the wrong location. Not catastrophic, but just return the right
435+
* error code and block it.
436+
*/
437+
aligned_size = PAGE_ALIGN(size);
438+
if (aligned_size < size)
439+
return -EOVERFLOW;
440+
441+
return alloc_shstk(addr, aligned_size, size, set_tok);
442+
}
443+
401444
long shstk_prctl(struct task_struct *task, int option, unsigned long features)
402445
{
403446
if (option == ARCH_SHSTK_LOCK) {

include/linux/syscalls.h

+1
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,7 @@ asmlinkage long sys_set_mempolicy_home_node(unsigned long start, unsigned long l
953953
asmlinkage long sys_cachestat(unsigned int fd,
954954
struct cachestat_range __user *cstat_range,
955955
struct cachestat __user *cstat, unsigned int flags);
956+
asmlinkage long sys_map_shadow_stack(unsigned long addr, unsigned long size, unsigned int flags);
956957

957958
/*
958959
* Architecture-specific system calls

kernel/sys_ni.c

+1
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ COND_SYSCALL(vm86old);
274274
COND_SYSCALL(modify_ldt);
275275
COND_SYSCALL(vm86);
276276
COND_SYSCALL(kexec_file_load);
277+
COND_SYSCALL(map_shadow_stack);
277278

278279
/* s390 */
279280
COND_SYSCALL(s390_pci_mmio_read);

0 commit comments

Comments
 (0)