[PATCH v3 3/3] aarch64: GCS: add clone3 tests for shadow stack
Yury Khrustalev
yury.khrustalev@arm.com
Tue Sep 16 12:27:57 GMT 2025
If GCS is available check that new thread is created with a
shadow stack allocated by Glibc.
Check that shadow stack is de-allocated if a new thread hasn't
started or has been cancelled.
---
sysdeps/unix/sysv/linux/aarch64/Makefile | 17 +++
.../linux/aarch64/tst-gcs-clone3-cancel.c | 84 +++++++++++++
.../sysv/linux/aarch64/tst-gcs-clone3-nomem.c | 111 ++++++++++++++++++
.../unix/sysv/linux/aarch64/tst-gcs-clone3.c | 89 ++++++++++++++
4 files changed, 301 insertions(+)
create mode 100644 sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3-cancel.c
create mode 100644 sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3-nomem.c
create mode 100644 sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3.c
diff --git a/sysdeps/unix/sysv/linux/aarch64/Makefile b/sysdeps/unix/sysv/linux/aarch64/Makefile
index 15a2b4471d..e4775f8024 100644
--- a/sysdeps/unix/sysv/linux/aarch64/Makefile
+++ b/sysdeps/unix/sysv/linux/aarch64/Makefile
@@ -50,12 +50,21 @@ gcs-tests-static = \
tests += \
$(gcs-tests-dynamic) \
$(gcs-tests-static) \
+ tst-gcs-clone3 \
+ tst-gcs-clone3-cancel \
+ tst-gcs-clone3-nomem \
# tests
tests-static += \
$(gcs-tests-static) \
# tests-static
+tests-internal += \
+ tst-gcs-clone3 \
+ tst-gcs-clone3-cancel \
+ tst-gcs-clone3-nomem \
+ # tests-internal
+
define run-gcs-abort-test
$(test-wrapper-env) $(run-program-env) \
$(tst-gcs-$*-abort-ENV) $(host-test-program-cmd)
@@ -80,6 +89,10 @@ LDFLAGS-tst-gcs-optional-static-on += -Wl,-z gcs=always
LDFLAGS-tst-gcs-optional-static-off += -Wl,-z gcs=never
LDFLAGS-tst-gcs-override-static += -Wl,-z gcs=never
+LDFLAGS-tst-gcs-clone3 += -Wl,-z gcs=always
+LDFLAGS-tst-gcs-clone3-cancel += -Wl,-z gcs=always
+LDFLAGS-tst-gcs-clone3-nomem += -Wl,-z gcs=always
+
tst-gcs-disabled-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=0
tst-gcs-enforced-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=1
tst-gcs-enforced-abort-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=1
@@ -94,6 +107,10 @@ tst-gcs-optional-static-on-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=2
tst-gcs-optional-static-off-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=2
tst-gcs-override-static-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=3
+tst-gcs-clone3-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=1
+tst-gcs-clone3-cancel-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=1
+tst-gcs-clone3-nomem-ENV = GLIBC_TUNABLES=glibc.cpu.aarch64_gcs=1
+
# force one of the dependencies to be unmarked
LDFLAGS-tst-gcs-mod2.so += -Wl,-z gcs=never
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3-cancel.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3-cancel.c
new file mode 100644
index 0000000000..1d2b4b3e3e
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3-cancel.c
@@ -0,0 +1,84 @@
+/* Check that shadow stack allocated for a thread is freed
+ when this thread is cancelled.
+ Copyright (C) 2025 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <stdio.h>
+#include <regex.h>
+#include <string.h>
+#include <unistd.h>
+#include <atomic.h>
+#include <sys/auxv.h>
+
+#include <support/xthread.h>
+#include <support/check_mem_access.h>
+
+#include "tst-gcs-helper.h"
+
+static uint64_t gcspr = 0;
+
+static void *
+forever (void *arg)
+{
+#ifdef __ARM_FEATURE_GCS_DEFAULT
+ /* While thread is running, we make a note of the GCSPR reg. */
+ uint64_t t = (uint64_t)__builtin_aarch64_gcspr ();
+ atomic_store_relaxed (&gcspr, t);
+#endif
+ /* If all is well, we should cancel this thread. */
+ pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
+ while (true);
+ return arg;
+}
+
+static void
+test_cancel (void)
+{
+ pthread_t th = xpthread_create (NULL, forever, NULL);
+ printf ("thread created\n");
+
+#ifdef __ARM_FEATURE_GCS_DEFAULT
+ while (atomic_load_relaxed (&gcspr) == 0)
+ usleep (1000);
+ TEST_VERIFY (gcspr != 0);
+#endif
+
+ xpthread_cancel (th);
+ printf ("thread cancelled\n");
+ void *res = xpthread_join (th);
+ TEST_VERIFY (res == PTHREAD_CANCELED);
+
+#ifdef __ARM_FEATURE_GCS_DEFAULT
+ TEST_COMPARE (check_mem_access ((void *)gcspr, false), false);
+ TEST_COMPARE (check_mem_access ((void *)gcspr, true), false);
+#endif
+}
+
+static int
+do_test (void)
+{
+ /* Check if GCS could possibly be enabled. */
+ if (!(getauxval (AT_HWCAP) & HWCAP_GCS) || !__check_gcs_status ())
+ {
+ puts ("kernel or CPU does not support GCS or GCS is disabled");
+ return EXIT_UNSUPPORTED;
+ }
+ test_cancel ();
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3-nomem.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3-nomem.c
new file mode 100644
index 0000000000..0c75fecb33
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3-nomem.c
@@ -0,0 +1,111 @@
+/* Check that shadow stack allocated for a thread is freed
+ when this thread doesn't run, for example, due to resource
+ constraints.
+ Copyright (C) 2025 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <stdio.h>
+#include <sys/auxv.h>
+
+#include <nptl/descr.h>
+#include <support/check.h>
+#include <support/xthread.h>
+#include <support/check_mem_access.h>
+
+#include <linux/seccomp.h>
+#include <linux/filter.h>
+#include <sys/prctl.h>
+
+#include "tst-gcs-helper.h"
+
+#define LENGTH_OF(v) (sizeof (v) / sizeof (v[0]))
+
+static void *
+fun (void *arg)
+{
+ return arg;
+}
+
+static void
+test_not_launched (void)
+{
+
+ /* Simulate failure coming from the kernel when the clone3 syscall is
+ made by pthread_create. We use EFAULT for the simulated error
+ to make sure that the trial clone3 syscall made by the code that
+ allocates shadow stack to succeed (this code relies on E2BIG)
+ to detect if clone3 supports shadow stack. */
+ struct sock_filter filter[] = {
+ BPF_STMT (BPF_ABS, offsetof (struct seccomp_data, nr)),
+ BPF_JUMP (BPF_JMP | BPF_JEQ, __NR_clone3, 0, 1),
+ BPF_STMT (BPF_RET, SECCOMP_RET_ERRNO | EFAULT),
+ BPF_STMT (BPF_RET, SECCOMP_RET_ALLOW),
+ };
+ struct sock_fprog program = {
+ .len = LENGTH_OF (filter),
+ .filter = filter,
+ };
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1
+ || prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &program) == -1)
+ {
+ perror ("prctl");
+ TEST_VERIFY (false);
+ return;
+ }
+
+ pthread_t th;
+ if (pthread_create (&th, NULL, fun, NULL) != 0)
+ {
+ perror ("expected: pthread_create");
+ /* Because the thread doesn't actually start, we need to
+ obtain the token of the shadow stack from the internal
+ pthread struct. */
+ struct pthread *pd = (struct pthread *)th;
+ void *token = pd->shadow_stack_token;
+ printf ("token: %016lx\n", (uintptr_t)token);
+ /* For the test to be valid the token must be non-NULL which
+ means that the shadow stack has indeed been allocated (so
+ the call to map_shadow_stack() succeeded. */
+ TEST_VERIFY (token != NULL);
+ /* However, shadow stack must be unmapped if thread has not
+ started. */
+ TEST_COMPARE (check_mem_access (token, false), false);
+ TEST_COMPARE (check_mem_access (token, true), false);
+ }
+ else
+ {
+ xpthread_join (th);
+ printf ("pthread created (unexpected)\n");
+ TEST_VERIFY (false);
+ }
+
+}
+
+static int
+do_test (void)
+{
+ /* Check if GCS could possibly be enabled. */
+ if (!(getauxval (AT_HWCAP) & HWCAP_GCS) || !__check_gcs_status ())
+ {
+ puts ("kernel or CPU does not support GCS or GCS is disabled");
+ return EXIT_UNSUPPORTED;
+ }
+ test_not_launched ();
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3.c b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3.c
new file mode 100644
index 0000000000..0da1afc513
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3.c
@@ -0,0 +1,89 @@
+/* Check clone3 uses shadow stack allocated by Glibc when
+ GCS is enabled and shadow stack is supported.
+ Copyright (C) 2025 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/auxv.h>
+
+#include <nptl/descr.h>
+#include <support/xthread.h>
+#include <support/check_mem_access.h>
+
+#include "tst-gcs-helper.h"
+
+static void *
+fun (void *arg)
+{
+ pthread_t self = pthread_self ();
+ struct pthread *pd = (struct pthread *)self;
+ uint64_t token = (uint64_t)pd->shadow_stack_token;
+ uint64_t base = (uint64_t)pd->shadow_stack_base;
+ uint64_t top = base + pd->shadow_stack_size;
+
+ /* If clone3 does not support shadow stack, this pointer
+ will not be initialised and test should give the
+ UNSUPPORTED result. */
+ if (base == 0)
+ {
+ puts ("shadow stack was not allocated by Glibc");
+ exit (EXIT_UNSUPPORTED);
+ }
+ printf ("shadow stack top: %016lx\n", top);
+ printf ("shadow stack token: %016lx\n", token);
+
+ /* This macro guard is for the sake of compilers that don't
+ yet have the __builtin_aarch64_gcspr() builtin so that
+ the test could compiler regardless. */
+#ifdef __ARM_FEATURE_GCS_DEFAULT
+ /* Check that current shadow stack pointer has correct value. */
+ uint64_t gcspr = (uint64_t)__builtin_aarch64_gcspr ();
+ printf ("current shadow stack: %016lx\n", gcspr);
+ TEST_VERIFY (gcspr < token);
+#else
+ uint64_t gcspr = 0;
+#endif
+ TEST_VERIFY (token < top);
+ TEST_VERIFY (base < token);
+
+ printf ("shadow stack base: %016lx\n", base);
+
+ return (void *)gcspr;
+}
+
+static int
+do_test (void)
+{
+ /* Check if GCS could possibly be enabled. */
+ if (!(getauxval (AT_HWCAP) & HWCAP_GCS) || !__check_gcs_status ())
+ {
+ puts ("kernel or CPU does not support GCS or GCS is disabled");
+ return EXIT_UNSUPPORTED;
+ }
+ pthread_t th = xpthread_create (NULL, fun, NULL);
+ void *gcspr = xpthread_join (th);
+
+ /* Check that if shadow stack was used, it has been freed and is
+ not accessible after the thread has finished. */
+ TEST_COMPARE (check_mem_access (gcspr, false), false);
+ TEST_COMPARE (check_mem_access (gcspr, true), false);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
--
2.47.3
More information about the Libc-alpha
mailing list