lkml.org 
[lkml]   [2023]   [Nov]   [22]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Date
    Subject[PATCH v7 33/39] kselftest/arm64: Add very basic GCS test program
    This test program just covers the basic GCS ABI, covering aspects of the
    ABI as standalone features without attempting to integrate things.

    Signed-off-by: Mark Brown <broonie@kernel.org>
    ---
    tools/testing/selftests/arm64/Makefile | 2 +-
    tools/testing/selftests/arm64/gcs/.gitignore | 1 +
    tools/testing/selftests/arm64/gcs/Makefile | 18 ++
    tools/testing/selftests/arm64/gcs/basic-gcs.c | 428 ++++++++++++++++++++++++++
    tools/testing/selftests/arm64/gcs/gcs-util.h | 90 ++++++
    5 files changed, 538 insertions(+), 1 deletion(-)

    diff --git a/tools/testing/selftests/arm64/Makefile b/tools/testing/selftests/arm64/Makefile
    index 28b93cab8c0d..22029e60eff3 100644
    --- a/tools/testing/selftests/arm64/Makefile
    +++ b/tools/testing/selftests/arm64/Makefile
    @@ -4,7 +4,7 @@
    ARCH ?= $(shell uname -m 2>/dev/null || echo not)

    ifneq (,$(filter $(ARCH),aarch64 arm64))
    -ARM64_SUBTARGETS ?= tags signal pauth fp mte bti abi
    +ARM64_SUBTARGETS ?= tags signal pauth fp mte bti abi gcs
    else
    ARM64_SUBTARGETS :=
    endif
    diff --git a/tools/testing/selftests/arm64/gcs/.gitignore b/tools/testing/selftests/arm64/gcs/.gitignore
    new file mode 100644
    index 000000000000..0e5e695ecba5
    --- /dev/null
    +++ b/tools/testing/selftests/arm64/gcs/.gitignore
    @@ -0,0 +1 @@
    +basic-gcs
    diff --git a/tools/testing/selftests/arm64/gcs/Makefile b/tools/testing/selftests/arm64/gcs/Makefile
    new file mode 100644
    index 000000000000..61a30f483429
    --- /dev/null
    +++ b/tools/testing/selftests/arm64/gcs/Makefile
    @@ -0,0 +1,18 @@
    +# SPDX-License-Identifier: GPL-2.0
    +# Copyright (C) 2023 ARM Limited
    +#
    +# In order to avoid interaction with the toolchain and dynamic linker the
    +# portions of these tests that interact with the GCS are implemented using
    +# nolibc.
    +#
    +
    +TEST_GEN_PROGS := basic-gcs
    +
    +include ../../lib.mk
    +
    +$(OUTPUT)/basic-gcs: basic-gcs.c
    + $(CC) -g -fno-asynchronous-unwind-tables -fno-ident -s -Os -nostdlib \
    + -static -include ../../../../include/nolibc/nolibc.h \
    + -I../../../../../usr/include \
    + -std=gnu99 -I../.. -g \
    + -ffreestanding -Wall $^ -o $@ -lgcc
    diff --git a/tools/testing/selftests/arm64/gcs/basic-gcs.c b/tools/testing/selftests/arm64/gcs/basic-gcs.c
    new file mode 100644
    index 000000000000..3ef957e0065f
    --- /dev/null
    +++ b/tools/testing/selftests/arm64/gcs/basic-gcs.c
    @@ -0,0 +1,428 @@
    +// 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 %x not %x\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 %d byte GCS: %d\n",
    + page_size, errno);
    + return false;
    + }
    + ksft_print_msg("Mapped GCS at %p-%p\n", buf,
    + (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%lx 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%lx not 0x%lx\n",
    + buf[elem], expected_cap);
    + pass = false;
    + }
    + ksft_print_msg("cap token is 0x%lx\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%lx not 0x0\n",
    + elem, buf[elem]);
    + pass = false;
    + }
    +
    + ret = munmap(buf, page_size);
    + if (ret != 0) {
    + ksft_print_msg("Failed to unmap %d 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;
    +}
    +
    +/* Check that we can explicitly specify a GCS via clone3() */
    +static bool test_clone3(void)
    +{
    + struct clone_args args;
    + unsigned long child_mode;
    + pid_t pid = -1;
    + int status, ret;
    + bool pass;
    +
    + memset(&args, 0, sizeof(args));
    + args.flags = CLONE_VM;
    + args.shadow_stack_size = page_size;
    +
    + pid = my_syscall2(__NR_clone3, &args, sizeof(args));
    + if (pid < 0) {
    + ksft_print_msg("clone3() failed: %d\n", errno);
    + pass = false;
    + goto out;
    + }
    +
    + /* In child? */
    + if (pid == 0) {
    + /* Do we have GCS enabled? */
    + ret = my_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS,
    + &child_mode, 0, 0, 0);
    + if (ret != 0) {
    + ksft_print_msg("PR_GET_SHADOW_STACK_STATUS failed: %d\n",
    + ret);
    + exit(EXIT_FAILURE);
    + }
    +
    + if (!(child_mode & PR_SHADOW_STACK_ENABLE)) {
    + ksft_print_msg("GCS not enabled in child\n");
    + exit(EXIT_FAILURE);
    + }
    +
    + ksft_print_msg("GCS enabled in child\n");
    +
    + /* We've probably already called a function but make sure */
    + valid_gcs_function();
    +
    + exit(EXIT_SUCCESS);
    + }
    +
    + if (waitpid(-1, &status, __WALL) < 0) {
    + ksft_print_msg("waitpid() failed %d\n", errno);
    + pass = false;
    + goto out;
    + }
    + if (WIFEXITED(status)) {
    + if (WEXITSTATUS(status) == EXIT_SUCCESS) {
    + pass = true;
    + } else {
    + ksft_print_msg("Child returned status %d\n",
    + WEXITSTATUS(status));
    + pass = false;
    + }
    + } else if (WIFSIGNALED(status)) {
    + ksft_print_msg("Child exited due to signal %d\n",
    + WTERMSIG(status));
    + pass = false;
    + } else {
    + ksft_print_msg("Child exited uncleanly\n");
    + 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 },
    + { "clone3", test_clone3 },
    +};
    +
    +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;
    +}
    diff --git a/tools/testing/selftests/arm64/gcs/gcs-util.h b/tools/testing/selftests/arm64/gcs/gcs-util.h
    new file mode 100644
    index 000000000000..b37801c95604
    --- /dev/null
    +++ b/tools/testing/selftests/arm64/gcs/gcs-util.h
    @@ -0,0 +1,90 @@
    +/* SPDX-License-Identifier: GPL-2.0-only */
    +/*
    + * Copyright (C) 2023 ARM Limited.
    + */
    +
    +#ifndef GCS_UTIL_H
    +#define GCS_UTIL_H
    +
    +#include <stdbool.h>
    +
    +#ifndef __NR_map_shadow_stack
    +#define __NR_map_shadow_stack 453
    +#endif
    +
    +#ifndef __NR_prctl
    +#define __NR_prctl 167
    +#endif
    +
    +/* Shadow Stack/Guarded Control Stack interface */
    +#define PR_GET_SHADOW_STACK_STATUS 71
    +#define PR_SET_SHADOW_STACK_STATUS 72
    +#define PR_LOCK_SHADOW_STACK_STATUS 73
    +
    +# define PR_SHADOW_STACK_ENABLE (1UL << 0)
    +# define PR_SHADOW_STACK_WRITE (1UL << 1)
    +# define PR_SHADOW_STACK_PUSH (1UL << 2)
    +
    +#define PR_SHADOW_STACK_ALL_MODES \
    + PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_WRITE | PR_SHADOW_STACK_PUSH
    +
    +#define SHADOW_STACK_SET_TOKEN (1ULL << 0) /* Set up a restore token in the shadow stack */
    +#define SHADOW_STACK_SET_MARKER (1ULL << 1) /* Set up a top of stack merker in the shadow stack */
    +
    +#define GCS_CAP_ADDR_MASK (0xfffffffffffff000UL)
    +#define GCS_CAP_TOKEN_MASK (0x0000000000000fffUL)
    +#define GCS_CAP_VALID_TOKEN 1
    +#define GCS_CAP_IN_PROGRESS_TOKEN 5
    +
    +#define GCS_CAP(x) (((unsigned long)(x) & GCS_CAP_ADDR_MASK) | \
    + GCS_CAP_VALID_TOKEN)
    +
    +static inline unsigned long *get_gcspr(void)
    +{
    + unsigned long *gcspr;
    +
    + asm volatile(
    + "mrs %0, S3_3_C2_C5_1"
    + : "=r" (gcspr)
    + :
    + : "cc");
    +
    + return gcspr;
    +}
    +
    +static inline void __attribute__((always_inline)) gcsss1(unsigned long *Xt)
    +{
    + asm volatile (
    + "sys #3, C7, C7, #2, %0\n"
    + :
    + : "rZ" (Xt)
    + : "memory");
    +}
    +
    +static inline unsigned long __attribute__((always_inline)) *gcsss2(void)
    +{
    + unsigned long *Xt;
    +
    + asm volatile(
    + "SYSL %0, #3, C7, C7, #3\n"
    + : "=r" (Xt)
    + :
    + : "memory");
    +
    + return Xt;
    +}
    +
    +static inline bool chkfeat_gcs(void)
    +{
    + register long val __asm__ ("x16") = 1;
    +
    + /* CHKFEAT x16 */
    + asm volatile(
    + "hint #0x28\n"
    + : "=r" (val)
    + : "r" (val));
    +
    + return val != 1;
    +}
    +
    +#endif
    --
    2.39.2

    \
     
     \ /
      Last update: 2023-11-22 10:57    [W:2.742 / U:0.152 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site