Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Mark Brown | 1303 | 100.00% | 1 | 100.00% |
Total | 1303 | 1 |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2023 ARM Limited. */ #include <limits.h> #include <stdbool.h> #include <linux/prctl.h> #include <sys/mman.h> #include <asm/mman.h> #include <linux/sched.h> #include "kselftest.h" #include "gcs-util.h" /* nolibc doesn't have sysconf(), just hard code the maximum */ static size_t page_size = 65536; static __attribute__((noinline)) void valid_gcs_function(void) { /* Do something the compiler can't optimise out */ my_syscall1(__NR_prctl, PR_SVE_GET_VL); } static inline int gcs_set_status(unsigned long mode) { bool enabling = mode & PR_SHADOW_STACK_ENABLE; int ret; unsigned long new_mode; /* * The prctl takes 1 argument but we need to ensure that the * other 3 values passed in registers to the syscall are zero * since the kernel validates them. */ ret = my_syscall5(__NR_prctl, PR_SET_SHADOW_STACK_STATUS, mode, 0, 0, 0); if (ret == 0) { ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &new_mode, 0, 0, 0); if (ret == 0) { if (new_mode != mode) { ksft_print_msg("Mode set to %lx not %lx\n", new_mode, mode); ret = -EINVAL; } } else { ksft_print_msg("Failed to validate mode: %d\n", ret); } if (enabling != chkfeat_gcs()) { ksft_print_msg("%senabled by prctl but %senabled in CHKFEAT\n", enabling ? "" : "not ", chkfeat_gcs() ? "" : "not "); ret = -EINVAL; } } return ret; } /* Try to read the status */ static bool read_status(void) { unsigned long state; int ret; ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &state, 0, 0, 0); if (ret != 0) { ksft_print_msg("Failed to read state: %d\n", ret); return false; } return state & PR_SHADOW_STACK_ENABLE; } /* Just a straight enable */ static bool base_enable(void) { int ret; ret = gcs_set_status(PR_SHADOW_STACK_ENABLE); if (ret) { ksft_print_msg("PR_SHADOW_STACK_ENABLE failed %d\n", ret); return false; } return true; } /* Check we can read GCSPR_EL0 when GCS is enabled */ static bool read_gcspr_el0(void) { unsigned long *gcspr_el0; ksft_print_msg("GET GCSPR\n"); gcspr_el0 = get_gcspr(); ksft_print_msg("GCSPR_EL0 is %p\n", gcspr_el0); return true; } /* Also allow writes to stack */ static bool enable_writeable(void) { int ret; ret = gcs_set_status(PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_WRITE); if (ret) { ksft_print_msg("PR_SHADOW_STACK_ENABLE writeable failed: %d\n", ret); return false; } ret = gcs_set_status(PR_SHADOW_STACK_ENABLE); if (ret) { ksft_print_msg("failed to restore plain enable %d\n", ret); return false; } return true; } /* Also allow writes to stack */ static bool enable_push_pop(void) { int ret; ret = gcs_set_status(PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_PUSH); if (ret) { ksft_print_msg("PR_SHADOW_STACK_ENABLE with push failed: %d\n", ret); return false; } ret = gcs_set_status(PR_SHADOW_STACK_ENABLE); if (ret) { ksft_print_msg("failed to restore plain enable %d\n", ret); return false; } return true; } /* Enable GCS and allow everything */ static bool enable_all(void) { int ret; ret = gcs_set_status(PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_PUSH | PR_SHADOW_STACK_WRITE); if (ret) { ksft_print_msg("PR_SHADOW_STACK_ENABLE with everything failed: %d\n", ret); return false; } ret = gcs_set_status(PR_SHADOW_STACK_ENABLE); if (ret) { ksft_print_msg("failed to restore plain enable %d\n", ret); return false; } return true; } static bool enable_invalid(void) { int ret = gcs_set_status(ULONG_MAX); if (ret == 0) { ksft_print_msg("GCS_SET_STATUS %lx succeeded\n", ULONG_MAX); return false; } return true; } /* Map a GCS */ static bool map_guarded_stack(void) { int ret; uint64_t *buf; uint64_t expected_cap; int elem; bool pass = true; buf = (void *)my_syscall3(__NR_map_shadow_stack, 0, page_size, SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN); if (buf == MAP_FAILED) { ksft_print_msg("Failed to map %lu byte GCS: %d\n", page_size, errno); return false; } ksft_print_msg("Mapped GCS at %p-%p\n", buf, (void *)((uint64_t)buf + page_size)); /* The top of the newly allocated region should be 0 */ elem = (page_size / sizeof(uint64_t)) - 1; if (buf[elem]) { ksft_print_msg("Last entry is 0x%llx not 0x0\n", buf[elem]); pass = false; } /* Then a valid cap token */ elem--; expected_cap = ((uint64_t)buf + page_size - 16); expected_cap &= GCS_CAP_ADDR_MASK; expected_cap |= GCS_CAP_VALID_TOKEN; if (buf[elem] != expected_cap) { ksft_print_msg("Cap entry is 0x%llx not 0x%llx\n", buf[elem], expected_cap); pass = false; } ksft_print_msg("cap token is 0x%llx\n", buf[elem]); /* The rest should be zeros */ for (elem = 0; elem < page_size / sizeof(uint64_t) - 2; elem++) { if (!buf[elem]) continue; ksft_print_msg("GCS slot %d is 0x%llx not 0x0\n", elem, buf[elem]); pass = false; } ret = munmap(buf, page_size); if (ret != 0) { ksft_print_msg("Failed to unmap %ld byte GCS: %d\n", page_size, errno); pass = false; } return pass; } /* A fork()ed process can run */ static bool test_fork(void) { unsigned long child_mode; int ret, status; pid_t pid; bool pass = true; pid = fork(); if (pid == -1) { ksft_print_msg("fork() failed: %d\n", errno); pass = false; goto out; } if (pid == 0) { /* In child, make sure we can call a function, read * the GCS pointer and status and then exit */ valid_gcs_function(); get_gcspr(); ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &child_mode, 0, 0, 0); if (ret == 0 && !(child_mode & PR_SHADOW_STACK_ENABLE)) { ksft_print_msg("GCS not enabled in child\n"); ret = -EINVAL; } exit(ret); } /* * In parent, check we can still do function calls then block * for the child. */ valid_gcs_function(); ksft_print_msg("Waiting for child %d\n", pid); ret = waitpid(pid, &status, 0); if (ret == -1) { ksft_print_msg("Failed to wait for child: %d\n", errno); return false; } if (!WIFEXITED(status)) { ksft_print_msg("Child exited due to signal %d\n", WTERMSIG(status)); pass = false; } else { if (WEXITSTATUS(status)) { ksft_print_msg("Child exited with status %d\n", WEXITSTATUS(status)); pass = false; } } out: return pass; } typedef bool (*gcs_test)(void); static struct { char *name; gcs_test test; bool needs_enable; } tests[] = { { "read_status", read_status }, { "base_enable", base_enable, true }, { "read_gcspr_el0", read_gcspr_el0 }, { "enable_writeable", enable_writeable, true }, { "enable_push_pop", enable_push_pop, true }, { "enable_all", enable_all, true }, { "enable_invalid", enable_invalid, true }, { "map_guarded_stack", map_guarded_stack }, { "fork", test_fork }, }; int main(void) { int i, ret; unsigned long gcs_mode; ksft_print_header(); /* * We don't have getauxval() with nolibc so treat a failure to * read GCS state as a lack of support and skip. */ ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &gcs_mode, 0, 0, 0); if (ret != 0) ksft_exit_skip("Failed to read GCS state: %d\n", ret); if (!(gcs_mode & PR_SHADOW_STACK_ENABLE)) { gcs_mode = PR_SHADOW_STACK_ENABLE; ret = my_syscall5(__NR_prctl, PR_SET_SHADOW_STACK_STATUS, gcs_mode, 0, 0, 0); if (ret != 0) ksft_exit_fail_msg("Failed to enable GCS: %d\n", ret); } ksft_set_plan(ARRAY_SIZE(tests)); for (i = 0; i < ARRAY_SIZE(tests); i++) { ksft_test_result((*tests[i].test)(), "%s\n", tests[i].name); } /* One last test: disable GCS, we can do this one time */ my_syscall5(__NR_prctl, PR_SET_SHADOW_STACK_STATUS, 0, 0, 0, 0); if (ret != 0) ksft_print_msg("Failed to disable GCS: %d\n", ret); ksft_finished(); return 0; }
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1