944 lines
20 KiB
C
944 lines
20 KiB
C
// 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>
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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
|