[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