13d37d430SMark Brown // SPDX-License-Identifier: GPL-2.0-only
23d37d430SMark Brown /*
33d37d430SMark Brown * Copyright (C) 2023 ARM Limited.
43d37d430SMark Brown */
53d37d430SMark Brown
63d37d430SMark Brown #include <limits.h>
73d37d430SMark Brown #include <stdbool.h>
83d37d430SMark Brown
93d37d430SMark Brown #include <linux/prctl.h>
103d37d430SMark Brown
113d37d430SMark Brown #include <sys/mman.h>
123d37d430SMark Brown #include <asm/mman.h>
133d37d430SMark Brown #include <linux/sched.h>
143d37d430SMark Brown
153d37d430SMark Brown #include "kselftest.h"
163d37d430SMark Brown #include "gcs-util.h"
173d37d430SMark Brown
183d37d430SMark Brown /* nolibc doesn't have sysconf(), just hard code the maximum */
193d37d430SMark Brown static size_t page_size = 65536;
203d37d430SMark Brown
valid_gcs_function(void)213d37d430SMark Brown static __attribute__((noinline)) void valid_gcs_function(void)
223d37d430SMark Brown {
233d37d430SMark Brown /* Do something the compiler can't optimise out */
243d37d430SMark Brown my_syscall1(__NR_prctl, PR_SVE_GET_VL);
253d37d430SMark Brown }
263d37d430SMark Brown
gcs_set_status(unsigned long mode)273d37d430SMark Brown static inline int gcs_set_status(unsigned long mode)
283d37d430SMark Brown {
293d37d430SMark Brown bool enabling = mode & PR_SHADOW_STACK_ENABLE;
303d37d430SMark Brown int ret;
313d37d430SMark Brown unsigned long new_mode;
323d37d430SMark Brown
333d37d430SMark Brown /*
343d37d430SMark Brown * The prctl takes 1 argument but we need to ensure that the
353d37d430SMark Brown * other 3 values passed in registers to the syscall are zero
363d37d430SMark Brown * since the kernel validates them.
373d37d430SMark Brown */
383d37d430SMark Brown ret = my_syscall5(__NR_prctl, PR_SET_SHADOW_STACK_STATUS, mode,
393d37d430SMark Brown 0, 0, 0);
403d37d430SMark Brown
413d37d430SMark Brown if (ret == 0) {
423d37d430SMark Brown ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS,
433d37d430SMark Brown &new_mode, 0, 0, 0);
443d37d430SMark Brown if (ret == 0) {
453d37d430SMark Brown if (new_mode != mode) {
463d37d430SMark Brown ksft_print_msg("Mode set to %lx not %lx\n",
473d37d430SMark Brown new_mode, mode);
483d37d430SMark Brown ret = -EINVAL;
493d37d430SMark Brown }
503d37d430SMark Brown } else {
513d37d430SMark Brown ksft_print_msg("Failed to validate mode: %d\n", ret);
523d37d430SMark Brown }
533d37d430SMark Brown
543d37d430SMark Brown if (enabling != chkfeat_gcs()) {
553d37d430SMark Brown ksft_print_msg("%senabled by prctl but %senabled in CHKFEAT\n",
563d37d430SMark Brown enabling ? "" : "not ",
573d37d430SMark Brown chkfeat_gcs() ? "" : "not ");
583d37d430SMark Brown ret = -EINVAL;
593d37d430SMark Brown }
603d37d430SMark Brown }
613d37d430SMark Brown
623d37d430SMark Brown return ret;
633d37d430SMark Brown }
643d37d430SMark Brown
653d37d430SMark Brown /* Try to read the status */
read_status(void)663d37d430SMark Brown static bool read_status(void)
673d37d430SMark Brown {
683d37d430SMark Brown unsigned long state;
693d37d430SMark Brown int ret;
703d37d430SMark Brown
713d37d430SMark Brown ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS,
723d37d430SMark Brown &state, 0, 0, 0);
733d37d430SMark Brown if (ret != 0) {
743d37d430SMark Brown ksft_print_msg("Failed to read state: %d\n", ret);
753d37d430SMark Brown return false;
763d37d430SMark Brown }
773d37d430SMark Brown
783d37d430SMark Brown return state & PR_SHADOW_STACK_ENABLE;
793d37d430SMark Brown }
803d37d430SMark Brown
813d37d430SMark Brown /* Just a straight enable */
base_enable(void)823d37d430SMark Brown static bool base_enable(void)
833d37d430SMark Brown {
843d37d430SMark Brown int ret;
853d37d430SMark Brown
863d37d430SMark Brown ret = gcs_set_status(PR_SHADOW_STACK_ENABLE);
873d37d430SMark Brown if (ret) {
883d37d430SMark Brown ksft_print_msg("PR_SHADOW_STACK_ENABLE failed %d\n", ret);
893d37d430SMark Brown return false;
903d37d430SMark Brown }
913d37d430SMark Brown
923d37d430SMark Brown return true;
933d37d430SMark Brown }
943d37d430SMark Brown
953d37d430SMark Brown /* Check we can read GCSPR_EL0 when GCS is enabled */
read_gcspr_el0(void)963d37d430SMark Brown static bool read_gcspr_el0(void)
973d37d430SMark Brown {
983d37d430SMark Brown unsigned long *gcspr_el0;
993d37d430SMark Brown
1003d37d430SMark Brown ksft_print_msg("GET GCSPR\n");
1013d37d430SMark Brown gcspr_el0 = get_gcspr();
1023d37d430SMark Brown ksft_print_msg("GCSPR_EL0 is %p\n", gcspr_el0);
1033d37d430SMark Brown
1043d37d430SMark Brown return true;
1053d37d430SMark Brown }
1063d37d430SMark Brown
1073d37d430SMark Brown /* Also allow writes to stack */
enable_writeable(void)1083d37d430SMark Brown static bool enable_writeable(void)
1093d37d430SMark Brown {
1103d37d430SMark Brown int ret;
1113d37d430SMark Brown
1123d37d430SMark Brown ret = gcs_set_status(PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_WRITE);
1133d37d430SMark Brown if (ret) {
1143d37d430SMark Brown ksft_print_msg("PR_SHADOW_STACK_ENABLE writeable failed: %d\n", ret);
1153d37d430SMark Brown return false;
1163d37d430SMark Brown }
1173d37d430SMark Brown
1183d37d430SMark Brown ret = gcs_set_status(PR_SHADOW_STACK_ENABLE);
1193d37d430SMark Brown if (ret) {
1203d37d430SMark Brown ksft_print_msg("failed to restore plain enable %d\n", ret);
1213d37d430SMark Brown return false;
1223d37d430SMark Brown }
1233d37d430SMark Brown
1243d37d430SMark Brown return true;
1253d37d430SMark Brown }
1263d37d430SMark Brown
1273d37d430SMark Brown /* Also allow writes to stack */
enable_push_pop(void)1283d37d430SMark Brown static bool enable_push_pop(void)
1293d37d430SMark Brown {
1303d37d430SMark Brown int ret;
1313d37d430SMark Brown
1323d37d430SMark Brown ret = gcs_set_status(PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_PUSH);
1333d37d430SMark Brown if (ret) {
1343d37d430SMark Brown ksft_print_msg("PR_SHADOW_STACK_ENABLE with push failed: %d\n",
1353d37d430SMark Brown ret);
1363d37d430SMark Brown return false;
1373d37d430SMark Brown }
1383d37d430SMark Brown
1393d37d430SMark Brown ret = gcs_set_status(PR_SHADOW_STACK_ENABLE);
1403d37d430SMark Brown if (ret) {
1413d37d430SMark Brown ksft_print_msg("failed to restore plain enable %d\n", ret);
1423d37d430SMark Brown return false;
1433d37d430SMark Brown }
1443d37d430SMark Brown
1453d37d430SMark Brown return true;
1463d37d430SMark Brown }
1473d37d430SMark Brown
1483d37d430SMark Brown /* Enable GCS and allow everything */
enable_all(void)1493d37d430SMark Brown static bool enable_all(void)
1503d37d430SMark Brown {
1513d37d430SMark Brown int ret;
1523d37d430SMark Brown
1533d37d430SMark Brown ret = gcs_set_status(PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_PUSH |
1543d37d430SMark Brown PR_SHADOW_STACK_WRITE);
1553d37d430SMark Brown if (ret) {
1563d37d430SMark Brown ksft_print_msg("PR_SHADOW_STACK_ENABLE with everything failed: %d\n",
1573d37d430SMark Brown ret);
1583d37d430SMark Brown return false;
1593d37d430SMark Brown }
1603d37d430SMark Brown
1613d37d430SMark Brown ret = gcs_set_status(PR_SHADOW_STACK_ENABLE);
1623d37d430SMark Brown if (ret) {
1633d37d430SMark Brown ksft_print_msg("failed to restore plain enable %d\n", ret);
1643d37d430SMark Brown return false;
1653d37d430SMark Brown }
1663d37d430SMark Brown
1673d37d430SMark Brown return true;
1683d37d430SMark Brown }
1693d37d430SMark Brown
enable_invalid(void)1703d37d430SMark Brown static bool enable_invalid(void)
1713d37d430SMark Brown {
1723d37d430SMark Brown int ret = gcs_set_status(ULONG_MAX);
1733d37d430SMark Brown if (ret == 0) {
1743d37d430SMark Brown ksft_print_msg("GCS_SET_STATUS %lx succeeded\n", ULONG_MAX);
1753d37d430SMark Brown return false;
1763d37d430SMark Brown }
1773d37d430SMark Brown
1783d37d430SMark Brown return true;
1793d37d430SMark Brown }
1803d37d430SMark Brown
1813d37d430SMark Brown /* Map a GCS */
map_guarded_stack(void)1823d37d430SMark Brown static bool map_guarded_stack(void)
1833d37d430SMark Brown {
1843d37d430SMark Brown int ret;
1853d37d430SMark Brown uint64_t *buf;
1863d37d430SMark Brown uint64_t expected_cap;
1873d37d430SMark Brown int elem;
1883d37d430SMark Brown bool pass = true;
1893d37d430SMark Brown
1903d37d430SMark Brown buf = (void *)my_syscall3(__NR_map_shadow_stack, 0, page_size,
1913d37d430SMark Brown SHADOW_STACK_SET_MARKER |
1923d37d430SMark Brown SHADOW_STACK_SET_TOKEN);
1933d37d430SMark Brown if (buf == MAP_FAILED) {
1943d37d430SMark Brown ksft_print_msg("Failed to map %lu byte GCS: %d\n",
1953d37d430SMark Brown page_size, errno);
1963d37d430SMark Brown return false;
1973d37d430SMark Brown }
1983d37d430SMark Brown ksft_print_msg("Mapped GCS at %p-%p\n", buf,
1993d37d430SMark Brown (void *)((uint64_t)buf + page_size));
2003d37d430SMark Brown
2013d37d430SMark Brown /* The top of the newly allocated region should be 0 */
2023d37d430SMark Brown elem = (page_size / sizeof(uint64_t)) - 1;
2033d37d430SMark Brown if (buf[elem]) {
2043d37d430SMark Brown ksft_print_msg("Last entry is 0x%llx not 0x0\n", buf[elem]);
2053d37d430SMark Brown pass = false;
2063d37d430SMark Brown }
2073d37d430SMark Brown
2083d37d430SMark Brown /* Then a valid cap token */
2093d37d430SMark Brown elem--;
2103d37d430SMark Brown expected_cap = ((uint64_t)buf + page_size - 16);
2113d37d430SMark Brown expected_cap &= GCS_CAP_ADDR_MASK;
2123d37d430SMark Brown expected_cap |= GCS_CAP_VALID_TOKEN;
2133d37d430SMark Brown if (buf[elem] != expected_cap) {
2143d37d430SMark Brown ksft_print_msg("Cap entry is 0x%llx not 0x%llx\n",
2153d37d430SMark Brown buf[elem], expected_cap);
2163d37d430SMark Brown pass = false;
2173d37d430SMark Brown }
2183d37d430SMark Brown ksft_print_msg("cap token is 0x%llx\n", buf[elem]);
2193d37d430SMark Brown
2203d37d430SMark Brown /* The rest should be zeros */
2213d37d430SMark Brown for (elem = 0; elem < page_size / sizeof(uint64_t) - 2; elem++) {
2223d37d430SMark Brown if (!buf[elem])
2233d37d430SMark Brown continue;
2243d37d430SMark Brown ksft_print_msg("GCS slot %d is 0x%llx not 0x0\n",
2253d37d430SMark Brown elem, buf[elem]);
2263d37d430SMark Brown pass = false;
2273d37d430SMark Brown }
2283d37d430SMark Brown
2293d37d430SMark Brown ret = munmap(buf, page_size);
2303d37d430SMark Brown if (ret != 0) {
2313d37d430SMark Brown ksft_print_msg("Failed to unmap %ld byte GCS: %d\n",
2323d37d430SMark Brown page_size, errno);
2333d37d430SMark Brown pass = false;
2343d37d430SMark Brown }
2353d37d430SMark Brown
2363d37d430SMark Brown return pass;
2373d37d430SMark Brown }
2383d37d430SMark Brown
2393d37d430SMark Brown /* A fork()ed process can run */
test_fork(void)2403d37d430SMark Brown static bool test_fork(void)
2413d37d430SMark Brown {
2423d37d430SMark Brown unsigned long child_mode;
2433d37d430SMark Brown int ret, status;
2443d37d430SMark Brown pid_t pid;
2453d37d430SMark Brown bool pass = true;
2463d37d430SMark Brown
2473d37d430SMark Brown pid = fork();
2483d37d430SMark Brown if (pid == -1) {
2493d37d430SMark Brown ksft_print_msg("fork() failed: %d\n", errno);
2503d37d430SMark Brown pass = false;
2513d37d430SMark Brown goto out;
2523d37d430SMark Brown }
2533d37d430SMark Brown if (pid == 0) {
2543d37d430SMark Brown /* In child, make sure we can call a function, read
2553d37d430SMark Brown * the GCS pointer and status and then exit */
2563d37d430SMark Brown valid_gcs_function();
2573d37d430SMark Brown get_gcspr();
2583d37d430SMark Brown
2593d37d430SMark Brown ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS,
2603d37d430SMark Brown &child_mode, 0, 0, 0);
2613d37d430SMark Brown if (ret == 0 && !(child_mode & PR_SHADOW_STACK_ENABLE)) {
2623d37d430SMark Brown ksft_print_msg("GCS not enabled in child\n");
2633d37d430SMark Brown ret = -EINVAL;
2643d37d430SMark Brown }
2653d37d430SMark Brown
2663d37d430SMark Brown exit(ret);
2673d37d430SMark Brown }
2683d37d430SMark Brown
2693d37d430SMark Brown /*
2703d37d430SMark Brown * In parent, check we can still do function calls then block
2713d37d430SMark Brown * for the child.
2723d37d430SMark Brown */
2733d37d430SMark Brown valid_gcs_function();
2743d37d430SMark Brown
2753d37d430SMark Brown ksft_print_msg("Waiting for child %d\n", pid);
2763d37d430SMark Brown
2773d37d430SMark Brown ret = waitpid(pid, &status, 0);
2783d37d430SMark Brown if (ret == -1) {
2793d37d430SMark Brown ksft_print_msg("Failed to wait for child: %d\n",
2803d37d430SMark Brown errno);
2813d37d430SMark Brown return false;
2823d37d430SMark Brown }
2833d37d430SMark Brown
2843d37d430SMark Brown if (!WIFEXITED(status)) {
2853d37d430SMark Brown ksft_print_msg("Child exited due to signal %d\n",
2863d37d430SMark Brown WTERMSIG(status));
2873d37d430SMark Brown pass = false;
2883d37d430SMark Brown } else {
2893d37d430SMark Brown if (WEXITSTATUS(status)) {
2903d37d430SMark Brown ksft_print_msg("Child exited with status %d\n",
2913d37d430SMark Brown WEXITSTATUS(status));
2923d37d430SMark Brown pass = false;
2933d37d430SMark Brown }
2943d37d430SMark Brown }
2953d37d430SMark Brown
2963d37d430SMark Brown out:
2973d37d430SMark Brown
2983d37d430SMark Brown return pass;
2993d37d430SMark Brown }
3003d37d430SMark Brown
301*1536aa0fSMark Brown /* A vfork()ed process can run and exit */
test_vfork(void)302*1536aa0fSMark Brown static bool test_vfork(void)
303*1536aa0fSMark Brown {
304*1536aa0fSMark Brown unsigned long child_mode;
305*1536aa0fSMark Brown int ret, status;
306*1536aa0fSMark Brown pid_t pid;
307*1536aa0fSMark Brown bool pass = true;
308*1536aa0fSMark Brown
309*1536aa0fSMark Brown pid = vfork();
310*1536aa0fSMark Brown if (pid == -1) {
311*1536aa0fSMark Brown ksft_print_msg("vfork() failed: %d\n", errno);
312*1536aa0fSMark Brown pass = false;
313*1536aa0fSMark Brown goto out;
314*1536aa0fSMark Brown }
315*1536aa0fSMark Brown if (pid == 0) {
316*1536aa0fSMark Brown /*
317*1536aa0fSMark Brown * In child, make sure we can call a function, read
318*1536aa0fSMark Brown * the GCS pointer and status and then exit.
319*1536aa0fSMark Brown */
320*1536aa0fSMark Brown valid_gcs_function();
321*1536aa0fSMark Brown get_gcspr();
322*1536aa0fSMark Brown
323*1536aa0fSMark Brown ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS,
324*1536aa0fSMark Brown &child_mode, 0, 0, 0);
325*1536aa0fSMark Brown if (ret == 0 && !(child_mode & PR_SHADOW_STACK_ENABLE)) {
326*1536aa0fSMark Brown ksft_print_msg("GCS not enabled in child\n");
327*1536aa0fSMark Brown ret = EXIT_FAILURE;
328*1536aa0fSMark Brown }
329*1536aa0fSMark Brown
330*1536aa0fSMark Brown _exit(ret);
331*1536aa0fSMark Brown }
332*1536aa0fSMark Brown
333*1536aa0fSMark Brown /*
334*1536aa0fSMark Brown * In parent, check we can still do function calls then check
335*1536aa0fSMark Brown * on the child.
336*1536aa0fSMark Brown */
337*1536aa0fSMark Brown valid_gcs_function();
338*1536aa0fSMark Brown
339*1536aa0fSMark Brown ksft_print_msg("Waiting for child %d\n", pid);
340*1536aa0fSMark Brown
341*1536aa0fSMark Brown ret = waitpid(pid, &status, 0);
342*1536aa0fSMark Brown if (ret == -1) {
343*1536aa0fSMark Brown ksft_print_msg("Failed to wait for child: %d\n",
344*1536aa0fSMark Brown errno);
345*1536aa0fSMark Brown return false;
346*1536aa0fSMark Brown }
347*1536aa0fSMark Brown
348*1536aa0fSMark Brown if (!WIFEXITED(status)) {
349*1536aa0fSMark Brown ksft_print_msg("Child exited due to signal %d\n",
350*1536aa0fSMark Brown WTERMSIG(status));
351*1536aa0fSMark Brown pass = false;
352*1536aa0fSMark Brown } else if (WEXITSTATUS(status)) {
353*1536aa0fSMark Brown ksft_print_msg("Child exited with status %d\n",
354*1536aa0fSMark Brown WEXITSTATUS(status));
355*1536aa0fSMark Brown pass = false;
356*1536aa0fSMark Brown }
357*1536aa0fSMark Brown
358*1536aa0fSMark Brown out:
359*1536aa0fSMark Brown
360*1536aa0fSMark Brown return pass;
361*1536aa0fSMark Brown }
362*1536aa0fSMark Brown
3633d37d430SMark Brown typedef bool (*gcs_test)(void);
3643d37d430SMark Brown
3653d37d430SMark Brown static struct {
3663d37d430SMark Brown char *name;
3673d37d430SMark Brown gcs_test test;
3683d37d430SMark Brown bool needs_enable;
3693d37d430SMark Brown } tests[] = {
3703d37d430SMark Brown { "read_status", read_status },
3713d37d430SMark Brown { "base_enable", base_enable, true },
3723d37d430SMark Brown { "read_gcspr_el0", read_gcspr_el0 },
3733d37d430SMark Brown { "enable_writeable", enable_writeable, true },
3743d37d430SMark Brown { "enable_push_pop", enable_push_pop, true },
3753d37d430SMark Brown { "enable_all", enable_all, true },
3763d37d430SMark Brown { "enable_invalid", enable_invalid, true },
3773d37d430SMark Brown { "map_guarded_stack", map_guarded_stack },
3783d37d430SMark Brown { "fork", test_fork },
379*1536aa0fSMark Brown { "vfork", test_vfork },
3803d37d430SMark Brown };
3813d37d430SMark Brown
main(void)3823d37d430SMark Brown int main(void)
3833d37d430SMark Brown {
3843d37d430SMark Brown int i, ret;
3853d37d430SMark Brown unsigned long gcs_mode;
3863d37d430SMark Brown
3873d37d430SMark Brown ksft_print_header();
3883d37d430SMark Brown
3893d37d430SMark Brown /*
3903d37d430SMark Brown * We don't have getauxval() with nolibc so treat a failure to
3913d37d430SMark Brown * read GCS state as a lack of support and skip.
3923d37d430SMark Brown */
3933d37d430SMark Brown ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS,
3943d37d430SMark Brown &gcs_mode, 0, 0, 0);
3953d37d430SMark Brown if (ret != 0)
3963d37d430SMark Brown ksft_exit_skip("Failed to read GCS state: %d\n", ret);
3973d37d430SMark Brown
3983d37d430SMark Brown if (!(gcs_mode & PR_SHADOW_STACK_ENABLE)) {
3993d37d430SMark Brown gcs_mode = PR_SHADOW_STACK_ENABLE;
4003d37d430SMark Brown ret = my_syscall5(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
4013d37d430SMark Brown gcs_mode, 0, 0, 0);
4023d37d430SMark Brown if (ret != 0)
4033d37d430SMark Brown ksft_exit_fail_msg("Failed to enable GCS: %d\n", ret);
4043d37d430SMark Brown }
4053d37d430SMark Brown
4063d37d430SMark Brown ksft_set_plan(ARRAY_SIZE(tests));
4073d37d430SMark Brown
4083d37d430SMark Brown for (i = 0; i < ARRAY_SIZE(tests); i++) {
4093d37d430SMark Brown ksft_test_result((*tests[i].test)(), "%s\n", tests[i].name);
4103d37d430SMark Brown }
4113d37d430SMark Brown
4123d37d430SMark Brown /* One last test: disable GCS, we can do this one time */
4133d37d430SMark Brown my_syscall5(__NR_prctl, PR_SET_SHADOW_STACK_STATUS, 0, 0, 0, 0);
4143d37d430SMark Brown if (ret != 0)
4153d37d430SMark Brown ksft_print_msg("Failed to disable GCS: %d\n", ret);
4163d37d430SMark Brown
4173d37d430SMark Brown ksft_finished();
4183d37d430SMark Brown
4193d37d430SMark Brown return 0;
4203d37d430SMark Brown }
421