[PATCH v2 3/3] aarch64: GCS: add clone3 tests for shadow stack

Yury Khrustalev yury.khrustalev@arm.com
Mon Jul 7 12:47:22 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     |  81 ++++++++++++
 .../linux/aarch64/tst-gcs-clone3-helper.h     |  50 ++++++++
 .../sysv/linux/aarch64/tst-gcs-clone3-nomem.c |  86 +++++++++++++
 .../unix/sysv/linux/aarch64/tst-gcs-clone3.c  | 118 ++++++++++++++++++
 5 files changed, 352 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-helper.h
 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..b2a0a66abb
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3-cancel.c
@@ -0,0 +1,81 @@
+/* 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 "tst-gcs-helper.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/auxv.h>
+
+#include <nptl/descr.h>
+#include <support/check.h>
+
+#include "tst-gcs-clone3-helper.h"
+
+static void *
+forever (void *arg)
+{
+  /* 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;
+  if (pthread_create (&th, NULL, forever, NULL) != 0)
+    {
+      perror ("pthread_create");
+      TEST_VERIFY (false);
+      return;
+    }
+  printf ("thread created\n");
+  struct pthread *pd = (struct pthread *)th;
+  void *token = pd->shadow_stack_token;
+  if (pthread_cancel (th))
+    {
+      printf ("cannot cancel thread\n");
+      TEST_VERIFY (false);
+      return;
+    }
+  else
+    printf ("thread cancelled\n");
+  void *res = NULL;
+  pthread_join (th, &res);
+  TEST_VERIFY (res == PTHREAD_CANCELED);
+  check_unmapped (token);
+}
+
+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-helper.h b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3-helper.h
new file mode 100644
index 0000000000..4ca36aa5b3
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3-helper.h
@@ -0,0 +1,50 @@
+/* Helper utils for tests of clone3 with enabled GCS.
+   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/>.  */
+
+#ifndef TST_GCS_CLONE3_HELPER_H
+#define TST_GCS_CLONE3_HELPER_H
+
+#include <setjmp.h>
+
+jmp_buf jmp;
+
+static void
+handler (int signum)
+{
+  TEST_VERIFY (signum == SIGSEGV);
+  write (STDOUT_FILENO, "in signal handler\n", 18);
+  longjmp (jmp, 1);
+}
+
+static void
+check_unmapped (void *p)
+{
+  /* Check that shadow stack was unmapped.  */
+  signal (SIGSEGV, handler);
+  if (setjmp (jmp) == 0)
+    {
+      /* This should cause SIGSEGV.  */
+      volatile int check __attribute_maybe_unused__ = *(int *)p;
+      printf ("error: shadow stack of a thread has not been unmapped\n");
+      TEST_VERIFY (false);
+    }
+  else
+    printf ("shadow stack of a thread has been unmapped\n");
+}
+
+#endif // TST_GCS_CLONE3_HELPER_H
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..07d381ed29
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3-nomem.c
@@ -0,0 +1,86 @@
+/* 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 "tst-gcs-helper.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/auxv.h>
+#include <sys/resource.h>
+
+#include <nptl/descr.h>
+#include <support/check.h>
+
+#include "tst-gcs-clone3-helper.h"
+
+static void *
+fun (void *arg)
+{
+  return arg;
+}
+
+static void
+test_not_launched (void)
+{
+  struct rlimit prev = {};
+  getrlimit (RLIMIT_AS, &prev);
+
+  size_t pagesize = getpagesize ();
+  size_t max_mem = pagesize * 1024 * 4;
+  struct rlimit as = {
+    .rlim_cur = max_mem,
+    .rlim_max = max_mem,
+  };
+  if (setrlimit (RLIMIT_AS, &as))
+    {
+      perror ("setrlimit");
+      TEST_VERIFY (false);
+    }
+
+  pthread_t th;
+  if (pthread_create (&th, NULL, fun, NULL) != 0)
+    {
+      perror ("expected: pthread_create");
+      setrlimit (RLIMIT_AS, &prev);
+      struct pthread *pd = (struct pthread *)th;
+      void *token = pd->shadow_stack_token;
+      pthread_join (th, NULL);
+      check_unmapped (token);
+      return;
+    }
+  setrlimit (RLIMIT_AS, &prev);
+  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..3a1c596f9d
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/aarch64/tst-gcs-clone3.c
@@ -0,0 +1,118 @@
+/* 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 "tst-gcs-helper.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/auxv.h>
+
+#include <nptl/descr.h>
+#include <support/check.h>
+#include <support/test-driver.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.  */
+#if __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 void
+handler (int signum, siginfo_t *info, void *ptr)
+{
+  TEST_VERIFY (signum == SIGSEGV);
+  write (STDOUT_FILENO, "in signal handler\n", 18);
+  exit(0);
+}
+
+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;
+  if (pthread_create (&th, NULL, fun, NULL) != 0)
+    {
+      perror ("pthread_create");
+      TEST_VERIFY_EXIT (false);
+    }
+  void *gcspr;
+  if (pthread_join (th, &gcspr))
+    {
+      perror ("pthread_join");
+      TEST_VERIFY_EXIT (false);
+    }
+
+  /* Check that if shadow stack was used, it has been freed and is
+     not accessible after the thread has finished.  */
+  if (gcspr)
+    {
+      struct sigaction sa;
+      memset (&sa, 0, sizeof (sa));
+      sa.sa_sigaction = handler;
+      sigaction (SIGSEGV, &sa, NULL);
+      int *p = gcspr;
+      printf ("reading from shadow stack: %d\n", *p);
+      /* Unreachable.  */
+      TEST_VERIFY (false);
+      __builtin_unreachable ();
+    }
+
+  return 0;
+}
+
+#include <support/test-driver.c>
-- 
2.39.5



More information about the Libc-alpha mailing list