[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