Path: blob/master/tools/testing/selftests/arm64/gcs/libc-gcs.c
26292 views
// SPDX-License-Identifier: GPL-2.0-only1/*2* Copyright (C) 2023 ARM Limited.3*/45#define _GNU_SOURCE67#include <pthread.h>8#include <stdbool.h>910#include <sys/auxv.h>11#include <sys/mman.h>12#include <sys/prctl.h>13#include <sys/ptrace.h>14#include <sys/uio.h>1516#include <asm/hwcap.h>17#include <asm/mman.h>1819#include <linux/compiler.h>2021#include "kselftest_harness.h"2223#include "gcs-util.h"2425#define my_syscall2(num, arg1, arg2) \26({ \27register long _num __asm__ ("x8") = (num); \28register long _arg1 __asm__ ("x0") = (long)(arg1); \29register long _arg2 __asm__ ("x1") = (long)(arg2); \30register long _arg3 __asm__ ("x2") = 0; \31register long _arg4 __asm__ ("x3") = 0; \32register long _arg5 __asm__ ("x4") = 0; \33\34__asm__ volatile ( \35"svc #0\n" \36: "=r"(_arg1) \37: "r"(_arg1), "r"(_arg2), \38"r"(_arg3), "r"(_arg4), \39"r"(_arg5), "r"(_num) \40: "memory", "cc" \41); \42_arg1; \43})4445static noinline void gcs_recurse(int depth)46{47if (depth)48gcs_recurse(depth - 1);4950/* Prevent tail call optimization so we actually recurse */51asm volatile("dsb sy" : : : "memory");52}5354/* Smoke test that a function call and return works*/55TEST(can_call_function)56{57gcs_recurse(0);58}5960static void *gcs_test_thread(void *arg)61{62int ret;63unsigned long mode;6465/*66* Some libcs don't seem to fill unused arguments with 0 but67* the kernel validates this so we supply all 5 arguments.68*/69ret = prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0);70if (ret != 0) {71ksft_print_msg("PR_GET_SHADOW_STACK_STATUS failed: %d\n", ret);72return NULL;73}7475if (!(mode & PR_SHADOW_STACK_ENABLE)) {76ksft_print_msg("GCS not enabled in thread, mode is %lu\n",77mode);78return NULL;79}8081/* Just in case... */82gcs_recurse(0);8384/* Use a non-NULL value to indicate a pass */85return &gcs_test_thread;86}8788/* Verify that if we start a new thread it has GCS enabled */89TEST(gcs_enabled_thread)90{91pthread_t thread;92void *thread_ret;93int ret;9495ret = pthread_create(&thread, NULL, gcs_test_thread, NULL);96ASSERT_TRUE(ret == 0);97if (ret != 0)98return;99100ret = pthread_join(thread, &thread_ret);101ASSERT_TRUE(ret == 0);102if (ret != 0)103return;104105ASSERT_TRUE(thread_ret != NULL);106}107108/* Read the GCS until we find the terminator */109TEST(gcs_find_terminator)110{111unsigned long *gcs, *cur;112113gcs = get_gcspr();114cur = gcs;115while (*cur)116cur++;117118ksft_print_msg("GCS in use from %p-%p\n", gcs, cur);119120/*121* We should have at least whatever called into this test so122* the two pointer should differ.123*/124ASSERT_TRUE(gcs != cur);125}126127/*128* We can access a GCS via ptrace129*130* This could usefully have a fixture but note that each test is131* fork()ed into a new child whcih causes issues. Might be better to132* lift at least some of this out into a separate, non-harness, test133* program.134*/135TEST(ptrace_read_write)136{137pid_t child, pid;138int ret, status;139siginfo_t si;140uint64_t val, rval, gcspr;141struct user_gcs child_gcs;142struct iovec iov, local_iov, remote_iov;143144child = fork();145if (child == -1) {146ksft_print_msg("fork() failed: %d (%s)\n",147errno, strerror(errno));148ASSERT_NE(child, -1);149}150151if (child == 0) {152/*153* In child, make sure there's something on the stack and154* ask to be traced.155*/156gcs_recurse(0);157if (ptrace(PTRACE_TRACEME, -1, NULL, NULL))158ksft_exit_fail_msg("PTRACE_TRACEME %s",159strerror(errno));160161if (raise(SIGSTOP))162ksft_exit_fail_msg("raise(SIGSTOP) %s",163strerror(errno));164165return;166}167168ksft_print_msg("Child: %d\n", child);169170/* Attach to the child */171while (1) {172int sig;173174pid = wait(&status);175if (pid == -1) {176ksft_print_msg("wait() failed: %s",177strerror(errno));178goto error;179}180181/*182* This should never happen but it's hard to flag in183* the framework.184*/185if (pid != child)186continue;187188if (WIFEXITED(status) || WIFSIGNALED(status))189ksft_exit_fail_msg("Child died unexpectedly\n");190191if (!WIFSTOPPED(status))192goto error;193194sig = WSTOPSIG(status);195196if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &si)) {197if (errno == ESRCH) {198ASSERT_NE(errno, ESRCH);199return;200}201202if (errno == EINVAL) {203sig = 0; /* bust group-stop */204goto cont;205}206207ksft_print_msg("PTRACE_GETSIGINFO: %s\n",208strerror(errno));209goto error;210}211212if (sig == SIGSTOP && si.si_code == SI_TKILL &&213si.si_pid == pid)214break;215216cont:217if (ptrace(PTRACE_CONT, pid, NULL, sig)) {218if (errno == ESRCH) {219ASSERT_NE(errno, ESRCH);220return;221}222223ksft_print_msg("PTRACE_CONT: %s\n", strerror(errno));224goto error;225}226}227228/* Where is the child GCS? */229iov.iov_base = &child_gcs;230iov.iov_len = sizeof(child_gcs);231ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_GCS, &iov);232if (ret != 0) {233ksft_print_msg("Failed to read child GCS state: %s (%d)\n",234strerror(errno), errno);235goto error;236}237238/* We should have inherited GCS over fork(), confirm */239if (!(child_gcs.features_enabled & PR_SHADOW_STACK_ENABLE)) {240ASSERT_TRUE(child_gcs.features_enabled &241PR_SHADOW_STACK_ENABLE);242goto error;243}244245gcspr = child_gcs.gcspr_el0;246ksft_print_msg("Child GCSPR 0x%lx, flags %llx, locked %llx\n",247gcspr, child_gcs.features_enabled,248child_gcs.features_locked);249250/* Ideally we'd cross check with the child memory map */251252errno = 0;253val = ptrace(PTRACE_PEEKDATA, child, (void *)gcspr, NULL);254ret = errno;255if (ret != 0)256ksft_print_msg("PTRACE_PEEKDATA failed: %s (%d)\n",257strerror(ret), ret);258EXPECT_EQ(ret, 0);259260/* The child should be in a function, the GCSPR shouldn't be 0 */261EXPECT_NE(val, 0);262263/* Same thing via process_vm_readv() */264local_iov.iov_base = &rval;265local_iov.iov_len = sizeof(rval);266remote_iov.iov_base = (void *)gcspr;267remote_iov.iov_len = sizeof(rval);268ret = process_vm_readv(child, &local_iov, 1, &remote_iov, 1, 0);269if (ret == -1)270ksft_print_msg("process_vm_readv() failed: %s (%d)\n",271strerror(errno), errno);272EXPECT_EQ(ret, sizeof(rval));273EXPECT_EQ(val, rval);274275/* Write data via a peek */276ret = ptrace(PTRACE_POKEDATA, child, (void *)gcspr, NULL);277if (ret == -1)278ksft_print_msg("PTRACE_POKEDATA failed: %s (%d)\n",279strerror(errno), errno);280EXPECT_EQ(ret, 0);281EXPECT_EQ(0, ptrace(PTRACE_PEEKDATA, child, (void *)gcspr, NULL));282283/* Restore what we had before */284ret = ptrace(PTRACE_POKEDATA, child, (void *)gcspr, val);285if (ret == -1)286ksft_print_msg("PTRACE_POKEDATA failed: %s (%d)\n",287strerror(errno), errno);288EXPECT_EQ(ret, 0);289EXPECT_EQ(val, ptrace(PTRACE_PEEKDATA, child, (void *)gcspr, NULL));290291/* That's all, folks */292kill(child, SIGKILL);293return;294295error:296kill(child, SIGKILL);297ASSERT_FALSE(true);298}299300FIXTURE(map_gcs)301{302unsigned long *stack;303};304305FIXTURE_VARIANT(map_gcs)306{307size_t stack_size;308unsigned long flags;309};310311FIXTURE_VARIANT_ADD(map_gcs, s2k_cap_marker)312{313.stack_size = 2 * 1024,314.flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN,315};316317FIXTURE_VARIANT_ADD(map_gcs, s2k_cap)318{319.stack_size = 2 * 1024,320.flags = SHADOW_STACK_SET_TOKEN,321};322323FIXTURE_VARIANT_ADD(map_gcs, s2k_marker)324{325.stack_size = 2 * 1024,326.flags = SHADOW_STACK_SET_MARKER,327};328329FIXTURE_VARIANT_ADD(map_gcs, s2k)330{331.stack_size = 2 * 1024,332.flags = 0,333};334335FIXTURE_VARIANT_ADD(map_gcs, s4k_cap_marker)336{337.stack_size = 4 * 1024,338.flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN,339};340341FIXTURE_VARIANT_ADD(map_gcs, s4k_cap)342{343.stack_size = 4 * 1024,344.flags = SHADOW_STACK_SET_TOKEN,345};346347FIXTURE_VARIANT_ADD(map_gcs, s3k_marker)348{349.stack_size = 4 * 1024,350.flags = SHADOW_STACK_SET_MARKER,351};352353FIXTURE_VARIANT_ADD(map_gcs, s4k)354{355.stack_size = 4 * 1024,356.flags = 0,357};358359FIXTURE_VARIANT_ADD(map_gcs, s16k_cap_marker)360{361.stack_size = 16 * 1024,362.flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN,363};364365FIXTURE_VARIANT_ADD(map_gcs, s16k_cap)366{367.stack_size = 16 * 1024,368.flags = SHADOW_STACK_SET_TOKEN,369};370371FIXTURE_VARIANT_ADD(map_gcs, s16k_marker)372{373.stack_size = 16 * 1024,374.flags = SHADOW_STACK_SET_MARKER,375};376377FIXTURE_VARIANT_ADD(map_gcs, s16k)378{379.stack_size = 16 * 1024,380.flags = 0,381};382383FIXTURE_VARIANT_ADD(map_gcs, s64k_cap_marker)384{385.stack_size = 64 * 1024,386.flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN,387};388389FIXTURE_VARIANT_ADD(map_gcs, s64k_cap)390{391.stack_size = 64 * 1024,392.flags = SHADOW_STACK_SET_TOKEN,393};394395FIXTURE_VARIANT_ADD(map_gcs, s64k_marker)396{397.stack_size = 64 * 1024,398.flags = SHADOW_STACK_SET_MARKER,399};400401FIXTURE_VARIANT_ADD(map_gcs, s64k)402{403.stack_size = 64 * 1024,404.flags = 0,405};406407FIXTURE_VARIANT_ADD(map_gcs, s128k_cap_marker)408{409.stack_size = 128 * 1024,410.flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN,411};412413FIXTURE_VARIANT_ADD(map_gcs, s128k_cap)414{415.stack_size = 128 * 1024,416.flags = SHADOW_STACK_SET_TOKEN,417};418419FIXTURE_VARIANT_ADD(map_gcs, s128k_marker)420{421.stack_size = 128 * 1024,422.flags = SHADOW_STACK_SET_MARKER,423};424425FIXTURE_VARIANT_ADD(map_gcs, s128k)426{427.stack_size = 128 * 1024,428.flags = 0,429};430431FIXTURE_SETUP(map_gcs)432{433self->stack = (void *)syscall(__NR_map_shadow_stack, 0,434variant->stack_size,435variant->flags);436ASSERT_FALSE(self->stack == MAP_FAILED);437ksft_print_msg("Allocated stack from %p-%p\n", self->stack,438self->stack + variant->stack_size);439}440441FIXTURE_TEARDOWN(map_gcs)442{443int ret;444445if (self->stack != MAP_FAILED) {446ret = munmap(self->stack, variant->stack_size);447ASSERT_EQ(ret, 0);448}449}450451/* The stack has a cap token */452TEST_F(map_gcs, stack_capped)453{454unsigned long *stack = self->stack;455size_t cap_index;456457cap_index = (variant->stack_size / sizeof(unsigned long));458459switch (variant->flags & (SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN)) {460case SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN:461cap_index -= 2;462break;463case SHADOW_STACK_SET_TOKEN:464cap_index -= 1;465break;466case SHADOW_STACK_SET_MARKER:467case 0:468/* No cap, no test */469return;470}471472ASSERT_EQ(stack[cap_index], GCS_CAP(&stack[cap_index]));473}474475/* The top of the stack is 0 */476TEST_F(map_gcs, stack_terminated)477{478unsigned long *stack = self->stack;479size_t term_index;480481if (!(variant->flags & SHADOW_STACK_SET_MARKER))482return;483484term_index = (variant->stack_size / sizeof(unsigned long)) - 1;485486ASSERT_EQ(stack[term_index], 0);487}488489/* Writes should fault */490TEST_F_SIGNAL(map_gcs, not_writeable, SIGSEGV)491{492self->stack[0] = 0;493}494495/* Put it all together, we can safely switch to and from the stack */496TEST_F(map_gcs, stack_switch)497{498size_t cap_index;499cap_index = (variant->stack_size / sizeof(unsigned long));500unsigned long *orig_gcspr_el0, *pivot_gcspr_el0;501502/* Skip over the stack terminator and point at the cap */503switch (variant->flags & (SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN)) {504case SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN:505cap_index -= 2;506break;507case SHADOW_STACK_SET_TOKEN:508cap_index -= 1;509break;510case SHADOW_STACK_SET_MARKER:511case 0:512/* No cap, no test */513return;514}515pivot_gcspr_el0 = &self->stack[cap_index];516517/* Pivot to the new GCS */518ksft_print_msg("Pivoting to %p from %p, target has value 0x%lx\n",519pivot_gcspr_el0, get_gcspr(),520*pivot_gcspr_el0);521gcsss1(pivot_gcspr_el0);522orig_gcspr_el0 = gcsss2();523ksft_print_msg("Pivoted to %p from %p, target has value 0x%lx\n",524get_gcspr(), orig_gcspr_el0,525*pivot_gcspr_el0);526527ksft_print_msg("Pivoted, GCSPR_EL0 now %p\n", get_gcspr());528529/* New GCS must be in the new buffer */530ASSERT_TRUE((unsigned long)get_gcspr() > (unsigned long)self->stack);531ASSERT_TRUE((unsigned long)get_gcspr() <=532(unsigned long)self->stack + variant->stack_size);533534/* We should be able to use all but 2 slots of the new stack */535ksft_print_msg("Recursing %zu levels\n", cap_index - 1);536gcs_recurse(cap_index - 1);537538/* Pivot back to the original GCS */539gcsss1(orig_gcspr_el0);540pivot_gcspr_el0 = gcsss2();541542gcs_recurse(0);543ksft_print_msg("Pivoted back to GCSPR_EL0 0x%p\n", get_gcspr());544}545546/* We fault if we try to go beyond the end of the stack */547TEST_F_SIGNAL(map_gcs, stack_overflow, SIGSEGV)548{549size_t cap_index;550cap_index = (variant->stack_size / sizeof(unsigned long));551unsigned long *orig_gcspr_el0, *pivot_gcspr_el0;552553/* Skip over the stack terminator and point at the cap */554switch (variant->flags & (SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN)) {555case SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN:556cap_index -= 2;557break;558case SHADOW_STACK_SET_TOKEN:559cap_index -= 1;560break;561case SHADOW_STACK_SET_MARKER:562case 0:563/* No cap, no test but we need to SEGV to avoid a false fail */564orig_gcspr_el0 = get_gcspr();565*orig_gcspr_el0 = 0;566return;567}568pivot_gcspr_el0 = &self->stack[cap_index];569570/* Pivot to the new GCS */571ksft_print_msg("Pivoting to %p from %p, target has value 0x%lx\n",572pivot_gcspr_el0, get_gcspr(),573*pivot_gcspr_el0);574gcsss1(pivot_gcspr_el0);575orig_gcspr_el0 = gcsss2();576ksft_print_msg("Pivoted to %p from %p, target has value 0x%lx\n",577pivot_gcspr_el0, orig_gcspr_el0,578*pivot_gcspr_el0);579580ksft_print_msg("Pivoted, GCSPR_EL0 now %p\n", get_gcspr());581582/* New GCS must be in the new buffer */583ASSERT_TRUE((unsigned long)get_gcspr() > (unsigned long)self->stack);584ASSERT_TRUE((unsigned long)get_gcspr() <=585(unsigned long)self->stack + variant->stack_size);586587/* Now try to recurse, we should fault doing this. */588ksft_print_msg("Recursing %zu levels...\n", cap_index + 1);589gcs_recurse(cap_index + 1);590ksft_print_msg("...done\n");591592/* Clean up properly to try to guard against spurious passes. */593gcsss1(orig_gcspr_el0);594pivot_gcspr_el0 = gcsss2();595ksft_print_msg("Pivoted back to GCSPR_EL0 0x%p\n", get_gcspr());596}597598FIXTURE(map_invalid_gcs)599{600};601602FIXTURE_VARIANT(map_invalid_gcs)603{604size_t stack_size;605};606607FIXTURE_SETUP(map_invalid_gcs)608{609}610611FIXTURE_TEARDOWN(map_invalid_gcs)612{613}614615/* GCS must be larger than 16 bytes */616FIXTURE_VARIANT_ADD(map_invalid_gcs, too_small)617{618.stack_size = 8,619};620621/* GCS size must be 16 byte aligned */622FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_1) { .stack_size = 1024 + 1 };623FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_2) { .stack_size = 1024 + 2 };624FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_3) { .stack_size = 1024 + 3 };625FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_4) { .stack_size = 1024 + 4 };626FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_5) { .stack_size = 1024 + 5 };627FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_6) { .stack_size = 1024 + 6 };628FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_7) { .stack_size = 1024 + 7 };629630TEST_F(map_invalid_gcs, do_map)631{632void *stack;633634stack = (void *)syscall(__NR_map_shadow_stack, 0,635variant->stack_size, 0);636ASSERT_TRUE(stack == MAP_FAILED);637if (stack != MAP_FAILED)638munmap(stack, variant->stack_size);639}640641FIXTURE(invalid_mprotect)642{643unsigned long *stack;644size_t stack_size;645};646647FIXTURE_VARIANT(invalid_mprotect)648{649unsigned long flags;650};651652FIXTURE_SETUP(invalid_mprotect)653{654self->stack_size = sysconf(_SC_PAGE_SIZE);655self->stack = (void *)syscall(__NR_map_shadow_stack, 0,656self->stack_size, 0);657ASSERT_FALSE(self->stack == MAP_FAILED);658ksft_print_msg("Allocated stack from %p-%p\n", self->stack,659self->stack + self->stack_size);660}661662FIXTURE_TEARDOWN(invalid_mprotect)663{664int ret;665666if (self->stack != MAP_FAILED) {667ret = munmap(self->stack, self->stack_size);668ASSERT_EQ(ret, 0);669}670}671672FIXTURE_VARIANT_ADD(invalid_mprotect, exec)673{674.flags = PROT_EXEC,675};676677TEST_F(invalid_mprotect, do_map)678{679int ret;680681ret = mprotect(self->stack, self->stack_size, variant->flags);682ASSERT_EQ(ret, -1);683}684685TEST_F(invalid_mprotect, do_map_read)686{687int ret;688689ret = mprotect(self->stack, self->stack_size,690variant->flags | PROT_READ);691ASSERT_EQ(ret, -1);692}693694int main(int argc, char **argv)695{696unsigned long gcs_mode;697int ret;698699if (!(getauxval(AT_HWCAP) & HWCAP_GCS))700ksft_exit_skip("SKIP GCS not supported\n");701702/*703* Force shadow stacks on, our tests *should* be fine with or704* without libc support and with or without this having ended705* up tagged for GCS and enabled by the dynamic linker. We706* can't use the libc prctl() function since we can't return707* from enabling the stack.708*/709ret = my_syscall2(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &gcs_mode);710if (ret) {711ksft_print_msg("Failed to read GCS state: %d\n", ret);712return EXIT_FAILURE;713}714715if (!(gcs_mode & PR_SHADOW_STACK_ENABLE)) {716gcs_mode = PR_SHADOW_STACK_ENABLE;717ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,718gcs_mode);719if (ret) {720ksft_print_msg("Failed to configure GCS: %d\n", ret);721return EXIT_FAILURE;722}723}724725/* Avoid returning in case libc doesn't understand GCS */726exit(test_harness_run(argc, argv));727}728729730