| // 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 <asm/mman.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> |
| #include <setjmp.h> |
| #include <sys/ptrace.h> |
| #include <sys/signal.h> |
| #include <linux/elf.h> |
| #include <linux/perf_event.h> |
| |
| /* |
| * Define the ABI defines if needed, so people can run the tests |
| * without building the headers. |
| */ |
| #ifndef __NR_map_shadow_stack |
| #define __NR_map_shadow_stack 453 |
| |
| #define SHADOW_STACK_SET_TOKEN (1ULL << 0) |
| |
| #define ARCH_SHSTK_ENABLE 0x5001 |
| #define ARCH_SHSTK_DISABLE 0x5002 |
| #define ARCH_SHSTK_LOCK 0x5003 |
| #define ARCH_SHSTK_UNLOCK 0x5004 |
| #define ARCH_SHSTK_STATUS 0x5005 |
| |
| #define ARCH_SHSTK_SHSTK (1ULL << 0) |
| #define ARCH_SHSTK_WRSS (1ULL << 1) |
| |
| #define NT_X86_SHSTK 0x204 |
| #endif |
| |
| #define SS_SIZE 0x200000 |
| #define PAGE_SIZE 0x1000 |
| |
| #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 gets 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; |
| sa.sa_flags = SA_SIGINFO; |
| if (sigaction(SIGSEGV, &sa, NULL)) |
| return 1; |
| |
| 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) |
| 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; |
| sa.sa_flags = SA_SIGINFO; |
| if (sigaction(SIGSEGV, &sa, NULL)) |
| return 1; |
| |
| 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; |
| sa.sa_flags = SA_SIGINFO; |
| if (sigaction(SIGSEGV, &sa, NULL)) |
| return 1; |
| |
| segv_triggered = false; |
| |
| /* mprotect a shadow 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; |
| } |
| |
| /* |
| * The shadow stack was reset above to resolve the fault, make the new one |
| * read-only. |
| */ |
| if (mprotect(shstk_ptr, SS_SIZE, PROT_READ) < 0) { |
| printf("[FAIL]\tmprotect(PROT_READ) failed\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 wrss 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; |
| int ret; |
| |
| while (1) { |
| ret = read(uffd, &msg, sizeof(msg)); |
| if (ret > 0) |
| break; |
| else if (errno == EAGAIN) |
| continue; |
| 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; |
| sa.sa_flags = SA_SIGINFO; |
| if (sigaction(SIGSEGV, &sa, NULL)) |
| return 1; |
| |
| 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; |
| |
| reset_shstk(shstk_ptr); |
| 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); |
| |
| if (!res) |
| printf("[OK]\tUserfaultfd test\n"); |
| return !!res; |
| err: |
| free_shstk(shstk_ptr); |
| close(uffd); |
| signal(SIGSEGV, SIG_DFL); |
| return 1; |
| } |
| |
| /* Simple linked list for keeping track of mappings in test_guard_gap() */ |
| struct node { |
| struct node *next; |
| void *mapping; |
| }; |
| |
| /* |
| * This tests whether mmap will place other mappings in a shadow stack's guard |
| * gap. The steps are: |
| * 1. Finds an empty place by mapping and unmapping something. |
| * 2. Map a shadow stack in the middle of the known empty area. |
| * 3. Map a bunch of PAGE_SIZE mappings. These will use the search down |
| * direction, filling any gaps until it encounters the shadow stack's |
| * guard gap. |
| * 4. When a mapping lands below the shadow stack from step 2, then all |
| * of the above gaps are filled. The search down algorithm will have |
| * looked at the shadow stack gaps. |
| * 5. See if it landed in the gap. |
| */ |
| int test_guard_gap_other_gaps(void) |
| { |
| void *free_area, *shstk, *test_map = (void *)0xFFFFFFFFFFFFFFFF; |
| struct node *head = NULL, *cur; |
| |
| free_area = mmap(0, SS_SIZE * 3, PROT_READ | PROT_WRITE, |
| MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
| munmap(free_area, SS_SIZE * 3); |
| |
| shstk = create_shstk(free_area + SS_SIZE); |
| if (shstk == MAP_FAILED) |
| return 1; |
| |
| while (test_map > shstk) { |
| test_map = mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE, |
| MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
| if (test_map == MAP_FAILED) |
| return 1; |
| cur = malloc(sizeof(*cur)); |
| cur->mapping = test_map; |
| |
| cur->next = head; |
| head = cur; |
| } |
| |
| while (head) { |
| cur = head; |
| head = cur->next; |
| munmap(cur->mapping, PAGE_SIZE); |
| free(cur); |
| } |
| |
| free_shstk(shstk); |
| |
| if (shstk - test_map - PAGE_SIZE != PAGE_SIZE) |
| return 1; |
| |
| printf("[OK]\tGuard gap test, other mapping's gaps\n"); |
| |
| return 0; |
| } |
| |
| /* Tests respecting the guard gap of the mapping getting placed */ |
| int test_guard_gap_new_mappings_gaps(void) |
| { |
| void *free_area, *shstk_start, *test_map = (void *)0xFFFFFFFFFFFFFFFF; |
| struct node *head = NULL, *cur; |
| int ret = 0; |
| |
| free_area = mmap(0, PAGE_SIZE * 4, PROT_READ | PROT_WRITE, |
| MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
| munmap(free_area, PAGE_SIZE * 4); |
| |
| /* Test letting map_shadow_stack find a free space */ |
| shstk_start = mmap(free_area, PAGE_SIZE, PROT_READ | PROT_WRITE, |
| MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
| if (shstk_start == MAP_FAILED || shstk_start != free_area) |
| return 1; |
| |
| while (test_map > shstk_start) { |
| test_map = (void *)syscall(__NR_map_shadow_stack, 0, PAGE_SIZE, 0); |
| if (test_map == MAP_FAILED) { |
| printf("[INFO]\tmap_shadow_stack MAP_FAILED\n"); |
| ret = 1; |
| break; |
| } |
| |
| cur = malloc(sizeof(*cur)); |
| cur->mapping = test_map; |
| |
| cur->next = head; |
| head = cur; |
| |
| if (test_map == free_area + PAGE_SIZE) { |
| printf("[INFO]\tNew mapping has other mapping in guard gap!\n"); |
| ret = 1; |
| break; |
| } |
| } |
| |
| while (head) { |
| cur = head; |
| head = cur->next; |
| munmap(cur->mapping, PAGE_SIZE); |
| free(cur); |
| } |
| |
| munmap(shstk_start, PAGE_SIZE); |
| |
| if (!ret) |
| printf("[OK]\tGuard gap test, placement mapping's gaps\n"); |
| |
| return ret; |
| } |
| |
| /* |
| * Too complicated to pull it out of the 32 bit header, but also get the |
| * 64 bit one needed above. Just define a copy here. |
| */ |
| #define __NR_compat_sigaction 67 |
| |
| /* |
| * Call 32 bit signal handler to get 32 bit signals ABI. Make sure |
| * to push the registers that will get clobbered. |
| */ |
| int sigaction32(int signum, const struct sigaction *restrict act, |
| struct sigaction *restrict oldact) |
| { |
| register long syscall_reg asm("eax") = __NR_compat_sigaction; |
| register long signum_reg asm("ebx") = signum; |
| register long act_reg asm("ecx") = (long)act; |
| register long oldact_reg asm("edx") = (long)oldact; |
| int ret = 0; |
| |
| asm volatile ("int $0x80;" |
| : "=a"(ret), "=m"(oldact) |
| : "r"(syscall_reg), "r"(signum_reg), "r"(act_reg), |
| "r"(oldact_reg) |
| : "r8", "r9", "r10", "r11" |
| ); |
| |
| return ret; |
| } |
| |
| sigjmp_buf jmp_buffer; |
| |
| void segv_gp_handler(int signum, siginfo_t *si, void *uc) |
| { |
| segv_triggered = true; |
| |
| /* |
| * To work with old glibc, this can't rely on siglongjmp working with |
| * shadow stack enabled, so disable shadow stack before siglongjmp(). |
| */ |
| ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK); |
| siglongjmp(jmp_buffer, -1); |
| } |
| |
| /* |
| * Transition to 32 bit mode and check that a #GP triggers a segfault. |
| */ |
| int test_32bit(void) |
| { |
| struct sigaction sa = {}; |
| struct sigaction *sa32; |
| |
| /* Create sigaction in 32 bit address range */ |
| sa32 = mmap(0, 4096, PROT_READ | PROT_WRITE, |
| MAP_32BIT | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); |
| sa32->sa_flags = SA_SIGINFO; |
| |
| sa.sa_sigaction = segv_gp_handler; |
| sa.sa_flags = SA_SIGINFO; |
| if (sigaction(SIGSEGV, &sa, NULL)) |
| return 1; |
| |
| |
| segv_triggered = false; |
| |
| /* Make sure segv_triggered is set before triggering the #GP */ |
| asm volatile("" : : : "memory"); |
| |
| /* |
| * Set handler to somewhere in 32 bit address space |
| */ |
| sa32->sa_handler = (void *)sa32; |
| if (sigaction32(SIGUSR1, sa32, NULL)) |
| return 1; |
| |
| if (!sigsetjmp(jmp_buffer, 1)) |
| raise(SIGUSR1); |
| |
| if (segv_triggered) |
| printf("[OK]\t32 bit test\n"); |
| |
| return !segv_triggered; |
| } |
| |
| static int parse_uint_from_file(const char *file, const char *fmt) |
| { |
| int err, ret; |
| FILE *f; |
| |
| f = fopen(file, "re"); |
| if (!f) { |
| err = -errno; |
| printf("failed to open '%s': %d\n", file, err); |
| return err; |
| } |
| err = fscanf(f, fmt, &ret); |
| if (err != 1) { |
| err = err == EOF ? -EIO : -errno; |
| printf("failed to parse '%s': %d\n", file, err); |
| fclose(f); |
| return err; |
| } |
| fclose(f); |
| return ret; |
| } |
| |
| static int determine_uprobe_perf_type(void) |
| { |
| const char *file = "/sys/bus/event_source/devices/uprobe/type"; |
| |
| return parse_uint_from_file(file, "%d\n"); |
| } |
| |
| static int determine_uprobe_retprobe_bit(void) |
| { |
| const char *file = "/sys/bus/event_source/devices/uprobe/format/retprobe"; |
| |
| return parse_uint_from_file(file, "config:%d\n"); |
| } |
| |
| static ssize_t get_uprobe_offset(const void *addr) |
| { |
| size_t start, end, base; |
| char buf[256]; |
| bool found = false; |
| FILE *f; |
| |
| f = fopen("/proc/self/maps", "r"); |
| if (!f) |
| return -errno; |
| |
| while (fscanf(f, "%zx-%zx %s %zx %*[^\n]\n", &start, &end, buf, &base) == 4) { |
| if (buf[2] == 'x' && (uintptr_t)addr >= start && (uintptr_t)addr < end) { |
| found = true; |
| break; |
| } |
| } |
| |
| fclose(f); |
| |
| if (!found) |
| return -ESRCH; |
| |
| return (uintptr_t)addr - start + base; |
| } |
| |
| static __attribute__((noinline)) void uretprobe_trigger(void) |
| { |
| asm volatile (""); |
| } |
| |
| /* |
| * This test setups return uprobe, which is sensitive to shadow stack |
| * (crashes without extra fix). After executing the uretprobe we fail |
| * the test if we receive SIGSEGV, no crash means we're good. |
| * |
| * Helper functions above borrowed from bpf selftests. |
| */ |
| static int test_uretprobe(void) |
| { |
| const size_t attr_sz = sizeof(struct perf_event_attr); |
| const char *file = "/proc/self/exe"; |
| int bit, fd = 0, type, err = 1; |
| struct perf_event_attr attr; |
| struct sigaction sa = {}; |
| ssize_t offset; |
| |
| type = determine_uprobe_perf_type(); |
| if (type < 0) { |
| if (type == -ENOENT) |
| printf("[SKIP]\tUretprobe test, uprobes are not available\n"); |
| return 0; |
| } |
| |
| offset = get_uprobe_offset(uretprobe_trigger); |
| if (offset < 0) |
| return 1; |
| |
| bit = determine_uprobe_retprobe_bit(); |
| if (bit < 0) |
| return 1; |
| |
| sa.sa_sigaction = segv_gp_handler; |
| sa.sa_flags = SA_SIGINFO; |
| if (sigaction(SIGSEGV, &sa, NULL)) |
| return 1; |
| |
| /* Setup return uprobe through perf event interface. */ |
| memset(&attr, 0, attr_sz); |
| attr.size = attr_sz; |
| attr.type = type; |
| attr.config = 1 << bit; |
| attr.config1 = (__u64) (unsigned long) file; |
| attr.config2 = offset; |
| |
| fd = syscall(__NR_perf_event_open, &attr, 0 /* pid */, -1 /* cpu */, |
| -1 /* group_fd */, PERF_FLAG_FD_CLOEXEC); |
| if (fd < 0) |
| goto out; |
| |
| if (sigsetjmp(jmp_buffer, 1)) |
| goto out; |
| |
| ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK); |
| |
| /* |
| * This either segfaults and goes through sigsetjmp above |
| * or succeeds and we're good. |
| */ |
| uretprobe_trigger(); |
| |
| printf("[OK]\tUretprobe test\n"); |
| err = 0; |
| |
| out: |
| ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK); |
| signal(SIGSEGV, SIG_DFL); |
| if (fd) |
| close(fd); |
| return err; |
| } |
| |
| void segv_handler_ptrace(int signum, siginfo_t *si, void *uc) |
| { |
| /* The SSP adjustment caused a segfault. */ |
| exit(0); |
| } |
| |
| int test_ptrace(void) |
| { |
| unsigned long saved_ssp, ssp = 0; |
| struct sigaction sa= {}; |
| struct iovec iov; |
| int status; |
| int pid; |
| |
| iov.iov_base = &ssp; |
| iov.iov_len = sizeof(ssp); |
| |
| pid = fork(); |
| if (!pid) { |
| ssp = get_ssp(); |
| |
| sa.sa_sigaction = segv_handler_ptrace; |
| sa.sa_flags = SA_SIGINFO; |
| if (sigaction(SIGSEGV, &sa, NULL)) |
| return 1; |
| |
| ptrace(PTRACE_TRACEME, NULL, NULL, NULL); |
| /* |
| * The parent will tweak the SSP and return from this function |
| * will #CP. |
| */ |
| raise(SIGTRAP); |
| |
| exit(1); |
| } |
| |
| while (waitpid(pid, &status, 0) != -1 && WSTOPSIG(status) != SIGTRAP); |
| |
| if (ptrace(PTRACE_GETREGSET, pid, NT_X86_SHSTK, &iov)) { |
| printf("[INFO]\tFailed to PTRACE_GETREGS\n"); |
| goto out_kill; |
| } |
| |
| if (!ssp) { |
| printf("[INFO]\tPtrace child SSP was 0\n"); |
| goto out_kill; |
| } |
| |
| saved_ssp = ssp; |
| |
| iov.iov_len = 0; |
| if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) { |
| printf("[INFO]\tToo small size accepted via PTRACE_SETREGS\n"); |
| goto out_kill; |
| } |
| |
| iov.iov_len = sizeof(ssp) + 1; |
| if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) { |
| printf("[INFO]\tToo large size accepted via PTRACE_SETREGS\n"); |
| goto out_kill; |
| } |
| |
| ssp += 1; |
| if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) { |
| printf("[INFO]\tUnaligned SSP written via PTRACE_SETREGS\n"); |
| goto out_kill; |
| } |
| |
| ssp = 0xFFFFFFFFFFFF0000; |
| if (!ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) { |
| printf("[INFO]\tKernel range SSP written via PTRACE_SETREGS\n"); |
| goto out_kill; |
| } |
| |
| /* |
| * Tweak the SSP so the child with #CP when it resumes and returns |
| * from raise() |
| */ |
| ssp = saved_ssp + 8; |
| iov.iov_len = sizeof(ssp); |
| if (ptrace(PTRACE_SETREGSET, pid, NT_X86_SHSTK, &iov)) { |
| printf("[INFO]\tFailed to PTRACE_SETREGS\n"); |
| goto out_kill; |
| } |
| |
| if (ptrace(PTRACE_DETACH, pid, NULL, NULL)) { |
| printf("[INFO]\tFailed to PTRACE_DETACH\n"); |
| goto out_kill; |
| } |
| |
| waitpid(pid, &status, 0); |
| if (WEXITSTATUS(status)) |
| return 1; |
| |
| printf("[OK]\tPtrace test\n"); |
| return 0; |
| |
| out_kill: |
| kill(pid, SIGKILL); |
| return 1; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int ret = 0; |
| |
| if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK)) { |
| printf("[SKIP]\tCould not enable Shadow stack\n"); |
| return 1; |
| } |
| |
| if (ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK)) { |
| ret = 1; |
| printf("[FAIL]\tDisabling shadow stack failed\n"); |
| } |
| |
| if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK)) { |
| printf("[SKIP]\tCould not re-enable Shadow stack\n"); |
| return 1; |
| } |
| |
| if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_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"); |
| goto out; |
| } |
| |
| if (test_mprotect()) { |
| ret = 1; |
| printf("[FAIL]\tShadow shadow mprotect test\n"); |
| goto out; |
| } |
| |
| if (test_userfaultfd()) { |
| ret = 1; |
| printf("[FAIL]\tUserfaultfd test\n"); |
| goto out; |
| } |
| |
| if (test_guard_gap_other_gaps()) { |
| ret = 1; |
| printf("[FAIL]\tGuard gap test, other mappings' gaps\n"); |
| goto out; |
| } |
| |
| if (test_guard_gap_new_mappings_gaps()) { |
| ret = 1; |
| printf("[FAIL]\tGuard gap test, placement mapping's gaps\n"); |
| goto out; |
| } |
| |
| if (test_ptrace()) { |
| ret = 1; |
| printf("[FAIL]\tptrace test\n"); |
| } |
| |
| if (test_32bit()) { |
| ret = 1; |
| printf("[FAIL]\t32 bit test\n"); |
| goto out; |
| } |
| |
| if (test_uretprobe()) { |
| ret = 1; |
| printf("[FAIL]\turetprobe test\n"); |
| goto out; |
| } |
| |
| return ret; |
| |
| out: |
| /* |
| * Disable shadow stack before the function returns, or there will be a |
| * shadow stack violation. |
| */ |
| if (ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK)) { |
| ret = 1; |
| printf("[FAIL]\tDisabling shadow stack failed\n"); |
| } |
| |
| return ret; |
| } |
| #endif |