[PATCH v2 2/3] nptl: Add libc allocated shadow stack for new threads

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Tue Aug 26 19:07:25 GMT 2025



On 07/07/25 09:47, 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 | 89 +++++++++++++++++++++++++++++
>  sysdeps/generic/libc-shadow-stack.h | 25 ++++++++
>  4 files changed, 150 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;

Isn't the shadow_stack_token derived from 'shadow_stack_base + shadow_stack_size - (2 * sizeof(void *)',
since we allocate it with SHADOW_STACK_SET_TOKEN | SHADOW_STACK_SET_MARKER?

> +  /* 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 e1033d4ee6..008e1f3684 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))
> @@ -574,6 +578,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.  */
> @@ -582,6 +590,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.
>  
> @@ -713,6 +726,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;
> +    }
> +

It adds a extra mmap (through map_shadow_stack) *and* munmap on every thread
allocation; which kind clashes with the idea of thread stack cache.  Would
it be possible to re-use a previous allocated shadow stack, so we can keep
it allocated, but logical linked the map stack, so a pthread_create will
just need to pop the stack from the cache and re-initialize the token?

>From kernel code it, it seems SHADOW_STACK_SET_TOKEN is just a matter of
setup correct value from the shadow stack itself (with GCS_CAP_VALID_TOKEN)
and issue a 'gcsb dsync'; but I am not fully sure.

>    /* Copy the stack guard canary.  */
>  #ifdef THREAD_COPY_STACK_GUARD
>    THREAD_COPY_STACK_GUARD (pd);
> @@ -866,6 +888,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..b35622f66a
> --- /dev/null
> +++ b/sysdeps/aarch64/libc-shadow-stack.h
> @@ -0,0 +1,89 @@
> +/* 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)

Should we also name the shadow stacks with name_stack_maps (if possible)?

> +
> +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 = 1;
> +  else
> +    ret = 2;

I think we should have an enum here, instead of magic constants.

> +  __set_errno (saved_errno);
> +  return ret;
> +}
> +
> +static inline int
> +thread_alloc_shadow_stack (struct pthread *pd, size_t stacksize)
> +{
> +  if (!has_gcs ())
> +    return 0;
> +  /* Support for shadow stack token in clone3 args:
> +       0 - not checked,
> +       1 - checked, not supported,
> +       2 - checked, supported.  */
> +  static int clone3_has_shadow_stack = 0;
> +  if (atomic_load_relaxed (&clone3_has_shadow_stack) == 0)
> +    {
> +      int res = __clone3_supports_shadow_stack();
> +      atomic_store_relaxed (&clone3_has_shadow_stack, res);
> +    }
> +  if (atomic_load_relaxed (&clone3_has_shadow_stack) == 1)
> +    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;
> +}
> +

Afaiu the clone3 check should be platform neutral; although the shadow
stack creation requires platform specific considerations.  Maybe move the
clone3 to generic support, since I take x86 (and maybe RISCV in the future)
will most likely to use the same support. 

> +#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