mirror of
https://github.com/torvalds/linux.git
synced 2025-12-07 20:06:24 +00:00
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. Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org> Tested-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org> Signed-off-by: Mark Brown <broonie@kernel.org> Link: https://lore.kernel.org/r/20241001-arm64-gcs-v13-34-222b78d87eee@kernel.org Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
This commit is contained in:
committed by
Catalin Marinas
parent
42155a8eb0
commit
3d37d4307e
@@ -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
|
||||
|
||||
1
tools/testing/selftests/arm64/gcs/.gitignore
vendored
Normal file
1
tools/testing/selftests/arm64/gcs/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
basic-gcs
|
||||
18
tools/testing/selftests/arm64/gcs/Makefile
Normal file
18
tools/testing/selftests/arm64/gcs/Makefile
Normal file
@@ -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
|
||||
357
tools/testing/selftests/arm64/gcs/basic-gcs.c
Normal file
357
tools/testing/selftests/arm64/gcs/basic-gcs.c
Normal file
@@ -0,0 +1,357 @@
|
||||
// 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;
|
||||
}
|
||||
90
tools/testing/selftests/arm64/gcs/gcs-util.h
Normal file
90
tools/testing/selftests/arm64/gcs/gcs-util.h
Normal file
@@ -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 74
|
||||
#define PR_SET_SHADOW_STACK_STATUS 75
|
||||
#define PR_LOCK_SHADOW_STACK_STATUS 76
|
||||
|
||||
# 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
|
||||
Reference in New Issue
Block a user