[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