// 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_shs