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

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Wed Nov 26 13:28:29 GMT 2025



On 07/11/25 06:35, Yury Khrustalev wrote:
> 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;
> +    }
> +

What happens if we do not allocate a shadow stack and pass NULL for 'shadow_stack_token'?
Will kernel not enable shadow stack for PR_SHADOW_STACK_ENABLE or will clone3 fail?

I wonder if we should stop allocating a shadow stack for user-defined stacks, similar on
how we handle guard pages.  My understanding in such situations, users aims to control
exactly the memory usage of threads and GCS adds an extra burden for each thread.

>    /* 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



More information about the Libc-alpha mailing list