]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
kselftest/arm64: Add test coverage for GCS mode locking
authorMark Brown <broonie@kernel.org>
Tue, 1 Oct 2024 22:59:15 +0000 (23:59 +0100)
committerCatalin Marinas <catalin.marinas@arm.com>
Fri, 4 Oct 2024 11:04:45 +0000 (12:04 +0100)
Verify that we can lock individual GCS mode bits, that other modes
aren't affected and as a side effect also that every combination of
modes can be enabled.

Normally the inability to reenable GCS after disabling it would be an
issue with testing but fortunately the kselftest_harness runs each test
within a fork()ed child.  This can be inconvenient for some kinds of
testing but here it means that each test is in a separate thread and
therefore won't be affected by other tests in the suite.

Once we get toolchains with support for enabling GCS by default we will
need to take care to not do that in the build system but there are no
such toolchains yet so it is not yet an issue.

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-36-222b78d87eee@kernel.org
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
tools/testing/selftests/arm64/gcs/.gitignore
tools/testing/selftests/arm64/gcs/Makefile
tools/testing/selftests/arm64/gcs/gcs-locking.c [new file with mode: 0644]

index 5810c4a163d4407ed9709fcf43c67c28ba56381f..0c86f53f68ad28faf0ed07f45aa17139207ce1d3 100644 (file)
@@ -1,2 +1,3 @@
 basic-gcs
 libc-gcs
+gcs-locking
index a8fdf21e9a4716c0cd526764cc8ff5aca95bc7d7..2173d6275956d8b12d353fef0b1ea6f453b1f45c 100644 (file)
@@ -6,7 +6,7 @@
 # nolibc.
 #
 
-TEST_GEN_PROGS := basic-gcs libc-gcs
+TEST_GEN_PROGS := basic-gcs libc-gcs gcs-locking
 
 LDLIBS+=-lpthread
 
diff --git a/tools/testing/selftests/arm64/gcs/gcs-locking.c b/tools/testing/selftests/arm64/gcs/gcs-locking.c
new file mode 100644 (file)
index 0000000..989f75a
--- /dev/null
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2023 ARM Limited.
+ *
+ * Tests for GCS mode locking.  These tests rely on both having GCS
+ * unconfigured on entry and on the kselftest harness running each
+ * test in a fork()ed process which will have it's own mode.
+ */
+
+#include <limits.h>
+
+#include <sys/auxv.h>
+#include <sys/prctl.h>
+
+#include <asm/hwcap.h>
+
+#include "kselftest_harness.h"
+
+#include "gcs-util.h"
+
+#define my_syscall2(num, arg1, arg2)                                          \
+({                                                                            \
+       register long _num  __asm__ ("x8") = (num);                           \
+       register long _arg1 __asm__ ("x0") = (long)(arg1);                    \
+       register long _arg2 __asm__ ("x1") = (long)(arg2);                    \
+       register long _arg3 __asm__ ("x2") = 0;                               \
+       register long _arg4 __asm__ ("x3") = 0;                               \
+       register long _arg5 __asm__ ("x4") = 0;                               \
+                                                                             \
+       __asm__  volatile (                                                   \
+               "svc #0\n"                                                    \
+               : "=r"(_arg1)                                                 \
+               : "r"(_arg1), "r"(_arg2),                                     \
+                 "r"(_arg3), "r"(_arg4),                                     \
+                 "r"(_arg5), "r"(_num)                                       \
+               : "memory", "cc"                                              \
+       );                                                                    \
+       _arg1;                                                                \
+})
+
+/* No mode bits are rejected for locking */
+TEST(lock_all_modes)
+{
+       int ret;
+
+       ret = prctl(PR_LOCK_SHADOW_STACK_STATUS, ULONG_MAX, 0, 0, 0);
+       ASSERT_EQ(ret, 0);
+}
+
+FIXTURE(valid_modes)
+{
+};
+
+FIXTURE_VARIANT(valid_modes)
+{
+       unsigned long mode;
+};
+
+FIXTURE_VARIANT_ADD(valid_modes, enable)
+{
+       .mode = PR_SHADOW_STACK_ENABLE,
+};
+
+FIXTURE_VARIANT_ADD(valid_modes, enable_write)
+{
+       .mode = PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_WRITE,
+};
+
+FIXTURE_VARIANT_ADD(valid_modes, enable_push)
+{
+       .mode = PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_PUSH,
+};
+
+FIXTURE_VARIANT_ADD(valid_modes, enable_write_push)
+{
+       .mode = PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_WRITE |
+               PR_SHADOW_STACK_PUSH,
+};
+
+FIXTURE_SETUP(valid_modes)
+{
+}
+
+FIXTURE_TEARDOWN(valid_modes)
+{
+}
+
+/* We can set the mode at all */
+TEST_F(valid_modes, set)
+{
+       int ret;
+
+       ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
+                         variant->mode);
+       ASSERT_EQ(ret, 0);
+
+       _exit(0);
+}
+
+/* Enabling, locking then disabling is rejected */
+TEST_F(valid_modes, enable_lock_disable)
+{
+       unsigned long mode;
+       int ret;
+
+       ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
+                         variant->mode);
+       ASSERT_EQ(ret, 0);
+
+       ret = prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0);
+       ASSERT_EQ(ret, 0);
+       ASSERT_EQ(mode, variant->mode);
+
+       ret = prctl(PR_LOCK_SHADOW_STACK_STATUS, variant->mode, 0, 0, 0);
+       ASSERT_EQ(ret, 0);
+
+       ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS, 0);
+       ASSERT_EQ(ret, -EBUSY);
+
+       _exit(0);
+}
+
+/* Locking then enabling is rejected */
+TEST_F(valid_modes, lock_enable)
+{
+       unsigned long mode;
+       int ret;
+
+       ret = prctl(PR_LOCK_SHADOW_STACK_STATUS, variant->mode, 0, 0, 0);
+       ASSERT_EQ(ret, 0);
+
+       ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
+                         variant->mode);
+       ASSERT_EQ(ret, -EBUSY);
+
+       ret = prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0);
+       ASSERT_EQ(ret, 0);
+       ASSERT_EQ(mode, 0);
+
+       _exit(0);
+}
+
+/* Locking then changing other modes is fine */
+TEST_F(valid_modes, lock_enable_disable_others)
+{
+       unsigned long mode;
+       int ret;
+
+       ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
+                         variant->mode);
+       ASSERT_EQ(ret, 0);
+
+       ret = prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0);
+       ASSERT_EQ(ret, 0);
+       ASSERT_EQ(mode, variant->mode);
+
+       ret = prctl(PR_LOCK_SHADOW_STACK_STATUS, variant->mode, 0, 0, 0);
+       ASSERT_EQ(ret, 0);
+
+       ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
+                         PR_SHADOW_STACK_ALL_MODES);
+       ASSERT_EQ(ret, 0);
+
+       ret = prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0);
+       ASSERT_EQ(ret, 0);
+       ASSERT_EQ(mode, PR_SHADOW_STACK_ALL_MODES);
+
+
+       ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
+                         variant->mode);
+       ASSERT_EQ(ret, 0);
+
+       ret = prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0);
+       ASSERT_EQ(ret, 0);
+       ASSERT_EQ(mode, variant->mode);
+
+       _exit(0);
+}
+
+int main(int argc, char **argv)
+{
+       unsigned long mode;
+       int ret;
+
+       if (!(getauxval(AT_HWCAP) & HWCAP_GCS))
+               ksft_exit_skip("SKIP GCS not supported\n");
+
+       ret = prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0);
+       if (ret) {
+               ksft_print_msg("Failed to read GCS state: %d\n", ret);
+               return EXIT_FAILURE;
+       }
+
+       if (mode & PR_SHADOW_STACK_ENABLE) {
+               ksft_print_msg("GCS was enabled, test unsupported\n");
+               return KSFT_SKIP;
+       }
+
+       return test_harness_run(argc, argv);
+}