// 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 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
/* * 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; \ registerlong _num asm("eax") = __NR_arch_prctl; \ registerlong _arg1 asm("rdi") = (long)(arg1); \ registerlong _arg2 asm("rsi") = (long)(arg2); \
\ asmvolatile ( \ "syscall\n" \
: "=a"(_ret) \
: "r"(_arg1), "r"(_arg2), \ "0"(_num) \
: "rcx", "r11", "memory", "cc" \
); \
_ret; \
})
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 (*(unsignedlong *)shstk_ptr == MAGIC_VAL) {
printf("[FAIL]\tWrite in child wrote through to shared memory\n"); return 1;
}
/* 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];
staticvoid *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; elseif (errno == EAGAIN) continue; return (void *)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;
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;
/* 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, conststruct sigaction *restrict act, struct sigaction *restrict oldact)
{ registerlong syscall_reg asm("eax") = __NR_compat_sigaction; registerlong signum_reg asm("ebx") = signum; registerlong act_reg asm("ecx") = (long)act; registerlong oldact_reg asm("edx") = (long)oldact; int ret = 0;
/* * 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;
/* * 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.
*/ staticint test_uretprobe(void)
{ const size_t attr_sz = sizeof(struct perf_event_attr); constchar *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;
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");
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.