[PATCH v4 2/5] nptl: Add libc allocated shadow stack for new threads

Yury Khrustalev yury.khrustalev@arm.com
Fri Nov 7 09:35:06 GMT 2025


Includes aarch64 GCS support code, does not include x86 shstk
support (should be similar to the aarch64 case).

Uses extended clone3 interface that allows to pass pointer to
architecture-defined token on shadow stack.

We allocate shadow stack before we pass it to the clone3 syscall
at which point it may fail with E2BIG if the kernel doesn't support
new field in struct clone_args (but it still may have support for
AArch64's HWCAP_GCS). In this case we would need to de-allocate
shadow stack and repeat clone3 syscall without it. Due to complexities
of create_thread() and also because we can't know which field was
actually a problem when we receive E2BIG error from the clone3 syscall,
it is better to check if kernel supports shadow stack in clone_args
before we even allocate it.

We do this by making a dummy clone3 syscall which is going to fail
(so no new task will actually start) but it can fail in one of two
possible ways that will tell us whether the shadow_stack_token field
is supported by the kernel or not. We then cache the result to avoid
doing checks again.

We can use relaxed atomics because even if two concurrent threads
run the check function, they are guaranteed to get the same result.
This even is extremely unlikely, however.

Co-authored-by: Szabolcs Nagy <szabolcs.nagy@arm.com>
---
 nptl/descr.h                        |  7 ++
 nptl/pthread_create.c               | 29 +++++++++
 sysdeps/aarch64/libc-shadow-stack.h | 99 +++++++++++++++++++++++++++++
 sysdeps/generic/libc-shadow-stack.h | 25 ++++++++
 4 files changed, 160 insertions(+)
 create mode 100644 sysdeps/aarch64/libc-shadow-stack.h
 create mode 100644 sysdeps/generic/libc-shadow-stack.h

diff --git a/nptl/descr.h b/nptl/descr.h
index ada6867a19..733e7b0ce6 100644
--- a/nptl/descr.h
+++ b/nptl/descr.h
@@ -384,6 +384,13 @@ struct pthread
   /* This is what the user specified and what we will report.  */
   size_t reported_guardsize;
 
+  /* Shadow stack base pointer.  */
+  void *shadow_stack_base;
+  /* Shadow stack architecture-defined token.  */
+  void *shadow_stack_token;
+  /* Shadow stack size.  */
+  size_t shadow_stack_size;
+
   /* Thread Priority Protection data.  */
   struct priority_protection_data *tpp;
 
diff --git a/nptl/pthread_create.c b/nptl/pthread_create.c
index 19e4ec8064..aec2b717b5 100644
--- a/nptl/pthread_create.c
+++ b/nptl/pthread_create.c
@@ -39,6 +39,7 @@
 #include <clone_internal.h>
 #include <futex-internal.h>
 #include <getrandom-internal.h>
+#include <libc-shadow-stack.h>
 
 #include <shlib-compat.h>
 
@@ -294,6 +295,9 @@ static int create_thread (struct pthread *pd, const struct pthread_attr *attr,
       .stack = (uintptr_t) stackaddr,
       .stack_size = stacksize,
       .tls = (uintptr_t) tp,
+      /* This should point to the architecture-defined token placed
+       * on correctly allocated shadow stack.  */
+      .shadow_stack_token = (uintptr_t) pd->shadow_stack_token,
     };
   int ret = __clone_internal (&args, &start_thread, pd);
   if (__glibc_unlikely (ret == -1))
@@ -578,6 +582,10 @@ start_thread (void *arg)
       pd->setxid_futex = 0;
     }
 
+  /* Shadow stack information required to de-allocate it later.  */
+  void *ss_base = pd->shadow_stack_base;
+  size_t ss_size = pd->shadow_stack_size;
+
   /* If the thread is detached free the TCB.  */
   if (IS_DETACHED (pd))
     /* Free the TCB.  */
@@ -586,6 +594,11 @@ start_thread (void *arg)
   /* Remove the associated name from the thread stack.  */
   name_stack_maps (pd, false);
 
+  /* Free the shadow stack if we allocated one, no more ret
+     is possible after this, must be inline right before exit.  */
+  if (ss_base)
+    INTERNAL_SYSCALL_CALL (munmap, ss_base, ss_size);
+
 out:
   /* We cannot call '_exit' here.  '_exit' will terminate the process.
 
@@ -717,6 +730,15 @@ __pthread_create_2_1 (pthread_t *newthread, const pthread_attr_t *attr,
   pd->schedpolicy = self->schedpolicy;
   pd->schedparam = self->schedparam;
 
+  /* Set up a shadow stack.  */
+  err = THREAD_ALLOC_SHADOW_STACK (pd, stacksize);
+  if (__glibc_unlikely (err != 0))
+    {
+      __nptl_deallocate_stack (pd);
+      retval = err == ENOMEM ? EAGAIN : err;
+      goto out;
+    }
+
   /* Copy the stack guard canary.  */
 #ifdef THREAD_COPY_STACK_GUARD
   THREAD_COPY_STACK_GUARD (pd);
@@ -870,6 +892,13 @@ __pthread_create_2_1 (pthread_t *newthread, const pthread_attr_t *attr,
 	    __futex_abstimed_wait_cancelable64 ((unsigned int *) &pd->tid,
 						tid, 0, NULL, LLL_SHARED);
         }
+      else
+        {
+          /* Free the shadow stack if allocated for a failed thread.  */
+          if (pd->shadow_stack_base)
+            INTERNAL_SYSCALL_CALL (munmap,
+              pd->shadow_stack_base, pd->shadow_stack_size);
+        }
 
       /* State (c) or (d) and we have ownership of PD (see CONCURRENCY
 	 NOTES above).  */
diff --git a/sysdeps/aarch64/libc-shadow-stack.h b/sysdeps/aarch64/libc-shadow-stack.h
new file mode 100644
index 0000000000..10e6792796
--- /dev/null
+++ b/sysdeps/aarch64/libc-shadow-stack.h
@@ -0,0 +1,99 @@
+/* libc-internal interfaces for shadow stack support.
+   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
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef _AARCH64_LIBC_SHADOW_STACK_H
+#define _AARCH64_LIBC_SHADOW_STACK_H 1
+
+#include <stddef.h>
+#include "aarch64-gcs.h"
+
+#define sizeof_field(TYPE, MEMBER) sizeof ((((TYPE *)0)->MEMBER))
+#define offsetofend(TYPE, MEMBER) \
+  (offsetof (TYPE, MEMBER) + sizeof_field (TYPE, MEMBER))
+
+#ifdef HAVE_CLONE3_WRAPPER
+
+#define THREAD_ALLOC_SHADOW_STACK(pd, stacksize) \
+  thread_alloc_shadow_stack (pd, stacksize)
+
+
+/* Support for shadow stack token in clone3 args:
+     0 - not checked,
+     1 - checked, not supported,
+     2 - checked, supported.  */
+
+enum
+{
+  clone3_sh_stack_unknown = 0,
+  clone3_sh_stack_off = 1,
+  clone3_sh_stack_on = 2,
+};
+
+static inline int
+__clone3_supports_shadow_stack (void)
+{
+  struct clone_args args = {
+  /* This guarantees that no thread will actually be created due to
+     strict alignment requirements for the shadow stack token.  */
+    .shadow_stack_token = 1,
+  };
+  /* Size that is required specifically for the shadow_stack_token
+     field of the struct clone_args type.  */
+  const size_t sz = offsetofend (struct clone_args, shadow_stack_token);
+  int saved_errno = errno;
+  /* Function passed to clone3 must be non-null otherwise we will
+     get EINVAL from the Glibc wrapper rather than the kernel.  */
+  int ret = __clone3 (&args, sz, (int (*)(void *))1, NULL);
+  if (ret == -1 && errno == E2BIG)
+    ret = clone3_sh_stack_off;
+  else
+    ret = clone3_sh_stack_on;
+  __set_errno (saved_errno);
+  return ret;
+}
+
+static inline int
+thread_alloc_shadow_stack (struct pthread *pd, size_t stacksize)
+{
+  if (!has_gcs ())
+    return 0;
+  static int clone3_has_shadow_stack = clone3_sh_stack_unknown;
+  if (atomic_load_relaxed (&clone3_has_shadow_stack)
+			   == clone3_sh_stack_unknown)
+    {
+      int res = __clone3_supports_shadow_stack();
+      atomic_store_relaxed (&clone3_has_shadow_stack, res);
+    }
+  if (atomic_load_relaxed (&clone3_has_shadow_stack) == clone3_sh_stack_off)
+    return 0;
+  struct gcs_record gcs;
+  if (__alloc_gcs (stacksize, &gcs) != NULL)
+    {
+      pd->shadow_stack_base = gcs.gcs_base;
+      pd->shadow_stack_token = gcs.gcs_token;
+      pd->shadow_stack_size = gcs.gcs_size;
+    }
+  /* Ignore errors: leave GCS allocation to the kernel.  */
+  return 0;
+}
+
+#else
+#define THREAD_ALLOC_SHADOW_STACK(pd, stacksize) 0
+#endif // HAVE_CLONE3_WRAPPER
+
+#endif
diff --git a/sysdeps/generic/libc-shadow-stack.h b/sysdeps/generic/libc-shadow-stack.h
new file mode 100644
index 0000000000..09f8ca5175
--- /dev/null
+++ b/sysdeps/generic/libc-shadow-stack.h
@@ -0,0 +1,25 @@
+/* libc-internal interfaces for shadow stack support.
+   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
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef _GENERIC_LIBC_SHADOW_STACK_H
+#define _GENERIC_LIBC_SHADOW_STACK_H 1
+
+/* Allocate shadow stack if supported, returns an error code.  */
+#define THREAD_ALLOC_SHADOW_STACK(pd, stacksize) 0
+
+#endif
-- 
2.47.3



More information about the Libc-alpha mailing list