Commit 3d37d430 authored by Mark Brown's avatar Mark Brown Committed by Catalin Marinas
Browse files

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: default avatarThiago Jung Bauermann <thiago.bauermann@linaro.org>
Tested-by: default avatarThiago Jung Bauermann <thiago.bauermann@linaro.org>
Signed-off-by: default avatarMark Brown <broonie@kernel.org>
Link: https://lore.kernel.org/r/20241001-arm64-gcs-v13-34-222b78d87eee@kernel.org


Signed-off-by: default avatarCatalin Marinas <catalin.marinas@arm.com>
parent 42155a8e
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
basic-gcs
+18 −0
Original line number Diff line number Diff line
# 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 −0
Original line number Diff line number Diff line
// 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 −0
Original line number Diff line number Diff line
/* 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