1088 lines · 22.8 KB · C
RawDownload
// 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;}#elsevoid 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 0x12345678bool 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;	}

Lines 1–500 of 1088.