Add some infrastructure for testing gcs in userspace.
Validate successful and trapped executions of GCSSTR.
Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
---
tests/tcg/aarch64/gcs.h | 71 +++++++++++++++++++++++++++++++
tests/tcg/aarch64/gcsstr.c | 48 +++++++++++++++++++++
tests/tcg/aarch64/Makefile.target | 5 +++
3 files changed, 124 insertions(+)
create mode 100644 tests/tcg/aarch64/gcs.h
create mode 100644 tests/tcg/aarch64/gcsstr.c
diff --git a/tests/tcg/aarch64/gcs.h b/tests/tcg/aarch64/gcs.h
new file mode 100644
index 0000000000..99cb4d4e38
--- /dev/null
+++ b/tests/tcg/aarch64/gcs.h
@@ -0,0 +1,71 @@
+/*
+ * Linux kernel fallback API definitions for GCS and test helpers.
+ *
+ * Copyright (c) 2025 Linaro Ltd
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+#include <sys/syscall.h>
+
+#ifndef PR_GET_SHADOW_STACK_STATUS
+#define PR_GET_SHADOW_STACK_STATUS 74
+#endif
+#ifndef PR_SET_SHADOW_STACK_STATUS
+#define PR_SET_SHADOW_STACK_STATUS 75
+#endif
+#ifndef PR_LOCK_SHADOW_STACK_STATUS
+#define PR_LOCK_SHADOW_STACK_STATUS 76
+#endif
+#ifndef PR_SHADOW_STACK_ENABLE
+# define PR_SHADOW_STACK_ENABLE (1 << 0)
+# define PR_SHADOW_STACK_WRITE (1 << 1)
+# define PR_SHADOW_STACK_PUSH (1 << 2)
+#endif
+#ifndef SHADOW_STACK_SET_TOKEN
+#define SHADOW_STACK_SET_TOKEN (1 << 0)
+#endif
+#ifndef SHADOW_STACK_SET_MARKER
+#define SHADOW_STACK_SET_MARKER (1 << 1)
+#endif
+#ifndef SEGV_CPERR
+#define SEGV_CPERR 10
+#endif
+#ifndef __NR_map_shadow_stack
+#define __NR_map_shadow_stack 453
+#endif
+
+/*
+ * Macros, and implement the syscall inline, lest we fail
+ * the checked return from any function call.
+ */
+#define enable_gcs(flags) \
+ do { \
+ register long num __asm__ ("x8") = __NR_prctl; \
+ register long arg1 __asm__ ("x0") = PR_SET_SHADOW_STACK_STATUS; \
+ register long arg2 __asm__ ("x1") = PR_SHADOW_STACK_ENABLE | flags; \
+ register long arg3 __asm__ ("x2") = 0; \
+ register long arg4 __asm__ ("x3") = 0; \
+ register long arg5 __asm__ ("x4") = 0; \
+ asm volatile("svc #0" \
+ : "+r"(arg1) \
+ : "r"(arg2), "r"(arg3), "r"(arg4), "r"(arg5), "r"(num) \
+ : "memory", "cc"); \
+ if (arg1) { \
+ errno = -arg1; \
+ perror("PR_SET_SHADOW_STACK_STATUS"); \
+ exit(2); \
+ } \
+ } while (0)
+
+#define gcspr() \
+ ({ uint64_t *r; asm volatile("mrs %0, s3_3_c2_c5_1" : "=r"(r)); r; })
diff --git a/tests/tcg/aarch64/gcsstr.c b/tests/tcg/aarch64/gcsstr.c
new file mode 100644
index 0000000000..b045aee925
--- /dev/null
+++ b/tests/tcg/aarch64/gcsstr.c
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "gcs.h"
+
+/*
+ * A single garbage store to the gcs stack.
+ * The asm inside must be unique, so disallow inlining.
+ */
+void __attribute__((noinline))
+test_gcsstr(void)
+{
+ register uint64_t *ptr __asm__("x0") = gcspr();
+ /* GCSSTR x1, x0 */
+ __asm__("inst_gcsstr: .inst 0xd91f1c01" : : "r"(--ptr));
+}
+
+static void test_sigsegv(int sig, siginfo_t *info, void *vuc)
+{
+ ucontext_t *uc = vuc;
+ uint64_t inst_gcsstr;
+
+ __asm__("adr %0, inst_gcsstr" : "=r"(inst_gcsstr));
+ assert(uc->uc_mcontext.pc == inst_gcsstr);
+ assert(info->si_code == SEGV_CPERR);
+ /* TODO: Dig for ESR and verify syndrome. */
+ exit(0);
+}
+
+int main()
+{
+ struct sigaction sa = {
+ .sa_sigaction = test_sigsegv,
+ .sa_flags = SA_SIGINFO,
+ };
+
+ /* Enable GCSSTR and test the store succeeds. */
+ enable_gcs(PR_SHADOW_STACK_WRITE);
+ test_gcsstr();
+
+ /* Disable GCSSTR and test the resulting sigsegv. */
+ enable_gcs(0);
+ if (sigaction(SIGSEGV, &sa, NULL) < 0) {
+ perror("sigaction");
+ exit(1);
+ }
+ test_gcsstr();
+ abort();
+}
diff --git a/tests/tcg/aarch64/Makefile.target b/tests/tcg/aarch64/Makefile.target
index 16ddcf4f88..0347a36e8d 100644
--- a/tests/tcg/aarch64/Makefile.target
+++ b/tests/tcg/aarch64/Makefile.target
@@ -75,6 +75,11 @@ AARCH64_TESTS += $(SME_TESTS)
$(SME_TESTS): CFLAGS += $(CROSS_AS_HAS_ARMV9_SME)
endif
+# GCS Tests
+GCS_TESTS += gcsstr
+AARCH64_TESTS += $(GCS_TESTS)
+$(GCS_TESTS): gcs.h
+
# System Registers Tests
AARCH64_TESTS += sysregs
--
2.43.0