Message ID | 20220929222936.14584-33-rick.p.edgecombe@intel.com (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | Shadowstacks for userspace | expand |
On Thu, Sep 29, 2022 at 03:29:29PM -0700, Rick Edgecombe wrote: > Add a simple selftest for exercising some shadow stack behavior: > - map_shadow_stack syscall and pivot > - Faulting in shadow stack memory > - Handling shadow stack violations > - GUP of shadow stack memory > - mprotect() of shadow stack memory > - Userfaultfd on shadow stack memory > > Since this test exercises a recently added syscall manually, it needs > to find the automatically created __NR_foo defines. Per the selftest > documentation, KHDR_INCLUDES can be used to help the selftest Makefile's > find the headers from the kernel source. This way the new selftest can > be built inside the kernel source tree without installing the headers > to the system. So also add KHDR_INCLUDES as described in the selftest > docs, to facilitate this. > > Co-developed-by: Yu-cheng Yu <yu-cheng.yu@intel.com> > Signed-off-by: Yu-cheng Yu <yu-cheng.yu@intel.com> > Signed-off-by: Rick Edgecombe <rick.p.edgecombe@intel.com> Yay tests! Thank you thank you! :) > @@ -18,7 +18,7 @@ TARGETS_C_32BIT_ONLY := entry_from_vm86 test_syscall_vdso unwind_vdso \ > test_FCMOV test_FCOMI test_FISTTP \ > vdso_restorer > TARGETS_C_64BIT_ONLY := fsgsbase sysret_rip syscall_numbering \ > - corrupt_xstate_header amx > + corrupt_xstate_header amx test_shadow_stack At present, there is still a map_shadow_stack syscall on 32-bit, so it should be tested (that it correctly does nothing with the expected error results), if it is kept. :P > [...] > +#if (__GNUC__ < 8) || (__GNUC__ == 8 && __GNUC_MINOR__ < 5) > +int main(int argc, char *argv[]) > +{ > + printf("[SKIP]\tCompiler does not support CET.\n"); > + return 0; > +} I realize other x86 selftests doesn't use the standard kselftest test harness, but if an entirely new test is being written, like here, it makes sense to use that instead. It would avoid bugs like the above, where a SKIP is seen as a success, not a skip (i.e. wrong exit code). See tools/testing/selftests/kselftest_harness.h Note that each TEST is run as a separate process. The skip here would be rewritten as: ... #include "../kselftest_harness.h" #if (__GNUC__ < 8) || (__GNUC__ == 8 && __GNUC_MINOR__ < 5) TEST(compiler_support) { SKIP(return, "Compiler does not support CET."); } #else ...rest of tests... #endif TEST_HARNESS_MAIN I'll give some other examples of replacements below... > +#else > +void write_shstk(unsigned long *addr, unsigned long val) > +{ > + asm volatile("wrssq %[val], (%[addr])\n" > + : "+m" (addr) > + : [addr] "r" (addr), [val] "r" (val)); > +} > + > +static inline unsigned long __attribute__((always_inline)) get_ssp(void) > +{ > + unsigned long ret = 0; > + > + asm volatile("xor %0, %0; rdsspq %0" : "=r" (ret)); > + return ret; > +} > + > +/* > + * For use in inline enablement of shadow stack. > + * > + * The program can't return from the point where shadow stack get's enabled > + * because there will be no address on the shadow stack. So it can't use > + * syscall() for enablement, since it is a function. Hmm, this will be a problem for glibc too? > + * > + * Based on code from nolibc.h. Keep a copy here because this can't pull in all > + * of nolibc.h. > + */ > +#define ARCH_PRCTL(arg1, arg2) \ > +({ \ > + long _ret; \ > + register long _num asm("eax") = __NR_arch_prctl; \ > + register long _arg1 asm("rdi") = (long)(arg1); \ > + register long _arg2 asm("rsi") = (long)(arg2); \ > + \ > + asm volatile ( \ > + "syscall\n" \ > + : "=a"(_ret) \ > + : "r"(_arg1), "r"(_arg2), \ > + "0"(_num) \ > + : "rcx", "r11", "memory", "cc" \ > + ); \ > + _ret; \ > +}) > + > +void *create_shstk(void *addr) > +{ > + return (void *)syscall(__NR_map_shadow_stack, addr, SS_SIZE, SHADOW_STACK_SET_TOKEN); > +} Hmm, I'd suggest adding some wider exercising of the syscall itself. (This only ever tests SS_SIZE and SHADOW_STACK_SET_TOKEN). I'd expect to see testing of error conditions too: TEST(map_shadow_stack_bad_args) { int ret; ret = ARCH_PRCTL(ARCH_CET_ENABLE, CET_SHSTK); ASSERT_EQ(0, ret) { TH_LOG("Could not enable SHSTK"); } ret = syscall(__NR_map_shadow_stack, addr, SS_SIZE, 0); EXPECT_EQ(-1, ret); EXPECT_EQ(errno, EINVAL); ret = syscall(__NR_map_shadow_stack, addr, SS_SIZE, ~(SHADOW_STACK_SET_TOKEN)); EXPECT_EQ(-1, ret); EXPECT_EQ(errno, EINVAL); ret = syscall(__NR_map_shadow_stack, addr, ULONG_MAX, SHADOW_STACK_SET_TOKEN); EXPECT_EQ(-1, ret); EXPECT_EQ(errno, ENOMEM); ret = syscall(__NR_map_shadow_stack, addr, 0, SHADOW_STACK_SET_TOKEN); EXPECT_EQ(-1, ret); EXPECT_EQ(errno, EINVAL); ... } Although the last example there will probably segv, so that could be extracted to a separate test: TEST_SIGNAL(map_shadow_stack_tiny, SIGSEGV) { int ret; ret = ARCH_PRCTL(ARCH_CET_ENABLE, CET_SHSTK); ASSERT_EQ(0, ret) { TH_LOG("Could not enable SHSTK"); } ret = syscall(__NR_map_shadow_stack, addr, 0, SHADOW_STACK_SET_TOKEN); EXPECT_EQ(0, ret) { TH_LOG("Wasn't expecting to survive the syscall"); } } Helpers are easier to plumb as expression statement macros, so: #define create_shstk(addr) ({ \ void *__addr; \ __addr = (void *)syscall(__NR_map_shadow_stack, addr, \ SS_SIZE, SHADOW_STACK_SET_TOKEN); \ ASSERT_NE(MMAP_FAILED, __addr) { \ TH_LOG("Error creating shadow stack: %d", errno); \ } \ __addr; \ }) And I expect the enable will need to be in each test, so: #define enable_shstk do { \ int __ret; \ \ __ret = ARCH_PRCTL(ARCH_CET_ENABLE, CET_SHSTK); \ ASSERT_EQ(0, __ret) { \ TH_LOG("Could not enable SHSTK"); \ } while (0) > +void *create_normal_mem(void *addr) > +{ > + return mmap(addr, SS_SIZE, PROT_READ | PROT_WRITE, > + MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); > +} > + > +void free_shstk(void *shstk) > +{ > + munmap(shstk, SS_SIZE); > +} > + > +int reset_shstk(void *shstk) > +{ > + return madvise(shstk, SS_SIZE, MADV_DONTNEED); > +} > + > +void try_shstk(unsigned long new_ssp) > +{ > + unsigned long ssp; > + > + printf("[INFO]\tnew_ssp = %lx, *new_ssp = %lx\n", > + new_ssp, *((unsigned long *)new_ssp)); > + > + ssp = get_ssp(); > + printf("[INFO]\tchanging ssp from %lx to %lx\n", ssp, new_ssp); > + > + asm volatile("rstorssp (%0)\n":: "r" (new_ssp)); > + asm volatile("saveprevssp"); > + printf("[INFO]\tssp is now %lx\n", get_ssp()); > + > + /* Switch back to original shadow stack */ > + ssp -= 8; > + asm volatile("rstorssp (%0)\n":: "r" (ssp)); > + asm volatile("saveprevssp"); > +} > + > +int test_shstk_pivot(void) > +{ > + void *shstk = create_shstk(0); > + > + if (shstk == MAP_FAILED) { > + printf("[FAIL]\tError creating shadow stack: %d\n", errno); > + return 1; > + } > + try_shstk((unsigned long)shstk + SS_SIZE - 8); > + free_shstk(shstk); > + > + printf("[OK]\tShadow stack pivot\n"); > + return 0; > +} e.g., the above could be written as this, using the previous create_shstk macro: TEST(shstk_pivot) { unsigned long ssp, new_ssp; void *shstk = create_shstk(0); new_ssp = (unsigned long)shstk + SS_SIZE - 8; TH_LOG("new_ssp = %lx, *new_ssp = %lx", new_ssp, *((unsigned long *)new_ssp); ssp = get_ssp(); TH_LOG("changing ssp from %lx to %lx", ssp, new_ssp); asm volatile("rstorssp (%0)\n":: "r" (new_ssp)); asm volatile("saveprevssp"); TH_LOG("ssp is now %lx", get_ssp()); ssp -= 8; asm volatile("rstorssp (%0)\n":: "r" (ssp)); asm volatile("saveprevssp"); free_shstk(shstk); } > + > +int test_shstk_faults(void) > +{ > + unsigned long *shstk = create_shstk(0); > + > + /* Read shadow stack, test if it's zero to not get read optimized out */ > + if (*shstk != 0) > + goto err; > + > + /* Wrss memory that was already read. */ > + write_shstk(shstk, 1); > + if (*shstk != 1) > + goto err; > + > + /* Page out memory, so we can wrss it again. */ > + if (reset_shstk((void *)shstk)) > + goto err; > + > + write_shstk(shstk, 1); > + if (*shstk != 1) > + goto err; > + > + printf("[OK]\tShadow stack faults\n"); > + return 0; > + > +err: > + return 1; > +} > + > +unsigned long saved_ssp; > +unsigned long saved_ssp_val; > +volatile bool segv_triggered; > + > +void __attribute__((noinline)) violate_ss(void) > +{ > + saved_ssp = get_ssp(); > + saved_ssp_val = *(unsigned long *)saved_ssp; > + > + /* Corrupt shadow stack */ > + printf("[INFO]\tCorrupting shadow stack\n"); > + write_shstk((void *)saved_ssp, 0); > +} > + > +void segv_handler(int signum, siginfo_t *si, void *uc) > +{ > + printf("[INFO]\tGenerated shadow stack violation successfully\n"); > + > + segv_triggered = true; > + > + /* Fix shadow stack */ > + write_shstk((void *)saved_ssp, saved_ssp_val); > +} To call TH_LOG() or EXPECT(), etc from a signal handler, you'll need to store a global and use it local with the name _metadata: struct __test_metadata *global_test_metadata; And I'd expect a test for SEGV_CPERR (add in below example). void segv_handler(int signum, siginfo_t *si, void *uc) { struct __test_metadata *_metadata = global_test_metadata; TH_LOG("enerated shadow stack violation successfully"); EXPECT_EQ(si.si_code, SEGV_CPERR); segv_triggered = true; /* Fix shadow stack */ write_shstk((void *)saved_ssp, saved_ssp_val); } > + > +int test_shstk_violation(void) > +{ > + struct sigaction sa; > + > + sa.sa_sigaction = segv_handler; > + if (sigaction(SIGSEGV, &sa, NULL)) > + return 1; > + sa.sa_flags = SA_SIGINFO; > + > + segv_triggered = false; > + > + /* Make sure segv_triggered is set before violate_ss() */ > + asm volatile("" : : : "memory"); > + > + violate_ss(); > + > + signal(SIGSEGV, SIG_DFL); > + > + printf("[OK]\tShadow stack violation test\n"); > + > + return !segv_triggered; > +} > + becomes: TEST(shstk_violation) { struct sigaction sa = { .sa_sigaction = segv_handler; .sa_flags = SA_SIGINFO; }; global_test_metadata = _metadata; ASSERT_EQ(sigaction(SIGSEGV, &sa, NULL), 0); segv_triggered = false; /* Make sure segv_triggered is set before violate_ss() */ asm volatile("" : : : "memory"); violate_ss(); signal(SIGSEGV, SIG_DFL); EXPECT_EQ(segv_trigger, 1) { TH_LOG("Segfault did not happen"); } } Without the SEGV_CPERR test, the entire thing could just be: TEST_SIGNAL(shstk_violation, SIGSEGV) { enable_shstk(); violate_ss(); } > +/* Gup test state */ > +#define MAGIC_VAL 0x12345678 > +bool is_shstk_access; > +void *shstk_ptr; > +int fd; > + > +void reset_test_shstk(void *addr) > +{ > + if (shstk_ptr != NULL) > + free_shstk(shstk_ptr); > + shstk_ptr = create_shstk(addr); > +} > + > +void test_access_fix_handler(int signum, siginfo_t *si, void *uc) > +{ > + printf("[INFO]\tViolation from %s\n", is_shstk_access ? "shstk access" : "normal write"); > + > + segv_triggered = true; > + > + /* Fix shadow stack */ > + if (is_shstk_access) { > + reset_test_shstk(shstk_ptr); > + return; > + } > + > + free_shstk(shstk_ptr); > + create_normal_mem(shstk_ptr); > +} > + > +bool test_shstk_access(void *ptr) > +{ > + is_shstk_access = true; > + segv_triggered = false; > + write_shstk(ptr, MAGIC_VAL); > + > + asm volatile("" : : : "memory"); > + > + return segv_triggered; > +} > + > +bool test_write_access(void *ptr) > +{ > + is_shstk_access = false; > + segv_triggered = false; > + *(unsigned long *)ptr = MAGIC_VAL; > + > + asm volatile("" : : : "memory"); > + > + return segv_triggered; > +} > + > +bool gup_write(void *ptr) > +{ > + unsigned long val; > + > + lseek(fd, (unsigned long)ptr, SEEK_SET); > + if (write(fd, &val, sizeof(val)) < 0) > + return 1; > + > + return 0; > +} > + > +bool gup_read(void *ptr) > +{ > + unsigned long val; > + > + lseek(fd, (unsigned long)ptr, SEEK_SET); > + if (read(fd, &val, sizeof(val)) < 0) > + return 1; > + > + return 0; > +} > + > +int test_gup(void) > +{ > + struct sigaction sa; > + int status; > + pid_t pid; > + > + sa.sa_sigaction = test_access_fix_handler; > + if (sigaction(SIGSEGV, &sa, NULL)) > + return 1; > + sa.sa_flags = SA_SIGINFO; > + > + segv_triggered = false; > + > + fd = open("/proc/self/mem", O_RDWR); > + if (fd == -1) > + return 1; > + > + reset_test_shstk(0); > + if (gup_read(shstk_ptr)) > + return 1; > + if (test_shstk_access(shstk_ptr)) > + return 1; > + printf("[INFO]\tGup read -> shstk access success\n"); > + > + reset_test_shstk(0); > + if (gup_write(shstk_ptr)) > + return 1; > + if (test_shstk_access(shstk_ptr)) > + return 1; > + printf("[INFO]\tGup write -> shstk access success\n"); For multiple thing with the same setup, you can use a fixture: FIXTURE(GUP) { int fd; void *shstk_ptr; }; FIXTURE_SETUP(GUP) { ... sigaction ... self->fd = open("/proc/self/mem", O_RDWR); ASSERT_GE(fd, 0); self->shstk_ptr = create_shstk(0); ASSERT_NE(self->shstk_ptr, NULL); } /* Don't need to clean up fd nor sigaction since process will die */ TEST_F(GUP, read) { gup_read ... test_shstk_access ... } TEST_F(GUP, write) ... Anyway, I won't cry if this doesn't get swapped to kselftest_harness, but it would be much nicer. Writing tests for that is way way easier.
diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile index 0388c4d60af0..cfc8a26ad151 100644 --- a/tools/testing/selftests/x86/Makefile +++ b/tools/testing/selftests/x86/Makefile @@ -18,7 +18,7 @@ TARGETS_C_32BIT_ONLY := entry_from_vm86 test_syscall_vdso unwind_vdso \ test_FCMOV test_FCOMI test_FISTTP \ vdso_restorer TARGETS_C_64BIT_ONLY := fsgsbase sysret_rip syscall_numbering \ - corrupt_xstate_header amx + corrupt_xstate_header amx test_shadow_stack # Some selftests require 32bit support enabled also on 64bit systems TARGETS_C_32BIT_NEEDED := ldt_gdt ptrace_syscall @@ -34,7 +34,7 @@ BINARIES_64 := $(TARGETS_C_64BIT_ALL:%=%_64) BINARIES_32 := $(patsubst %,$(OUTPUT)/%,$(BINARIES_32)) BINARIES_64 := $(patsubst %,$(OUTPUT)/%,$(BINARIES_64)) -CFLAGS := -O2 -g -std=gnu99 -pthread -Wall +CFLAGS := -O2 -g -std=gnu99 -pthread -Wall $(KHDR_INCLUDES) # call32_from_64 in thunks.S uses absolute addresses. ifeq ($(CAN_BUILD_WITH_NOPIE),1) diff --git a/tools/testing/selftests/x86/test_shadow_stack.c b/tools/testing/selftests/x86/test_shadow_stack.c new file mode 100644 index 000000000000..249397736d0d --- /dev/null +++ b/tools/testing/selftests/x86/test_shadow_stack.c @@ -0,0 +1,571 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This program test's basic kernel shadow stack support. It enables shadow + * stack manual via the arch_prctl(), instead of relying on glibc. It's + * Makefile doesn't compile with shadow stack support, so it doesn't rely on + * any particular glibc. As a result it can't do any operations that require + * special glibc shadow stack support (longjmp(), swapcontext(), etc). Just + * stick to the basics and hope the compiler doesn't do anything strange. + */ + +#define _GNU_SOURCE + +#include <sys/syscall.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <stdbool.h> +#include <x86intrin.h> +#include <asm/prctl.h> +#include <sys/prctl.h> +#include <stdint.h> +#include <signal.h> +#include <pthread.h> +#include <sys/ioctl.h> +#include <linux/userfaultfd.h> + +#define SS_SIZE 0x200000 + +#if (__GNUC__ < 8) || (__GNUC__ == 8 && __GNUC_MINOR__ < 5) +int main(int argc, char *argv[]) +{ + printf("[SKIP]\tCompiler does not support CET.\n"); + return 0; +} +#else +void write_shstk(unsigned long *addr, unsigned long val) +{ + asm volatile("wrssq %[val], (%[addr])\n" + : "+m" (addr) + : [addr] "r" (addr), [val] "r" (val)); +} + +static inline unsigned long __attribute__((always_inline)) get_ssp(void) +{ + unsigned long ret = 0; + + asm volatile("xor %0, %0; rdsspq %0" : "=r" (ret)); + return ret; +} + +/* + * For use in inline enablement of shadow stack. + * + * The program can't return from the point where shadow stack get's enabled + * because there will be no address on the shadow stack. So it can't use + * syscall() for enablement, since it is a function. + * + * Based on code from nolibc.h. Keep a copy here because this can't pull in all + * of nolibc.h. + */ +#define ARCH_PRCTL(arg1, arg2) \ +({ \ + long _ret; \ + register long _num asm("eax") = __NR_arch_prctl; \ + register long _arg1 asm("rdi") = (long)(arg1); \ + register long _arg2 asm("rsi") = (long)(arg2); \ + \ + asm volatile ( \ + "syscall\n" \ + : "=a"(_ret) \ + : "r"(_arg1), "r"(_arg2), \ + "0"(_num) \ + : "rcx", "r11", "memory", "cc" \ + ); \ + _ret; \ +}) + +void *create_shstk(void *addr) +{ + return (void *)syscall(__NR_map_shadow_stack, addr, SS_SIZE, SHADOW_STACK_SET_TOKEN); +} + +void *create_normal_mem(void *addr) +{ + return mmap(addr, SS_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); +} + +void free_shstk(void *shstk) +{ + munmap(shstk, SS_SIZE); +} + +int reset_shstk(void *shstk) +{ + return madvise(shstk, SS_SIZE, MADV_DONTNEED); +} + +void try_shstk(unsigned long new_ssp) +{ + unsigned long ssp; + + printf("[INFO]\tnew_ssp = %lx, *new_ssp = %lx\n", + new_ssp, *((unsigned long *)new_ssp)); + + ssp = get_ssp(); + printf("[INFO]\tchanging ssp from %lx to %lx\n", ssp, new_ssp); + + asm volatile("rstorssp (%0)\n":: "r" (new_ssp)); + asm volatile("saveprevssp"); + printf("[INFO]\tssp is now %lx\n", get_ssp()); + + /* Switch back to original shadow stack */ + ssp -= 8; + asm volatile("rstorssp (%0)\n":: "r" (ssp)); + asm volatile("saveprevssp"); +} + +int test_shstk_pivot(void) +{ + void *shstk = create_shstk(0); + + if (shstk == MAP_FAILED) { + printf("[FAIL]\tError creating shadow stack: %d\n", errno); + return 1; + } + try_shstk((unsigned long)shstk + SS_SIZE - 8); + free_shstk(shstk); + + printf("[OK]\tShadow stack pivot\n"); + return 0; +} + +int test_shstk_faults(void) +{ + unsigned long *shstk = create_shstk(0); + + /* Read shadow stack, test if it's zero to not get read optimized out */ + if (*shstk != 0) + goto err; + + /* Wrss memory that was already read. */ + write_shstk(shstk, 1); + if (*shstk != 1) + goto err; + + /* Page out memory, so we can wrss it again. */ + if (reset_shstk((void *)shstk)) + goto err; + + write_shstk(shstk, 1); + if (*shstk != 1) + goto err; + + printf("[OK]\tShadow stack faults\n"); + return 0; + +err: + return 1; +} + +unsigned long saved_ssp; +unsigned long saved_ssp_val; +volatile bool segv_triggered; + +void __attribute__((noinline)) violate_ss(void) +{ + saved_ssp = get_ssp(); + saved_ssp_val = *(unsigned long *)saved_ssp; + + /* Corrupt shadow stack */ + printf("[INFO]\tCorrupting shadow stack\n"); + write_shstk((void *)saved_ssp, 0); +} + +void segv_handler(int signum, siginfo_t *si, void *uc) +{ + printf("[INFO]\tGenerated shadow stack violation successfully\n"); + + segv_triggered = true; + + /* Fix shadow stack */ + write_shstk((void *)saved_ssp, saved_ssp_val); +} + +int test_shstk_violation(void) +{ + struct sigaction sa; + + sa.sa_sigaction = segv_handler; + if (sigaction(SIGSEGV, &sa, NULL)) + return 1; + sa.sa_flags = SA_SIGINFO; + + segv_triggered = false; + + /* Make sure segv_triggered is set before violate_ss() */ + asm volatile("" : : : "memory"); + + violate_ss(); + + signal(SIGSEGV, SIG_DFL); + + printf("[OK]\tShadow stack violation test\n"); + + return !segv_triggered; +} + +/* Gup test state */ +#define MAGIC_VAL 0x12345678 +bool is_shstk_access; +void *shstk_ptr; +int fd; + +void reset_test_shstk(void *addr) +{ + if (shstk_ptr != NULL) + free_shstk(shstk_ptr); + shstk_ptr = create_shstk(addr); +} + +void test_access_fix_handler(int signum, siginfo_t *si, void *uc) +{ + printf("[INFO]\tViolation from %s\n", is_shstk_access ? "shstk access" : "normal write"); + + segv_triggered = true; + + /* Fix shadow stack */ + if (is_shstk_access) { + reset_test_shstk(shstk_ptr); + return; + } + + free_shstk(shstk_ptr); + create_normal_mem(shstk_ptr); +} + +bool test_shstk_access(void *ptr) +{ + is_shstk_access = true; + segv_triggered = false; + write_shstk(ptr, MAGIC_VAL); + + asm volatile("" : : : "memory"); + + return segv_triggered; +} + +bool test_write_access(void *ptr) +{ + is_shstk_access = false; + segv_triggered = false; + *(unsigned long *)ptr = MAGIC_VAL; + + asm volatile("" : : : "memory"); + + return segv_triggered; +} + +bool gup_write(void *ptr) +{ + unsigned long val; + + lseek(fd, (unsigned long)ptr, SEEK_SET); + if (write(fd, &val, sizeof(val)) < 0) + return 1; + + return 0; +} + +bool gup_read(void *ptr) +{ + unsigned long val; + + lseek(fd, (unsigned long)ptr, SEEK_SET); + if (read(fd, &val, sizeof(val)) < 0) + return 1; + + return 0; +} + +int test_gup(void) +{ + struct sigaction sa; + int status; + pid_t pid; + + sa.sa_sigaction = test_access_fix_handler; + if (sigaction(SIGSEGV, &sa, NULL)) + return 1; + sa.sa_flags = SA_SIGINFO; + + segv_triggered = false; + + fd = open("/proc/self/mem", O_RDWR); + if (fd == -1) + return 1; + + reset_test_shstk(0); + if (gup_read(shstk_ptr)) + return 1; + if (test_shstk_access(shstk_ptr)) + return 1; + printf("[INFO]\tGup read -> shstk access success\n"); + + reset_test_shstk(0); + if (gup_write(shstk_ptr)) + return 1; + if (test_shstk_access(shstk_ptr)) + return 1; + printf("[INFO]\tGup write -> shstk access success\n"); + + reset_test_shstk(0); + if (gup_read(shstk_ptr)) + return 1; + if (!test_write_access(shstk_ptr)) + return 1; + printf("[INFO]\tGup read -> write access success\n"); + + reset_test_shstk(0); + if (gup_write(shstk_ptr)) + return 1; + if (!test_write_access(shstk_ptr)) + return 1; + printf("[INFO]\tGup write -> write access success\n"); + + close(fd); + + /* COW/gup test */ + reset_test_shstk(0); + pid = fork(); + if (!pid) { + fd = open("/proc/self/mem", O_RDWR); + if (fd == -1) + exit(1); + + if (gup_write(shstk_ptr)) { + close(fd); + exit(1); + } + close(fd); + exit(0); + } + waitpid(pid, &status, 0); + if (WEXITSTATUS(status)) { + printf("[FAIL]\tWrite in child failed\n"); + return 1; + } + if (*(unsigned long *)shstk_ptr == MAGIC_VAL) { + printf("[FAIL]\tWrite in child wrote through to shared memory\n"); + return 1; + } + + printf("[INFO]\tCow gup write -> write access success\n"); + + free_shstk(shstk_ptr); + + signal(SIGSEGV, SIG_DFL); + + printf("[OK]\tShadow gup test\n"); + + return 0; +} + +int test_mprotect(void) +{ + struct sigaction sa; + + sa.sa_sigaction = test_access_fix_handler; + if (sigaction(SIGSEGV, &sa, NULL)) + return 1; + sa.sa_flags = SA_SIGINFO; + + segv_triggered = false; + + /* mprotect a shaodw stack as read only */ + reset_test_shstk(0); + if (mprotect(shstk_ptr, SS_SIZE, PROT_READ) < 0) { + printf("[FAIL]\tmprotect(PROT_READ) failed\n"); + return 1; + } + + /* try to wrss it and fail */ + if (!test_shstk_access(shstk_ptr)) { + printf("[FAIL]\tShadow stack access to read-only memory succeeded\n"); + return 1; + } + + /* then back to writable */ + if (mprotect(shstk_ptr, SS_SIZE, PROT_WRITE | PROT_READ) < 0) { + printf("[FAIL]\tmprotect(PROT_WRITE) failed\n"); + return 1; + } + + /* then pivot to it and succeed */ + if (test_shstk_access(shstk_ptr)) { + printf("[FAIL]\tShadow stack access to mprotect() writable memory failed\n"); + return 1; + } + + free_shstk(shstk_ptr); + + signal(SIGSEGV, SIG_DFL); + + printf("[OK]\tmprotect() test\n"); + + return 0; +} + +char zero[4096]; + +static void *uffd_thread(void *arg) +{ + struct uffdio_copy req; + int uffd = *(int *)arg; + struct uffd_msg msg; + + if (read(uffd, &msg, sizeof(msg)) <= 0) + return (void *)1; + + req.dst = msg.arg.pagefault.address; + req.src = (__u64)zero; + req.len = 4096; + req.mode = 0; + + if (ioctl(uffd, UFFDIO_COPY, &req)) + return (void *)1; + + return (void *)0; +} + +int test_userfaultfd(void) +{ + struct uffdio_register uffdio_register; + struct uffdio_api uffdio_api; + struct sigaction sa; + pthread_t thread; + void *res; + int uffd; + + sa.sa_sigaction = test_access_fix_handler; + if (sigaction(SIGSEGV, &sa, NULL)) + return 1; + sa.sa_flags = SA_SIGINFO; + + uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); + if (uffd < 0) { + printf("[SKIP]\tUserfaultfd unavailable.\n"); + return 0; + } + + reset_test_shstk(0); + + uffdio_api.api = UFFD_API; + uffdio_api.features = 0; + if (ioctl(uffd, UFFDIO_API, &uffdio_api)) + goto err; + + uffdio_register.range.start = (__u64)shstk_ptr; + uffdio_register.range.len = 4096; + uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING; + if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register)) + goto err; + + if (pthread_create(&thread, NULL, &uffd_thread, &uffd)) + goto err; + + test_shstk_access(shstk_ptr); + + if (pthread_join(thread, &res)) + goto err; + + if (test_shstk_access(shstk_ptr)) + goto err; + + free_shstk(shstk_ptr); + + signal(SIGSEGV, SIG_DFL); + + printf("[OK]\tUserfaultfd test\n"); + return !!res; +err: + free_shstk(shstk_ptr); + close(uffd); + signal(SIGSEGV, SIG_DFL); + return 1; +} + +int main(int argc, char *argv[]) +{ + int ret = 0; + + if (ARCH_PRCTL(ARCH_CET_ENABLE, CET_SHSTK)) { + printf("[SKIP]\tCould not enable Shadow stack\n"); + return 1; + } + + if (ARCH_PRCTL(ARCH_CET_DISABLE, CET_SHSTK)) { + ret = 1; + printf("[FAIL]\tDisabling shadow stack failed\n"); + } + + if (ARCH_PRCTL(ARCH_CET_ENABLE, CET_SHSTK)) { + printf("[SKIP]\tCould not re-enable Shadow stack\n"); + return 1; + } + + if (ARCH_PRCTL(ARCH_CET_ENABLE, CET_WRSS)) { + printf("[SKIP]\tCould not enable WRSS\n"); + ret = 1; + goto out; + } + + /* Should have succeeded if here, but this is a test, so double check. */ + if (!get_ssp()) { + printf("[FAIL]\tShadow stack disabled\n"); + return 1; + } + + if (test_shstk_pivot()) { + ret = 1; + printf("[FAIL]\tShadow stack pivot\n"); + goto out; + } + + if (test_shstk_faults()) { + ret = 1; + printf("[FAIL]\tShadow stack fault test\n"); + goto out; + } + + if (test_shstk_violation()) { + ret = 1; + printf("[FAIL]\tShadow stack violation test\n"); + goto out; + } + + if (test_gup()) { + ret = 1; + printf("[FAIL]\tShadow shadow stack gup\n"); + } + + if (test_mprotect()) { + ret = 1; + printf("[FAIL]\tShadow shadow mprotect test\n"); + } + + if (test_userfaultfd()) { + ret = 1; + printf("[FAIL]\tUserfaultfd test\n"); + } + +out: + /* + * Disable shadow stack before the function returns, or there will be a + * shadow stack violation. + */ + if (ARCH_PRCTL(ARCH_CET_DISABLE, CET_SHSTK)) { + ret = 1; + printf("[FAIL]\tDisabling shadow stack failed\n"); + } + + return ret; +} +#endif