| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright (C) 2023 ARM Limited. | |
| */ | |
| #define _GNU_SOURCE | |
| #include <pthread.h> | |
| #include <stdbool.h> | |
| #include <sys/auxv.h> | |
| #include <sys/mman.h> | |
| #include <sys/prctl.h> | |
| #include <sys/ptrace.h> | |
| #include <sys/uio.h> | |
| #include <asm/hwcap.h> | |
| #include <asm/mman.h> | |
| #include <asm/ptrace.h> | |
| #include <linux/compiler.h> | |
| #include "kselftest_harness.h" | |
| #include "gcs-util.h" | |
| #define my_syscall2(num, arg1, arg2) \ | |
| ({ \ | |
| register long _num __asm__ ("x8") = (num); \ | |
| register long _arg1 __asm__ ("x0") = (long)(arg1); \ | |
| register long _arg2 __asm__ ("x1") = (long)(arg2); \ | |
| register long _arg3 __asm__ ("x2") = 0; \ | |
| register long _arg4 __asm__ ("x3") = 0; \ | |
| register long _arg5 __asm__ ("x4") = 0; \ | |
| \ | |
| __asm__ volatile ( \ | |
| "svc #0\n" \ | |
| : "=r"(_arg1) \ | |
| : "r"(_arg1), "r"(_arg2), \ | |
| "r"(_arg3), "r"(_arg4), \ | |
| "r"(_arg5), "r"(_num) \ | |
| : "memory", "cc" \ | |
| ); \ | |
| _arg1; \ | |
| }) | |
| static noinline void gcs_recurse(int depth) | |
| { | |
| if (depth) | |
| gcs_recurse(depth - 1); | |
| /* Prevent tail call optimization so we actually recurse */ | |
| asm volatile("dsb sy" : : : "memory"); | |
| } | |
| /* Smoke test that a function call and return works*/ | |
| TEST(can_call_function) | |
| { | |
| gcs_recurse(0); | |
| } | |
| static void *gcs_test_thread(void *arg) | |
| { | |
| int ret; | |
| unsigned long mode; | |
| /* | |
| * Some libcs don't seem to fill unused arguments with 0 but | |
| * the kernel validates this so we supply all 5 arguments. | |
| */ | |
| ret = prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0); | |
| if (ret != 0) { | |
| ksft_print_msg("PR_GET_SHADOW_STACK_STATUS failed: %d\n", ret); | |
| return NULL; | |
| } | |
| if (!(mode & PR_SHADOW_STACK_ENABLE)) { | |
| ksft_print_msg("GCS not enabled in thread, mode is %lu\n", | |
| mode); | |
| return NULL; | |
| } | |
| /* Just in case... */ | |
| gcs_recurse(0); | |
| /* Use a non-NULL value to indicate a pass */ | |
| return &gcs_test_thread; | |
| } | |
| /* Verify that if we start a new thread it has GCS enabled */ | |
| TEST(gcs_enabled_thread) | |
| { | |
| pthread_t thread; | |
| void *thread_ret; | |
| int ret; | |
| ret = pthread_create(&thread, NULL, gcs_test_thread, NULL); | |
| ASSERT_TRUE(ret == 0); | |
| if (ret != 0) | |
| return; | |
| ret = pthread_join(thread, &thread_ret); | |
| ASSERT_TRUE(ret == 0); | |
| if (ret != 0) | |
| return; | |
| ASSERT_TRUE(thread_ret != NULL); | |
| } | |
| /* Read the GCS until we find the terminator */ | |
| TEST(gcs_find_terminator) | |
| { | |
| unsigned long *gcs, *cur; | |
| gcs = get_gcspr(); | |
| cur = gcs; | |
| while (*cur) | |
| cur++; | |
| ksft_print_msg("GCS in use from %p-%p\n", gcs, cur); | |
| /* | |
| * We should have at least whatever called into this test so | |
| * the two pointer should differ. | |
| */ | |
| ASSERT_TRUE(gcs != cur); | |
| } | |
| /* | |
| * We can access a GCS via ptrace | |
| * | |
| * This could usefully have a fixture but note that each test is | |
| * fork()ed into a new child which causes issues. Might be better to | |
| * lift at least some of this out into a separate, non-harness, test | |
| * program. | |
| */ | |
| TEST(ptrace_read_write) | |
| { | |
| pid_t child, pid; | |
| int ret, status; | |
| siginfo_t si; | |
| uint64_t val, rval, gcspr; | |
| struct user_gcs child_gcs; | |
| struct iovec iov, local_iov, remote_iov; | |
| child = fork(); | |
| if (child == -1) { | |
| ksft_print_msg("fork() failed: %d (%s)\n", | |
| errno, strerror(errno)); | |
| ASSERT_NE(child, -1); | |
| } | |
| if (child == 0) { | |
| /* | |
| * In child, make sure there's something on the stack and | |
| * ask to be traced. | |
| */ | |
| gcs_recurse(0); | |
| if (ptrace(PTRACE_TRACEME, -1, NULL, NULL)) | |
| ksft_exit_fail_msg("PTRACE_TRACEME %s", | |
| strerror(errno)); | |
| if (raise(SIGSTOP)) | |
| ksft_exit_fail_msg("raise(SIGSTOP) %s", | |
| strerror(errno)); | |
| return; | |
| } | |
| ksft_print_msg("Child: %d\n", child); | |
| /* Attach to the child */ | |
| while (1) { | |
| int sig; | |
| pid = wait(&status); | |
| if (pid == -1) { | |
| ksft_print_msg("wait() failed: %s", | |
| strerror(errno)); | |
| goto error; | |
| } | |
| /* | |
| * This should never happen but it's hard to flag in | |
| * the framework. | |
| */ | |
| if (pid != child) | |
| continue; | |
| if (WIFEXITED(status) || WIFSIGNALED(status)) | |
| ksft_exit_fail_msg("Child died unexpectedly\n"); | |
| if (!WIFSTOPPED(status)) | |
| goto error; | |
| sig = WSTOPSIG(status); | |
| if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &si)) { | |
| if (errno == ESRCH) { | |
| ASSERT_NE(errno, ESRCH); | |
| return; | |
| } | |
| if (errno == EINVAL) { | |
| sig = 0; /* bust group-stop */ | |
| goto cont; | |
| } | |
| ksft_print_msg("PTRACE_GETSIGINFO: %s\n", | |
| strerror(errno)); | |
| goto error; | |
| } | |
| if (sig == SIGSTOP && si.si_code == SI_TKILL && | |
| si.si_pid == pid) | |
| break; | |
| cont: | |
| if (ptrace(PTRACE_CONT, pid, NULL, sig)) { | |
| if (errno == ESRCH) { | |
| ASSERT_NE(errno, ESRCH); | |
| return; | |
| } | |
| ksft_print_msg("PTRACE_CONT: %s\n", strerror(errno)); | |
| goto error; | |
| } | |
| } | |
| /* Where is the child GCS? */ | |
| iov.iov_base = &child_gcs; | |
| iov.iov_len = sizeof(child_gcs); | |
| ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_GCS, &iov); | |
| if (ret != 0) { | |
| ksft_print_msg("Failed to read child GCS state: %s (%d)\n", | |
| strerror(errno), errno); | |
| goto error; | |
| } | |
| /* We should have inherited GCS over fork(), confirm */ | |
| if (!(child_gcs.features_enabled & PR_SHADOW_STACK_ENABLE)) { | |
| ASSERT_TRUE(child_gcs.features_enabled & | |
| PR_SHADOW_STACK_ENABLE); | |
| goto error; | |
| } | |
| gcspr = child_gcs.gcspr_el0; | |
| ksft_print_msg("Child GCSPR 0x%lx, flags %llx, locked %llx\n", | |
| gcspr, child_gcs.features_enabled, | |
| child_gcs.features_locked); | |
| /* Ideally we'd cross check with the child memory map */ | |
| errno = 0; | |
| val = ptrace(PTRACE_PEEKDATA, child, (void *)gcspr, NULL); | |
| ret = errno; | |
| if (ret != 0) | |
| ksft_print_msg("PTRACE_PEEKDATA failed: %s (%d)\n", | |
| strerror(ret), ret); | |
| EXPECT_EQ(ret, 0); | |
| /* The child should be in a function, the GCSPR shouldn't be 0 */ | |
| EXPECT_NE(val, 0); | |
| /* Same thing via process_vm_readv() */ | |
| local_iov.iov_base = &rval; | |
| local_iov.iov_len = sizeof(rval); | |
| remote_iov.iov_base = (void *)gcspr; | |
| remote_iov.iov_len = sizeof(rval); | |
| ret = process_vm_readv(child, &local_iov, 1, &remote_iov, 1, 0); | |
| if (ret == -1) | |
| ksft_print_msg("process_vm_readv() failed: %s (%d)\n", | |
| strerror(errno), errno); | |
| EXPECT_EQ(ret, sizeof(rval)); | |
| EXPECT_EQ(val, rval); | |
| /* Write data via a peek */ | |
| ret = ptrace(PTRACE_POKEDATA, child, (void *)gcspr, NULL); | |
| if (ret == -1) | |
| ksft_print_msg("PTRACE_POKEDATA failed: %s (%d)\n", | |
| strerror(errno), errno); | |
| EXPECT_EQ(ret, 0); | |
| EXPECT_EQ(0, ptrace(PTRACE_PEEKDATA, child, (void *)gcspr, NULL)); | |
| /* Restore what we had before */ | |
| ret = ptrace(PTRACE_POKEDATA, child, (void *)gcspr, val); | |
| if (ret == -1) | |
| ksft_print_msg("PTRACE_POKEDATA failed: %s (%d)\n", | |
| strerror(errno), errno); | |
| EXPECT_EQ(ret, 0); | |
| EXPECT_EQ(val, ptrace(PTRACE_PEEKDATA, child, (void *)gcspr, NULL)); | |
| /* That's all, folks */ | |
| kill(child, SIGKILL); | |
| return; | |
| error: | |
| kill(child, SIGKILL); | |
| ASSERT_FALSE(true); | |
| } | |
| FIXTURE(map_gcs) | |
| { | |
| unsigned long *stack; | |
| }; | |
| FIXTURE_VARIANT(map_gcs) | |
| { | |
| size_t stack_size; | |
| unsigned long flags; | |
| }; | |
| FIXTURE_VARIANT_ADD(map_gcs, s2k_cap_marker) | |
| { | |
| .stack_size = 2 * 1024, | |
| .flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN, | |
| }; | |
| FIXTURE_VARIANT_ADD(map_gcs, s2k_cap) | |
| { | |
| .stack_size = 2 * 1024, | |
| .flags = SHADOW_STACK_SET_TOKEN, | |
| }; | |
| FIXTURE_VARIANT_ADD(map_gcs, s2k_marker) | |
| { | |
| .stack_size = 2 * 1024, | |
| .flags = SHADOW_STACK_SET_MARKER, | |
| }; | |
| FIXTURE_VARIANT_ADD(map_gcs, s2k) | |
| { | |
| .stack_size = 2 * 1024, | |
| .flags = 0, | |
| }; | |
| FIXTURE_VARIANT_ADD(map_gcs, s4k_cap_marker) | |
| { | |
| .stack_size = 4 * 1024, | |
| .flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN, | |
| }; | |
| FIXTURE_VARIANT_ADD(map_gcs, s4k_cap) | |
| { | |
| .stack_size = 4 * 1024, | |
| .flags = SHADOW_STACK_SET_TOKEN, | |
| }; | |
| FIXTURE_VARIANT_ADD(map_gcs, s3k_marker) | |
| { | |
| .stack_size = 4 * 1024, | |
| .flags = SHADOW_STACK_SET_MARKER, | |
| }; | |
| FIXTURE_VARIANT_ADD(map_gcs, s4k) | |
| { | |
| .stack_size = 4 * 1024, | |
| .flags = 0, | |
| }; | |
| FIXTURE_VARIANT_ADD(map_gcs, s16k_cap_marker) | |
| { | |
| .stack_size = 16 * 1024, | |
| .flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN, | |
| }; | |
| FIXTURE_VARIANT_ADD(map_gcs, s16k_cap) | |
| { | |
| .stack_size = 16 * 1024, | |
| .flags = SHADOW_STACK_SET_TOKEN, | |
| }; | |
| FIXTURE_VARIANT_ADD(map_gcs, s16k_marker) | |
| { | |
| .stack_size = 16 * 1024, | |
| .flags = SHADOW_STACK_SET_MARKER, | |
| }; | |
| FIXTURE_VARIANT_ADD(map_gcs, s16k) | |
| { | |
| .stack_size = 16 * 1024, | |
| .flags = 0, | |
| }; | |
| FIXTURE_VARIANT_ADD(map_gcs, s64k_cap_marker) | |
| { | |
| .stack_size = 64 * 1024, | |
| .flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN, | |
| }; | |
| FIXTURE_VARIANT_ADD(map_gcs, s64k_cap) | |
| { | |
| .stack_size = 64 * 1024, | |
| .flags = SHADOW_STACK_SET_TOKEN, | |
| }; | |
| FIXTURE_VARIANT_ADD(map_gcs, s64k_marker) | |
| { | |
| .stack_size = 64 * 1024, | |
| .flags = SHADOW_STACK_SET_MARKER, | |
| }; | |
| FIXTURE_VARIANT_ADD(map_gcs, s64k) | |
| { | |
| .stack_size = 64 * 1024, | |
| .flags = 0, | |
| }; | |
| FIXTURE_VARIANT_ADD(map_gcs, s128k_cap_marker) | |
| { | |
| .stack_size = 128 * 1024, | |
| .flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN, | |
| }; | |
| FIXTURE_VARIANT_ADD(map_gcs, s128k_cap) | |
| { | |
| .stack_size = 128 * 1024, | |
| .flags = SHADOW_STACK_SET_TOKEN, | |
| }; | |
| FIXTURE_VARIANT_ADD(map_gcs, s128k_marker) | |
| { | |
| .stack_size = 128 * 1024, | |
| .flags = SHADOW_STACK_SET_MARKER, | |
| }; | |
| FIXTURE_VARIANT_ADD(map_gcs, s128k) | |
| { | |
| .stack_size = 128 * 1024, | |
| .flags = 0, | |
| }; | |
| FIXTURE_SETUP(map_gcs) | |
| { | |
| self->stack = (void *)syscall(__NR_map_shadow_stack, 0, | |
| variant->stack_size, | |
| variant->flags); | |
| ASSERT_FALSE(self->stack == MAP_FAILED); | |
| ksft_print_msg("Allocated stack from %p-%p\n", self->stack, | |
| self->stack + variant->stack_size); | |
| } | |
| FIXTURE_TEARDOWN(map_gcs) | |
| { | |
| int ret; | |
| if (self->stack != MAP_FAILED) { | |
| ret = munmap(self->stack, variant->stack_size); | |
| ASSERT_EQ(ret, 0); | |
| } | |
| } | |
| /* The stack has a cap token */ | |
| TEST_F(map_gcs, stack_capped) | |
| { | |
| unsigned long *stack = self->stack; | |
| size_t cap_index; | |
| cap_index = (variant->stack_size / sizeof(unsigned long)); | |
| switch (variant->flags & (SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN)) { | |
| case SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN: | |
| cap_index -= 2; | |
| break; | |
| case SHADOW_STACK_SET_TOKEN: | |
| cap_index -= 1; | |
| break; | |
| case SHADOW_STACK_SET_MARKER: | |
| case 0: | |
| /* No cap, no test */ | |
| return; | |
| } | |
| ASSERT_EQ(stack[cap_index], GCS_CAP(&stack[cap_index])); | |
| } | |
| /* The top of the stack is 0 */ | |
| TEST_F(map_gcs, stack_terminated) | |
| { | |
| unsigned long *stack = self->stack; | |
| size_t term_index; | |
| if (!(variant->flags & SHADOW_STACK_SET_MARKER)) | |
| return; | |
| term_index = (variant->stack_size / sizeof(unsigned long)) - 1; | |
| ASSERT_EQ(stack[term_index], 0); | |
| } | |
| /* Writes should fault */ | |
| TEST_F_SIGNAL(map_gcs, not_writeable, SIGSEGV) | |
| { | |
| self->stack[0] = 0; | |
| } | |
| /* Put it all together, we can safely switch to and from the stack */ | |
| TEST_F(map_gcs, stack_switch) | |
| { | |
| size_t cap_index; | |
| cap_index = (variant->stack_size / sizeof(unsigned long)); | |
| unsigned long *orig_gcspr_el0, *pivot_gcspr_el0; | |
| /* Skip over the stack terminator and point at the cap */ | |
| switch (variant->flags & (SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN)) { | |
| case SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN: | |
| cap_index -= 2; | |
| break; | |
| case SHADOW_STACK_SET_TOKEN: | |
| cap_index -= 1; | |
| break; | |
| case SHADOW_STACK_SET_MARKER: | |
| case 0: | |
| /* No cap, no test */ | |
| return; | |
| } | |
| pivot_gcspr_el0 = &self->stack[cap_index]; | |
| /* Pivot to the new GCS */ | |
| ksft_print_msg("Pivoting to %p from %p, target has value 0x%lx\n", | |
| pivot_gcspr_el0, get_gcspr(), | |
| *pivot_gcspr_el0); | |
| gcsss1(pivot_gcspr_el0); | |
| orig_gcspr_el0 = gcsss2(); | |
| ksft_print_msg("Pivoted to %p from %p, target has value 0x%lx\n", | |
| get_gcspr(), orig_gcspr_el0, | |
| *pivot_gcspr_el0); | |
| ksft_print_msg("Pivoted, GCSPR_EL0 now %p\n", get_gcspr()); | |
| /* New GCS must be in the new buffer */ | |
| ASSERT_TRUE((unsigned long)get_gcspr() > (unsigned long)self->stack); | |
| ASSERT_TRUE((unsigned long)get_gcspr() <= | |
| (unsigned long)self->stack + variant->stack_size); | |
| /* We should be able to use all but 2 slots of the new stack */ | |
| ksft_print_msg("Recursing %zu levels\n", cap_index - 1); | |
| gcs_recurse(cap_index - 1); | |
| /* Pivot back to the original GCS */ | |
| gcsss1(orig_gcspr_el0); | |
| pivot_gcspr_el0 = gcsss2(); | |
| gcs_recurse(0); | |
| ksft_print_msg("Pivoted back to GCSPR_EL0 0x%p\n", get_gcspr()); | |
| } | |
| /* We fault if we try to go beyond the end of the stack */ | |
| TEST_F_SIGNAL(map_gcs, stack_overflow, SIGSEGV) | |
| { | |
| size_t cap_index; | |
| cap_index = (variant->stack_size / sizeof(unsigned long)); | |
| unsigned long *orig_gcspr_el0, *pivot_gcspr_el0; | |
| /* Skip over the stack terminator and point at the cap */ | |
| switch (variant->flags & (SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN)) { | |
| case SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN: | |
| cap_index -= 2; | |
| break; | |
| case SHADOW_STACK_SET_TOKEN: | |
| cap_index -= 1; | |
| break; | |
| case SHADOW_STACK_SET_MARKER: | |
| case 0: | |
| /* No cap, no test but we need to SEGV to avoid a false fail */ | |
| orig_gcspr_el0 = get_gcspr(); | |
| *orig_gcspr_el0 = 0; | |
| return; | |
| } | |
| pivot_gcspr_el0 = &self->stack[cap_index]; | |
| /* Pivot to the new GCS */ | |
| ksft_print_msg("Pivoting to %p from %p, target has value 0x%lx\n", | |
| pivot_gcspr_el0, get_gcspr(), | |
| *pivot_gcspr_el0); | |
| gcsss1(pivot_gcspr_el0); | |
| orig_gcspr_el0 = gcsss2(); | |
| ksft_print_msg("Pivoted to %p from %p, target has value 0x%lx\n", | |
| pivot_gcspr_el0, orig_gcspr_el0, | |
| *pivot_gcspr_el0); | |
| ksft_print_msg("Pivoted, GCSPR_EL0 now %p\n", get_gcspr()); | |
| /* New GCS must be in the new buffer */ | |
| ASSERT_TRUE((unsigned long)get_gcspr() > (unsigned long)self->stack); | |
| ASSERT_TRUE((unsigned long)get_gcspr() <= | |
| (unsigned long)self->stack + variant->stack_size); | |
| /* Now try to recurse, we should fault doing this. */ | |
| ksft_print_msg("Recursing %zu levels...\n", cap_index + 1); | |
| gcs_recurse(cap_index + 1); | |
| ksft_print_msg("...done\n"); | |
| /* Clean up properly to try to guard against spurious passes. */ | |
| gcsss1(orig_gcspr_el0); | |
| pivot_gcspr_el0 = gcsss2(); | |
| ksft_print_msg("Pivoted back to GCSPR_EL0 0x%p\n", get_gcspr()); | |
| } | |
| FIXTURE(map_invalid_gcs) | |
| { | |
| }; | |
| FIXTURE_VARIANT(map_invalid_gcs) | |
| { | |
| size_t stack_size; | |
| }; | |
| FIXTURE_SETUP(map_invalid_gcs) | |
| { | |
| } | |
| FIXTURE_TEARDOWN(map_invalid_gcs) | |
| { | |
| } | |
| /* GCS must be larger than 16 bytes */ | |
| FIXTURE_VARIANT_ADD(map_invalid_gcs, too_small) | |
| { | |
| .stack_size = 8, | |
| }; | |
| /* GCS size must be 16 byte aligned */ | |
| FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_1) { .stack_size = 1024 + 1 }; | |
| FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_2) { .stack_size = 1024 + 2 }; | |
| FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_3) { .stack_size = 1024 + 3 }; | |
| FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_4) { .stack_size = 1024 + 4 }; | |
| FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_5) { .stack_size = 1024 + 5 }; | |
| FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_6) { .stack_size = 1024 + 6 }; | |
| FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_7) { .stack_size = 1024 + 7 }; | |
| TEST_F(map_invalid_gcs, do_map) | |
| { | |
| void *stack; | |
| stack = (void *)syscall(__NR_map_shadow_stack, 0, | |
| variant->stack_size, 0); | |
| ASSERT_TRUE(stack == MAP_FAILED); | |
| if (stack != MAP_FAILED) | |
| munmap(stack, variant->stack_size); | |
| } | |
| FIXTURE(invalid_mprotect) | |
| { | |
| unsigned long *stack; | |
| size_t stack_size; | |
| }; | |
| FIXTURE_VARIANT(invalid_mprotect) | |
| { | |
| unsigned long flags; | |
| }; | |
| FIXTURE_SETUP(invalid_mprotect) | |
| { | |
| self->stack_size = sysconf(_SC_PAGE_SIZE); | |
| self->stack = (void *)syscall(__NR_map_shadow_stack, 0, | |
| self->stack_size, 0); | |
| ASSERT_FALSE(self->stack == MAP_FAILED); | |
| ksft_print_msg("Allocated stack from %p-%p\n", self->stack, | |
| self->stack + self->stack_size); | |
| } | |
| FIXTURE_TEARDOWN(invalid_mprotect) | |
| { | |
| int ret; | |
| if (self->stack != MAP_FAILED) { | |
| ret = munmap(self->stack, self->stack_size); | |
| ASSERT_EQ(ret, 0); | |
| } | |
| } | |
| FIXTURE_VARIANT_ADD(invalid_mprotect, exec) | |
| { | |
| .flags = PROT_EXEC, | |
| }; | |
| TEST_F(invalid_mprotect, do_map) | |
| { | |
| int ret; | |
| ret = mprotect(self->stack, self->stack_size, variant->flags); | |
| ASSERT_EQ(ret, -1); | |
| } | |
| TEST_F(invalid_mprotect, do_map_read) | |
| { | |
| int ret; | |
| ret = mprotect(self->stack, self->stack_size, | |
| variant->flags | PROT_READ); | |
| ASSERT_EQ(ret, -1); | |
| } | |
| int main(int argc, char **argv) | |
| { | |
| unsigned long gcs_mode; | |
| int ret; | |
| if (!(getauxval(AT_HWCAP) & HWCAP_GCS)) | |
| ksft_exit_skip("SKIP GCS not supported\n"); | |
| /* | |
| * Force shadow stacks on, our tests *should* be fine with or | |
| * without libc support and with or without this having ended | |
| * up tagged for GCS and enabled by the dynamic linker. We | |
| * can't use the libc prctl() function since we can't return | |
| * from enabling the stack. | |
| */ | |
| ret = my_syscall2(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &gcs_mode); | |
| if (ret) { | |
| ksft_print_msg("Failed to read GCS state: %d\n", ret); | |
| return EXIT_FAILURE; | |
| } | |
| if (!(gcs_mode & PR_SHADOW_STACK_ENABLE)) { | |
| gcs_mode = PR_SHADOW_STACK_ENABLE; | |
| ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS, | |
| gcs_mode); | |
| if (ret) { | |
| ksft_print_msg("Failed to configure GCS: %d\n", ret); | |
| return EXIT_FAILURE; | |
| } | |
| } | |
| /* Avoid returning in case libc doesn't understand GCS */ | |
| exit(test_harness_run(argc, argv)); | |
| } |