From: Carlos O'Donell <carlos@redhat.com>
To: Adhemerval Zanella <adhemerval.zanella@linaro.org>,
libc-alpha@sourceware.org
Cc: Jakob Koschel <jkl820.git@gmail.com>
Subject: Re: [PATCH] [RFC] nptl: Add TLS guard page between thread stack and static TLS [BZ #22850]
Date: Thu, 14 May 2026 17:53:22 -0400 [thread overview]
Message-ID: <6f714e38-aa86-4715-b8e9-95d92d2b53ae@redhat.com> (raw)
In-Reply-To: <20260504175532.423881-1-adhemerval.zanella@linaro.org>
On 5/4/26 1:55 PM, Adhemerval Zanella wrote:
> A stack buffer overflow in a thread's usable stack area can silently
> corrupt the thread's static TLS data (errno, thread-local variables,
> struct pthread itself) because no guard page separates the two regions.
> On architectures that store the stack-smashing canary in the TCB
> (THREAD_SET_STACK_GUARD), an overflow can also corrupt __stack_chk_guard
> itself, completely defeating SSP.
Overall I'm +1 to this RFC. I think you hit on all the right points and it's
a compromise over the solution for bug 11787.
OK. Agreed.
> Every _STACK_GROWS_DOWN architecture is affected. The severity depends
> on whether the architecture stores __stack_chk_guard in the TCB:
>
> Architectures with THREAD_SET_STACK_GUARD — an overflow can overwrite
> the canary and defeat SSP entirely, in addition to corrupting TLS data:
> - x86 / x86_64, i386 (TLS_TCB_AT_TP)
> - s390 / s390x (TLS_TCB_AT_TP)
> - sparc / sparc64 (TLS_TCB_AT_TP)
> - powerpc / powerpc64 (TLS_DTV_AT_TP)
OK. Corrupting TLS data can be just as bad.
>
> Architectures without THREAD_SET_STACK_GUARD — overflow corrupts TLS
> data but cannot defeat SSP via the canary:
> - aarch64, arm, riscv, mips, loongarch64, alpha, arc, csky, m68k,
> microblaze, or1k, sh (TLS_DTV_AT_TP)
OK.
> hppa (_STACK_GROWS_UP) is not affected.
OK. Yes, because
> To harden against this, insert a guard page between the usable stack area
> and the static TLS block. The TLS guard is the same size as the existing
> stack guard (guardsize) and is omitted when guardsize == 0 (user has
> disabled stack guards).
OK. Agreed. This is a good intermediate solution to just splitting up the
two allocations which is something we've talked about in:
"Bug 11787 - Program with large TLS segments fail."
https://sourceware.org/bugzilla/show_bug.cgi?id=11787
> The implementation is mostly on allocatestack.c:
OK. As expected, since that controls all the guard setup.
> - Add tls_guard_position / tls_guard_end_position helpers in
> allocatestack.c for both TLS_TCB_AT_TP (guard sits page-aligned below
> the static TLS area) and TLS_DTV_AT_TP + _STACK_GROWS_DOWN (guard sits
> page-aligned below struct pthread).
>
> - Extend setup_stack_prot and adjust_stack_prot to install, resize, and
> remove the TLS guard alongside the existing stack guard, using
> MADV_GUARD_INSTALL where available and falling back to PROT_NONE. The
> TLS guard size always equals guardsize, so no extra field is needed in
> struct pthread.
>
> - Update pthread_getattr_np to exclude the TLS guard overhead from the
> reported stacksize and stackaddr so that POSIX-visible values remain
> consistent. With the TLS guard active, stackblock_size = S + 2*G and
> the reported stacksize = S (the user-requested size); this mirrors the
> treatment of the regular stack guard, which is also allocated on top of
> the requested size and not subtracted from it.
>
> - Gate the feature on the new ARCH_HAS_TLS_GUARD flag defined in each
> arch's pthreaddef.h, allowing architectures to opt in once their layout
> has been validated. Only x86 is enabled in this patch; all other
> _STACK_GROWS_DOWN architectures listed above are good candidates to opt
> in.
>
> == Alternatives considered ==
>
> -mstack-protector-guard=global (BZ 26817): This GCC flag moves
> __stack_chk_guard from a TLS slot to a global variable, preventing a stack
> overflow from overwriting the canary. It was originally added for kernel
> suppot and it falls short for two reasons:
>
> 1. Scope: it only protects the SSP canary. A stack overflow can still
> silently corrupt errno, other thread-local variables, and other
> struct pthread fields (tid, joinstate, etc.). The TLS guard page
> protects all of static TLS and struct pthread.
OK. Agreed.
>
> 2. ABI incompatibility: switching the canary source from a
> TLS-relative load to a global-variable load is an ABI-visible
> change. Object files and DSOs compiled with the flag cannot safely
> be mixed with those compiled without it; a full recompile of all
> code (including third-party libraries) is required and it will
> require an update glibc to load the binary. The TLS guard page
> approach requires no compiler changes and is fully transparent
> to existing binaries.
>
> Tunable interface:
>
> A glibc.pthread.tls_guard tunable could allow programs to opt in or out
> of the TLS guard at startup. Although possible to extend it to use
> tunables, tying a security harning to a tunable have some drawbacks:
>
> - Tunables are intended for performance and compatibility tradeoffs,
> not security mechanisms. Exposing a hardening control through the
> same interface as, say, glibc.pthread.stack_cache_size conflates
> orthogonal concerns and makes auditing harder.
>
> - An attacker who can set environment variables (e.g. via
> GLIBC_TUNABLES or by exploiting a pre-exec privilege boundary) could
> disable the guard before glibc initialises, negating the protection.
>
> - Consistent, unconditional protection is more reliable than opt-in
> hardening whose coverage depends on deployment configuration.
>
> The existing POSIX guardsize mechanism already provides a clean,
> well-understood way to disable all guard protection: setting
> guardsize == 0 suppresses both the stack guard and the TLS guard.
OK. Agreed.
>
> == Performance considerations ==
>
> On kernels that support MADV_GUARD_INSTALL (Linux 6.13+), the TLS guard
> is installed with an extra madvise call on the existing RW mapping. The
> stack block remains one VMA; no additional mappings are created and the
> per-thread overhead is negligible.
>
> On older kernels that fall back to PROT_NONE, the TLS guard requires two
> extra mprotect calls per thread creation (one to mark the TLS guard
> PROT_NONE, one to mark the TLS data region back to RW after the initial
> all-PROT_NONE allocation). This splits the stack block from 2 VMAs into
> 4 VMAs per thread. The extra VMA pressure is the same as what the
> existing stack guard already imposes and is bounded by the number of live
> threads.
OK.
>
> == Testing ==
>
> - tst-tls-guard: new test that verifies the TLS guard is inaccessible,
> the stack below it is accessible, guardsize == 0 suppresses the guard,
> and behaviour is consistent across both MADV_GUARD_INSTALL and PROT_NONE
> implementations. Run both in isolated subprocesses and in-process to
> exercise the stack cache (including guard resize on cache reuse).
>
> - tst-guard1: updated assertions to account for stack-cache size
> distribution changes introduced by the extra guardsize allocation;
> exact-size cache reuse cannot be guaranteed when ARCH_HAS_TLS_GUARD is
> set, so the stacksize check uses >= rather than ==.
>
> I fully run a regressoin test on x86_64 with a kernel with and without
> MADV_GUARD_INSTALL support. I also did the same for aarch64 by manually
> setting ARCH_HAS_TLS_GUARD to 1.
>
> I also checked on some different kernels for powerpc64le, s390x,
> mips64le and qemu with and without setting ARCH_HAS_TLS_GUARD.
> ---
> nptl/Makefile | 1 +
> nptl/allocatestack.c | 385 ++++++++++++++++++++++++---
> nptl/pthread_getattr_np.c | 30 ++-
> nptl/tst-guard1.c | 25 ++
> nptl/tst-tls-guard.c | 281 +++++++++++++++++++
> sysdeps/aarch64/nptl/pthreaddef.h | 3 +
> sysdeps/alpha/nptl/pthreaddef.h | 3 +
> sysdeps/arc/nptl/pthreaddef.h | 3 +
> sysdeps/arm/nptl/pthreaddef.h | 3 +
> sysdeps/csky/nptl/pthreaddef.h | 3 +
> sysdeps/hppa/nptl/pthreaddef.h | 3 +
> sysdeps/loongarch/nptl/pthreaddef.h | 3 +
> sysdeps/m68k/nptl/pthreaddef.h | 3 +
> sysdeps/microblaze/nptl/pthreaddef.h | 3 +
> sysdeps/mips/nptl/pthreaddef.h | 3 +
> sysdeps/or1k/nptl/pthreaddef.h | 3 +
> sysdeps/powerpc/nptl/pthreaddef.h | 3 +
> sysdeps/riscv/nptl/pthreaddef.h | 3 +
> sysdeps/s390/nptl/pthreaddef.h | 3 +
> sysdeps/sh/nptl/pthreaddef.h | 3 +
> sysdeps/sparc/sparc32/pthreaddef.h | 3 +
> sysdeps/sparc/sparc64/pthreaddef.h | 3 +
> sysdeps/x86/nptl/pthreaddef.h | 3 +
> 23 files changed, 736 insertions(+), 40 deletions(-)
> create mode 100644 nptl/tst-tls-guard.c
>
> diff --git a/nptl/Makefile b/nptl/Makefile
> index 02862d1c04b..72edba04f64 100644
> --- a/nptl/Makefile
> +++ b/nptl/Makefile
> @@ -387,6 +387,7 @@ tests-internal := \
> tst-sem13 \
> tst-setgetname \
> tst-signal7 \
> + tst-tls-guard \
> # tests-internal
>
> xtests = \
> diff --git a/nptl/allocatestack.c b/nptl/allocatestack.c
> index b2ecb001136..d15dbe367d8 100644
> --- a/nptl/allocatestack.c
> +++ b/nptl/allocatestack.c
> @@ -150,6 +150,125 @@ get_cached_stack (size_t *sizep, void **memp)
> return result;
> }
>
> +/*
> + The thread stack layout depends on whether the ABI places the static TLS
> + (TLS_TCB_AT_TP or TLS_TCB_DTV_TP), if the kernel supports lightweight guard
> + pages (MADV_GUARD_INSTALL), and if the ABI adds an extra TLS guard page
> + (ARCH_HAS_TLS_GUARD).
> +
> + For TLS_TCB_AT_TP with !ARCH_HAS_TLS_GUARD:
> +
> + * !MADV_GUARD_INSTALL (2 VMAs):
> +
> + |------------|-------------------------------------------------------------|
> + | R-- | RW- |
> + |------------|-----------------------|--------------------|----------------|
> + | | | | struct pthread |
> + | GUARD PAGE | STACK | | |
> + | | | initial-exec + |----------------|
> + | | | local-exec TLS | tcbhead_t |
> + |------------|-----------------------|--------------------|----------------|
> + low ^
> + thread pointer
> + * MADV_GUARD_INSTALL (1 VMA):
> + |--------------------------------------------------------------------------|
> + | RW |
> + |------------------------------------|--------------------|----------------|
> + | | | struct pthread |
> + | (GUARD PAGE) STACK | | |
> + | | initial-exec + |----------------|
> + | | local-exec TLS | tcbhead_t |
> + |------------------------------------|--------------------|----------------|
> + low ^
> + thread pointer
> +
> + And with ARCH_HAS_TLS_GUARD:
> +
> + * !MADV_GUARD_INSTALL (4 VMAs)
> +
> + |------------|-----------------------|-----------|-------------------------|
> + | R-- | RW- | R-- | RW- |
> + |------------|-----------------------|-----------|---|-----------|---------|
> + | | | | | | struct |
> + | GUARD PAGE | STACK | TLS GUARD |pad| init-exec | pthread |
> + | | | | | + loc-exec|---------|
> + | | | | | TLS |tcbhead_t|
> + |------------|-----------------------|-----------|---|-----------|---------|
> + low ^ ^
> + page-aligned TP
> +
> + * MADV_GUARD_INSTALL (1 VMA):
> + |--------------------------------------------------------------------------|
> + | RW |
> + |------------------------------------------------|---------------|---------|
> + | | | | struct |
> + | (GUARD PAGE) STACK (TLS GUARD) |pad| | pthread |
> + | | | init-exec |---------|
> + | | | + loc-exec|tcbhead_t|
> + |------------------------------------------------|---------------|---------|
> + low ^ ^
> + page-aligned TP
> +
> +
> + For TLS_DTV_AT_TP, the thread pointer (TP) points to the start of the static
> + TLS block, and struct pthread sits just before TP. With !ARCH_HAS_TLS_GUARD:
> +
> +
> + * !MADV_GUARD_INSTALL (2 VMAs):
> +
> + |------------|-------------------------------------------------------------|
> + | R-- | RW- |
> + |------------|----------------------|----------------|---------------------|
> + | | | struct pthread | initial-exec |
> + | GUARD PAGE | STACK | | + local-exec TLS |
> + | | | | |
> + |------------|-------------------------------------------------------------|
> + low ^ high
> + thread pointer
> +
> + * MADV_GUARD_INSTALL (1 VMA):
> +
> + |--------------------------------------------------------------------------|
> + | RW |
> + |--------------------------------------------------------------------------|
> + | |struct pthread | initial-exec |
> + | (GUARD PAGE) STACK | | + local-exec TLS |
> + | | | |
> + |--------------------------------------------------------------------------|
> + low ^ thread pointer high
> +
> +
> + And with ARCH_HAS_TLS_GUARD:
> +
> + * !MADV_GUARD_INSTALL (4 VMAs):
> +
> + |------------|-------------------------------------------------------------|
> + | R-- | RW- | R-- | RW- |
> + |------------|----------------------|-----------|--------------------------|
> + | | | | struct | initial-exec |
> + | GUARD PAGE | STACK | TLS GUARD | pthread | + local-exec |
> + | | | | | |
> + |------------|-------------------------------------------------------------|
> + low ^ thread pointer high
> +
> + * MADV_GUARD_INSTALL (1 VMA):
> +
> + |--------------------------------------------------------------------------|
> + | RW |
> + |--------------------------------------------------------------------------|
> + | | struct | initial-exec |
> + | (GUARD PAGE) STACK (TLS GUARD) | pthread | + local-exec |
> + | | | |
> + |--------------------------------------------------------------------------|
> + low ^ thread pointer high
> +
> +
> + - The TLS guard is placed between the usable stack and the static TLS block
> + and takes space from the thread stack.
> + - Its high end is page-aligned at or below the start of static TLS and it
> + is omitted when guardsize == 0.
> + - TLS_TCB_AT_TP only supports _STACK_GROWS_DOWN. */
> +
> /* Assume support for MADV_ADVISE_GUARD, setup_stack_prot will disable it
> and fallback to ALLOCATE_GUARD_PROT_NONE if the madvise call fails. */
> static int allocate_stack_mode = ALLOCATE_GUARD_MADV_GUARD;
> @@ -184,43 +303,129 @@ guard_position (void *mem, size_t size, size_t guardsize, const struct pthread *
> #endif
> }
>
> +#if ARCH_HAS_TLS_GUARD
> +/* For TLS_TCB_AT_TP the TLS guard sits between the stack and the static TLS
> + area. Its end is page-aligned at or below the start of static TLS; the
> + caller supplies TGUARDSIZE (already a page multiple).
> +
> + For TLS_DTV_AT_TP with a downward-growing stack the TLS guard sits between
> + the usable stack and struct pthread. Its end is page-aligned at or below
> + the start of struct pthread.
> +
> + Returns a pointer to the first byte of the TLS guard, or NULL when
> + TGUARDSIZE is zero. */
> +static __always_inline char *
> +tls_guard_end_position (const struct pthread *pd,
> + size_t tls_static_size_for_stack,
> + size_t pagesize_m1)
> +{
> +# if TLS_TCB_AT_TP
> + char *tls_start = (char *) (pd + 1) - tls_static_size_for_stack;
> + return (char *) ((uintptr_t) tls_start & ~pagesize_m1);
> +# elif TLS_DTV_AT_TP
> +# if _STACK_GROWS_DOWN
> + return (char *) ((uintptr_t) pd & ~pagesize_m1);
> +# else
> +# error "TLS guard page does not support _STACK_GROWS_UP"
> +# endif
> +# endif
> +}
> +
> +static __always_inline char *
> +tls_guard_position (const struct pthread *pd,
> + size_t tls_static_size_for_stack,
> + size_t guardsize,
> + size_t pagesize_m1)
> +{
> + return tls_guard_end_position (pd, tls_static_size_for_stack, pagesize_m1)
> + - guardsize;
> +}
> +#endif
> +
> /* Setup the MEM thread stack of SIZE bytes with the required protection flags
> - along with a guard area of GUARDSIZE size. It first tries with
> - MADV_GUARD_INSTALL, and then fallback to setup the guard area using the
> - extra PROT_NONE mapping. Update PD with the type of guard area setup. */
> + along with a guard area of GUARDSIZE size and, when ARCH_HAS_TLS_GUARD is
> + set, a TLS guard area of the same GUARDSIZE between the stack and the
> + static TLS block.
> +
> + It first tries with MADV_GUARD_INSTALL, and then falls back to setting up
> + the guard areas using extra PROT_NONE mappings. Updates PD with the type
> + of guard area setup. */
> static inline bool
> setup_stack_prot (char *mem, size_t size, struct pthread *pd,
> - size_t guardsize, size_t pagesize_m1)
> + size_t guardsize, size_t tls_static_size_for_stack,
> + size_t pagesize_m1)
> {
> if (__glibc_unlikely (guardsize == 0))
> return true;
>
> char *guard = guard_position (mem, size, guardsize, pd, pagesize_m1);
> +
> +#if ARCH_HAS_TLS_GUARD
> + /* guardsize > 0 is guaranteed by the early return above. */
> + char *tls_guard = tls_guard_position (pd, tls_static_size_for_stack,
> + guardsize, pagesize_m1);
> +#endif
> +
> if (atomic_load_relaxed (&allocate_stack_mode) == ALLOCATE_GUARD_MADV_GUARD)
> {
> - if (__madvise (guard, guardsize, MADV_GUARD_INSTALL) == 0)
> + bool stack_guard_ok = __madvise (guard, guardsize,
> + MADV_GUARD_INSTALL) == 0;
> + bool tls_guard_ok =
> +#if ARCH_HAS_TLS_GUARD
> + __madvise (tls_guard, guardsize, MADV_GUARD_INSTALL) == 0;
> +#else
> + true;
> +#endif
> +
> + if (stack_guard_ok && tls_guard_ok)
> {
> pd->stack_mode = ALLOCATE_GUARD_MADV_GUARD;
> return true;
> }
>
> + /* At least one madvise failed; undo any successful madvise and fall
> + back to PROT_NONE for both this allocation and future ones. The
> + stack memory is already RW (MADV_GUARD mode initial allocation), so
> + we only need to PROT_NONE the guard regions. */
> + if (stack_guard_ok)
> + __madvise (guard, guardsize, MADV_GUARD_REMOVE);
> +#if ARCH_HAS_TLS_GUARD
> + if (tls_guard_ok)
> + __madvise (tls_guard, guardsize, MADV_GUARD_REMOVE);
> +#endif
> +
> /* If madvise fails it means the kernel does not support the guard
> advise (we assume that the syscall is available, guard is page-aligned
> and length is non negative). The stack has already the expected
> - protection flags, so it just need to PROT_NONE the guard area. */
> + protection flags, so it just need to PROT_NONE the guard areas. */
> atomic_store_relaxed (&allocate_stack_mode, ALLOCATE_GUARD_PROT_NONE);
> +
> if (__mprotect (guard, guardsize, PROT_NONE) != 0)
> return false;
> +#if ARCH_HAS_TLS_GUARD
> + if (__mprotect (tls_guard, guardsize, PROT_NONE) != 0)
> + return false;
> +#endif
> }
> else
> {
> const int prot = GL(dl_stack_prot_flags);
> char *guardend = guard + guardsize;
> #if _STACK_GROWS_DOWN
> - /* As defined at guard_position, for architectures with downward stack
> - the guard page is always at start of the allocated area. */
> + /* For architectures with downward stack the guard page is always at the
> + start of the allocated area. */
> +# if ARCH_HAS_TLS_GUARD
> + /* Make the stack area RW (between stack guard and TLS guard). */
> + if (__mprotect (guardend, tls_guard - guardend, prot) != 0)
> + return false;
> + /* Make the pad + TLS + TCB area RW (after TLS guard). */
> + char *tls_guardend = tls_guard + guardsize;
> + if (__mprotect (tls_guardend, (mem + size) - tls_guardend, prot) != 0)
> + return false;
> +# else
> if (__mprotect (guardend, size - guardsize, prot) != 0)
> return false;
> +# endif /* ARCH_HAS_TLS_GUARD */
> #else
> size_t mprots1 = (uintptr_t) guard - (uintptr_t) mem;
> if (__mprotect (mem, mprots1, prot) != 0)
> @@ -235,11 +440,14 @@ setup_stack_prot (char *mem, size_t size, struct pthread *pd,
> return true;
> }
>
> -/* Update the guard area of the thread stack MEM of size SIZE with the new
> - GUARDISZE. It uses the method defined by PD stack_mode. */
> +/* Update the guard areas of the thread stack MEM of size SIZE with the new
> + GUARDSIZE. Uses the method defined by PD stack_mode. When
> + ARCH_HAS_TLS_GUARD is set the TLS guard sits between the stack and struct
> + pthread and always has the same size as the stack guard. */
> static inline bool
> adjust_stack_prot (char *mem, size_t size, struct pthread *pd,
> - size_t guardsize, size_t pagesize_m1)
> + size_t guardsize,
> + size_t tls_static_size_for_stack, size_t pagesize_m1)
> {
> /* The required guard area is larger than the current one. For
> _STACK_GROWS_DOWN it means the guard should increase as:
> @@ -252,27 +460,56 @@ adjust_stack_prot (char *mem, size_t size, struct pthread *pd,
> |stack---------------------------|guard|-----|
> |stack--------------------|new guard---|-----|
>
> - Both madvise and mprotect allows overlap the required region,
> + With ARCH_HAS_TLS_GUARD the TLS guard also need to be increased
> + (TLS guard only supports _STACK_GROWS_DOWN):
> +
> + |guard|----------------------|tls_guard|stack|
> + |new guard--|--------------|new guard--|stack|
> +
> + Both madvise and mprotect allow overlapping the required region,
> so use the new guard placement with the new size. */
> if (guardsize > pd->guardsize)
> {
> /* There was no need to previously setup a guard page, so we need
> to check whether the kernel supports guard advise. */
> char *guard = guard_position (mem, size, guardsize, pd, pagesize_m1);
> +
> +#if ARCH_HAS_TLS_GUARD
> + /* guardsize > 0 is guaranteed by the outer condition. */
> + char *tls_guard = tls_guard_position (pd, tls_static_size_for_stack,
> + guardsize, pagesize_m1);
> +#endif
> +
> if (atomic_load_relaxed (&allocate_stack_mode)
> == ALLOCATE_GUARD_MADV_GUARD)
> {
> - if (__madvise (guard, guardsize, MADV_GUARD_INSTALL) == 0)
> + bool stack_ok = __madvise (guard, guardsize,
> + MADV_GUARD_INSTALL) == 0;
> + bool tls_ok =
> +#if ARCH_HAS_TLS_GUARD
> + __madvise (tls_guard, guardsize, MADV_GUARD_INSTALL) == 0;
> +#else
> + true;
> +#endif
> + if (stack_ok && tls_ok)
> {
> pd->stack_mode = ALLOCATE_GUARD_MADV_GUARD;
> return true;
> }
> + if (stack_ok)
> + __madvise (guard, guardsize, MADV_GUARD_REMOVE);
> atomic_store_relaxed (&allocate_stack_mode,
> ALLOCATE_GUARD_PROT_NONE);
> }
>
> pd->stack_mode = ALLOCATE_GUARD_PROT_NONE;
> - return __mprotect (guard, guardsize, PROT_NONE) == 0;
> + if (__mprotect (guard, guardsize, PROT_NONE) != 0)
> + return false;
> +#if ARCH_HAS_TLS_GUARD
> + if (__mprotect (tls_guard, guardsize, PROT_NONE) != 0)
> + return false;
> +#endif
> + return true;
> }
> /* The current guard area is larger than the required one. For
> _STACK_GROWS_DOWN is means change the guard as:
> @@ -285,28 +522,48 @@ adjust_stack_prot (char *mem, size_t size, struct pthread *pd,
> |stack---------------------|guard-------|---|
> |stack------------------------|new guard|---|
>
> + With ARCH_HAS_TLS_GUARD the TLS guard also need to be decreased
> + (TLS guard only supports _STACK_GROWS_DOWN):
> +
> + |guard-------|----------|tls_guard-----|stack|
> + |new guard|------------------|new guard|stack|
> +
> For ALLOCATE_GUARD_MADV_GUARD it means remove the slack area
> (disjointed region of guard and new guard), while for
> ALLOCATE_GUARD_PROT_NONE it requires to mprotect it with the stack
> - protection flags. */
> + protection flags. Same logic applies to the TLS guard. */
> else if (pd->guardsize > guardsize)
> {
> - size_t slacksize = pd->guardsize - guardsize;
> if (pd->stack_mode == ALLOCATE_GUARD_MADV_GUARD)
> {
> + size_t slacksize = pd->guardsize - guardsize;
> void *slack =
> #if _STACK_GROWS_DOWN
> mem + guardsize;
> #else
> guard_position (mem, size, pd->guardsize, pd, pagesize_m1);
> #endif
> - return __madvise (slack, slacksize, MADV_GUARD_REMOVE) == 0;
> + bool stack_ok = __madvise (slack, slacksize, MADV_GUARD_REMOVE) == 0;
> +
> +#if ARCH_HAS_TLS_GUARD
> + char *tls_guard_end =
> + tls_guard_end_position (pd, tls_static_size_for_stack,
> + pagesize_m1);
> + char *old_tls_guard_start = tls_guard_end - pd->guardsize;
> + bool tls_ok = __madvise (old_tls_guard_start, slacksize,
> + MADV_GUARD_REMOVE) == 0;
> + return stack_ok && tls_ok;
> +#else
> + return stack_ok;
> +#endif
> }
> else if (pd->stack_mode == ALLOCATE_GUARD_PROT_NONE)
> {
> const int prot = GL(dl_stack_prot_flags);
> + bool stack_ok = true;
> #if _STACK_GROWS_DOWN
> - return __mprotect (mem + guardsize, slacksize, prot) == 0;
> + size_t slacksize = pd->guardsize - guardsize;
> + stack_ok = __mprotect (mem + guardsize, slacksize, prot) == 0;
> #else
> char *new_guard = (char *)(((uintptr_t) pd - guardsize)
> & ~pagesize_m1);
> @@ -316,7 +573,20 @@ adjust_stack_prot (char *mem, size_t size, struct pthread *pd,
> to the nearest page the size difference might be zero. */
> if (new_guard > old_guard
> && __mprotect (old_guard, new_guard - old_guard, prot) != 0)
> - return false;
> + stack_ok = false;
> +#endif
> +
> +#if ARCH_HAS_TLS_GUARD
> + size_t tls_slacksize = pd->guardsize - guardsize;
> + char *tls_guard_end =
> + tls_guard_end_position (pd, tls_static_size_for_stack,
> + pagesize_m1);
> + char *old_tls_guard_start = tls_guard_end - pd->guardsize;
> + bool tls_ok = __mprotect (old_tls_guard_start, tls_slacksize,
> + prot) == 0;
> + return stack_ok && tls_ok;
> +#else
> + return stack_ok;
> #endif
> }
> }
> @@ -490,14 +760,29 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
> reported_guardsize = guardsize;
> if (guardsize > 0 && guardsize < ARCH_MIN_GUARD_SIZE)
> guardsize = ARCH_MIN_GUARD_SIZE;
> - if (guardsize < attr->guardsize || size + guardsize < guardsize)
> + if (guardsize < attr->guardsize
> + || INT_ADD_WRAPV (size, guardsize, &size))
> /* Arithmetic overflow. */
> return EINVAL;
> - size += guardsize;
> - if (__builtin_expect (size < ((guardsize + tls_static_size_for_stack
> - + MINIMAL_REST_STACK + pagesize_m1)
> - & ~pagesize_m1),
> - 0))
> +
> +#if ARCH_HAS_TLS_GUARD
> + /* A TLS guard of the same size as the stack guard is placed between the
> + stack and the static TLS (or struct pthread for TLS_DTV_AT_TP) to
> + harden against stack buffer overflows that overwrite TLS data. */
> + if (INT_ADD_WRAPV (size, guardsize, &size))
> + return EINVAL;
> +#endif
> +
> + size_t min_size;
> + if (INT_ADD_WRAPV (guardsize, tls_static_size_for_stack, &min_size)
> +#if ARCH_HAS_TLS_GUARD
> + || INT_ADD_WRAPV (min_size, guardsize, &min_size)
> +#endif
> + || INT_ADD_WRAPV (min_size, (size_t) MINIMAL_REST_STACK, &min_size)
> + || INT_ADD_WRAPV (min_size, pagesize_m1, &min_size))
> + return EINVAL;
> + min_size &= ~pagesize_m1;
> + if (__glibc_unlikely (size < min_size))
> /* The stack is too small (or the guard too large). */
> return EINVAL;
>
> @@ -531,8 +816,9 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
> - TLS_PRE_TCB_SIZE);
> #endif
>
> - /* Now mprotect the required region excluding the guard area. */
> - if (!setup_stack_prot (mem, size, pd, guardsize, pagesize_m1))
> + /* Now mprotect the required region excluding the guard areas. */
> + if (!setup_stack_prot (mem, size, pd, guardsize,
> + tls_static_size_for_stack, pagesize_m1))
> {
> __munmap (mem, size);
> return errno;
> @@ -593,9 +879,10 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
> which will be read next. */
> }
>
> - /* Create or resize the guard area if necessary on an already
> + /* Create or resize the guard areas if necessary on an already
> allocated stack. */
> - if (!adjust_stack_prot (mem, size, pd, guardsize, pagesize_m1))
> + if (!adjust_stack_prot (mem, size, pd, guardsize,
> + tls_static_size_for_stack, pagesize_m1))
> {
> lll_lock (GL (dl_stack_cache_lock), LLL_PRIVATE);
>
> @@ -645,11 +932,23 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
>
> void *stacktop;
>
> + /* The stack begins before the TLS guard (if any), static TLS, and TCB.
> + When a TLS guard is in use, its start (page-aligned below the static TLS)
> + is the top of the usable stack.
> +
> + With TLS_DTV_AT_TP and a downward-growing stack, struct pthread sits just
> + above the usable stack. When a TLS guard is in use it occupies the pages
> + between the usable stack top and struct pthread. */
> +#if ARCH_HAS_TLS_GUARD
> + if (pd->guardsize > 0)
> + stacktop = tls_guard_position (pd, tls_static_size_for_stack,
> + pd->guardsize, pagesize_m1);
> + else
> +#endif
> #if TLS_TCB_AT_TP
> - /* The stack begins before the TCB and the static TLS block. */
> - stacktop = ((char *) (pd + 1) - tls_static_size_for_stack);
> + stacktop = (char *) (pd + 1) - tls_static_size_for_stack;
> #elif TLS_DTV_AT_TP
> - stacktop = (char *) (pd - 1);
> + stacktop = (char *) (pd - 1);
> #endif
>
> *stacksize = stacktop - pd->stackblock;
> @@ -673,8 +972,8 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
> static void
> name_stack_maps (struct pthread *pd, bool set)
> {
> - size_t adjust = pd->stack_mode == ALLOCATE_GUARD_PROT_NONE ?
> - pd->guardsize : 0;
> + enum allocate_stack_mode_t mode = pd->stack_mode;
> + size_t adjust = mode == ALLOCATE_GUARD_PROT_NONE ? pd->guardsize : 0;
> #if _STACK_GROWS_DOWN
> void *stack = pd->stackblock + adjust;
> #else
> @@ -682,6 +981,24 @@ name_stack_maps (struct pthread *pd, bool set)
> #endif
> size_t stacksize = pd->stackblock_size - adjust;
>
> +#if ARCH_HAS_TLS_GUARD
> + /* With PROT_NONE guards and ARCH_HAS_TLS_GUARD, the TLS guard and TLS
> + data are separate VMAs (mprotect splits the mapping). Naming the full
> + [stack, stack+stacksize) region would apply the name to all three VMAs
> + (usable stack, TLS guard, TLS data), making the test count 3x too many
> + "pthread stack:" entries. Limit the name to the usable stack only.
> + With MADV_GUARD_INSTALL the whole block remains one VMA, so no
> + adjustment is needed there. */
> + if (mode == ALLOCATE_GUARD_PROT_NONE && pd->guardsize > 0)
> + {
> + size_t tls_static_size_for_stack = __nptl_tls_static_size_for_stack ();
> + size_t pagesize_m1 = __getpagesize () - 1;
> + char *tls_guard = tls_guard_position (pd, tls_static_size_for_stack,
> + pd->guardsize, pagesize_m1);
> + stacksize = (char *) tls_guard - (char *) stack;
> + }
> +#endif
> +
> if (!set)
> __set_vma_name (stack, stacksize, " glibc: unused stack");
> else
> diff --git a/nptl/pthread_getattr_np.c b/nptl/pthread_getattr_np.c
> index b0d2343a59a..359fd8932ff 100644
> --- a/nptl/pthread_getattr_np.c
> +++ b/nptl/pthread_getattr_np.c
> @@ -61,15 +61,33 @@ __pthread_getattr_np (pthread_t thread_id, pthread_attr_t *attr)
> /* The sizes are subject to alignment. */
> if (__glibc_likely (thread->stackblock != NULL))
> {
> - /* The stack size reported to the user should not include the
> - guard size. */
> - iattr->stacksize = thread->stackblock_size - thread->guardsize;
> + /* The reported stack size excludes all guard pages. The TLS guard
> + (when ARCH_HAS_TLS_GUARD and guardsize > 0) is allocated on top of
> + the user-requested size, just like the regular stack guard, so it is
> + also excluded. stackaddr is kept at the top of the user-requested
> + region (excluding the TLS guard overhead). */
> +#if ARCH_HAS_TLS_GUARD
> + if (thread->guardsize > 0)
> + {
> + iattr->stacksize = thread->stackblock_size - 2 * thread->guardsize;
> +# if _STACK_GROWS_DOWN
> + iattr->stackaddr = (char *) thread->stackblock
> + + thread->stackblock_size - thread->guardsize;
> +# else
> +# error "TLS guard page does not support _STACK_GROWS_UP"
> +# endif
> + }
> + else
> +#endif /* ARCH_HAS_TLS_GUARD */
> + {
> + iattr->stacksize = thread->stackblock_size - thread->guardsize;
> #if _STACK_GROWS_DOWN
> - iattr->stackaddr = (char *) thread->stackblock
> - + thread->stackblock_size;
> + iattr->stackaddr = (char *) thread->stackblock
> + + thread->stackblock_size;
> #else
> - iattr->stackaddr = (char *) thread->stackblock;
> + iattr->stackaddr = (char *) thread->stackblock;
> #endif
> + }
> }
> else
> {
> diff --git a/nptl/tst-guard1.c b/nptl/tst-guard1.c
> index b97ad23de41..3ac791a13fb 100644
> --- a/nptl/tst-guard1.c
> +++ b/nptl/tst-guard1.c
> @@ -17,6 +17,7 @@
> <https://www.gnu.org/licenses/>. */
>
> #include <array_length.h>
> +#include <pthread.h>
> #include <pthreaddef.h>
> #include <setjmp.h>
> #include <stackinfo.h>
> @@ -149,14 +150,38 @@ tf (void *closure)
>
> if (args != NULL)
> {
> + /* pthread_getattr_np must report at least the requested stacksize
> + and for a freshly allocated stack it reports the exact requested
> + size. With ARCH_HAS_TLS_GUARD the value may be larger when the
> + thread reuses a cached stack that is bigger than strictly needed
> + (because the TLS guard adds an extra guardsize to every allocation,
> + shifting the cached-size distribution so that an exact-fit hit is
> + unlikely).
> +
> + Without ARCH_HAS_TLS_GUARD the test cases are designed to always
> + reuse an exact-fit cached stack, so exact equality holds there. */
> +#if ARCH_HAS_TLS_GUARD
> + TEST_VERIFY (s.stacksize >= adjust_stacksize (args->stacksize));
> +#else
> TEST_COMPARE (adjust_stacksize (args->stacksize), s.stacksize);
> +#endif
> TEST_COMPARE (args->guardsize, s.guardsize);
> }
>
> /* Ensure we can access the stack area. */
> TEST_COMPARE (try_read_buf (s.stack), true);
> TEST_COMPARE (try_read_buf (&s.stack[s.stacksize / 2]), true);
> + /* With ARCH_HAS_TLS_GUARD and a guard installed, the reported stacksize
> + spans the TLS guard area too (it is part of the overhead like the stack
> + guard). The last byte may land inside the TLS guard when guardsize >=
> + tls_static_size; only check s.stacksize - 1 when no TLS guard is
> + active to avoid a spurious fault. */
> +#if ARCH_HAS_TLS_GUARD
> + if (s.guardsize == 0)
> + TEST_COMPARE (try_read_buf (&s.stack[s.stacksize - 1]), true);
> +#else
> TEST_COMPARE (try_read_buf (&s.stack[s.stacksize - 1]), true);
> +#endif
>
> /* Check if accessing the guard area results in SIGSEGV. */
> if (s.guardsize > 0)
> diff --git a/nptl/tst-tls-guard.c b/nptl/tst-tls-guard.c
> new file mode 100644
> index 00000000000..f416d88e31e
> --- /dev/null
> +++ b/nptl/tst-tls-guard.c
> @@ -0,0 +1,281 @@
> +/* Tests for the TLS guard page between stack and static TLS.
> + Copyright (C) 2026 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/>. */
> +
> +/* For architectures that set ARCH_HAS_TLS_GUARD, glibc inserts a guard page
> + between the thread stack and the static TLS / struct pthread area.
> + This test verifies:
> + 1. The TLS guard is inaccessible (traps on read/write).
> + 2. The stack area below the TLS guard is accessible.
> + 3. When guardsize == 0 no TLS guard is added.
> + 4. The behaviour is consistent across kernel support for
> + MADV_GUARD_INSTALL and the PROT_NONE fallback. */
> +
> +#include <array_length.h>
> +#include <pthread.h>
> +#include <pthreaddef.h>
> +#include <setjmp.h>
> +#include <stackinfo.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <support/capture_subprocess.h>
> +#include <support/check.h>
> +#include <support/check_mem_access.h>
> +#include <support/test-driver.h>
> +#include <support/xsignal.h>
> +#include <support/xthread.h>
> +#include <support/xunistd.h>
> +#include <tls.h>
> +
> +#if ARCH_HAS_TLS_GUARD
> +
> +static long int pagesz;
> +
> +/* Locate the TLS guard region by probing pages backward from STACK_TOP
> + (which equals (char *)stack + stacksize from pthread_attr_getstack, i.e.
> + approximately the first byte above the TLS guard) down to SCAN_LIMIT.
> + Works for both MADV_GUARD_INSTALL (guard stays inside one RW VMA but
> + pages are inaccessible) and the PROT_NONE fallback (separate ---p VMA).
> + Returns 1 and fills the output pointers on success, 0 if no inaccessible
> + region is found within the scan range. */
> +static int
> +find_tls_guard (char *stack_top, char *scan_limit, long pagesize,
> + char **guard_start_out, size_t *guard_size_out)
> +{
> + char *guard_end = NULL;
> + char *guard_start = NULL;
> + char *p = stack_top;
> + /* Probe at most 128 pages. The TLS guard sits within a few pages of
> + stack_top (static-TLS alignment overhead is typically 1–2 pages);
> + 128 pages gives ample headroom for large-TLS workloads. */
> + char *stop = stack_top - 128 * pagesize;
> + if (stop < scan_limit)
> + stop = scan_limit;
> +
> + while (p > stop)
> + {
> + p -= pagesize;
> + if (!check_mem_access (p, false))
> + {
> + if (guard_end == NULL)
> + guard_end = p + pagesize;
> + guard_start = p;
> + }
> + else if (guard_end != NULL)
> + break;
> + }
> +
> + if (guard_end == NULL)
> + return 0;
> +
> + *guard_start_out = guard_start;
> + *guard_size_out = (size_t) (guard_end - guard_start);
> + return 1;
> +}
> +
> +struct thread_args
> +{
> + int expect_tls_guard; /* 1: TLS guard expected, 0: not expected */
> +};
> +
> +static void *
> +tf (void *closure)
> +{
> + struct thread_args *args = closure;
> +
> + pthread_attr_t attr;
> + TEST_VERIFY_EXIT (pthread_getattr_np (pthread_self (), &attr) == 0);
> +
> + void *stack;
> + size_t stacksize;
> + TEST_VERIFY_EXIT (pthread_attr_getstack (&attr, &stack, &stacksize) == 0);
> +
> + size_t guardsize;
> + TEST_VERIFY_EXIT (pthread_attr_getguardsize (&attr, &guardsize) == 0);
> + if (guardsize != 0 && guardsize < ARCH_MIN_GUARD_SIZE)
> + guardsize = ARCH_MIN_GUARD_SIZE;
> + pthread_attr_destroy (&attr);
> +
> + /* Verify the stack area is accessible (start and midpoint are always in
> + the usable stack region below the TLS guard). */
> + TEST_VERIFY (check_mem_access (stack, false));
> + TEST_VERIFY (check_mem_access ((char *) stack + stacksize / 4, false));
> +
> + /* pthread_getattr_np reports stacksize as the user-requested size (both
> + the stack guard and the TLS guard are excluded) and stackaddr as the
> + top of that region. For _STACK_GROWS_DOWN:
> + stack == stackblock + guardsize (low end of usable stack)
> + stack + stacksize + guardsize == end of the mmap'd allocation
> + Scan from the end of the allocation downward; the TLS guard is the
> + first inaccessible region encountered. */
> + char *scan_top = (char *) stack + stacksize + guardsize;
> + char *guard_start = NULL;
> + size_t guard_size = 0;
> + int found = find_tls_guard (scan_top, (char *) stack,
> + pagesz, &guard_start, &guard_size);
> +
> + if (args->expect_tls_guard)
> + {
> + TEST_VERIFY (found != 0);
> + if (!found)
> + return NULL;
> +
> + if (test_verbose)
> + printf ("debug: [tid=%jd] TLS guard at %p+%#zx, guardsize=%#zx\n",
> + (intmax_t) gettid (), guard_start, guard_size, guardsize);
> +
> + /* The TLS guard should be the same size as the stack guard. */
> + TEST_COMPARE (guard_size, guardsize);
> +
> + /* Every byte of the TLS guard must be inaccessible. */
> + TEST_VERIFY (!check_mem_access (guard_start, false));
> + TEST_VERIFY (!check_mem_access (guard_start, true));
> + TEST_VERIFY (!check_mem_access (guard_start + guard_size / 2, false));
> + TEST_VERIFY (!check_mem_access (guard_start + guard_size - 1, false));
> +
> + /* The byte just below the TLS guard must be accessible (stack area). */
> + TEST_VERIFY (check_mem_access (guard_start - 1, false));
> + }
> + else
> + {
> + /* With guardsize == 0 there must be no TLS guard. */
> + TEST_VERIFY (found == 0);
> + }
> +
> + return NULL;
> +}
> +
> +/* Test with default pthread attributes (guard present). */
> +static void
> +do_test_default (void *closure)
> +{
> + struct thread_args args = {
> + .expect_tls_guard = 1
> + };
> + pthread_t t = xpthread_create (NULL, tf, &args);
> + void *status = xpthread_join (t);
> + TEST_VERIFY (status == 0);
> +}
> +
> +/* Test with explicit guard size > 0 (guard present). */
> +static void
> +do_test_with_guard (void *closure)
> +{
> + pthread_attr_t attr;
> + xpthread_attr_init (&attr);
> + xpthread_attr_setstacksize (&attr, 2 * 1024 * 1024);
> + xpthread_attr_setguardsize (&attr, pagesz);
> +
> + struct thread_args args = {
> + .expect_tls_guard = 1
> + };
> + pthread_t t = xpthread_create (&attr, tf, &args);
> + void *status = xpthread_join (t);
> + TEST_VERIFY (status == 0);
> +
> + xpthread_attr_destroy (&attr);
> +}
> +
> +/* Test with guardsize == 0 (no TLS guard expected). */
> +static void
> +do_test_no_guard (void *closure)
> +{
> + pthread_attr_t attr;
> + xpthread_attr_init (&attr);
> + /* Use a larger stack so the thread still fits after removing the guard. */
> + xpthread_attr_setstacksize (&attr, 2 * 1024 * 1024);
> + xpthread_attr_setguardsize (&attr, 0);
> +
> + struct thread_args args = {
> + .expect_tls_guard = 0
> + };
> + pthread_t t = xpthread_create (&attr, tf, &args);
> + void *status = xpthread_join (t);
> + TEST_VERIFY (status == 0);
> +
> + xpthread_attr_destroy (&attr);
> +}
> +
> +/* Test guard after cache reuse: create without guard, then with guard. */
> +static void
> +do_test_cache_reuse (void *closure)
> +{
> + /* First thread: no guard, puts stack in cache. */
> + do_test_no_guard (NULL);
> +
> + /* Second thread: default guard, retrieves cached stack and installs guard. */
> + do_test_default (NULL);
> +
> + /* Third thread: larger guard, exercises guard expansion. */
> + pthread_attr_t attr;
> + xpthread_attr_init (&attr);
> + xpthread_attr_setstacksize (&attr, 2 * 1024 * 1024);
> + xpthread_attr_setguardsize (&attr, 2 * pagesz);
> +
> + struct thread_args args = {
> + .expect_tls_guard = 1
> + };
> + pthread_t t = xpthread_create (&attr, tf, &args);
> + xpthread_join (t);
> + xpthread_attr_destroy (&attr);
> +}
> +#endif /* ARCH_HAS_TLS_GUARD */
> +
> +static int
> +do_test (void)
> +{
> +#if ARCH_HAS_TLS_GUARD
> + pagesz = sysconf (_SC_PAGESIZE);
> +
> + static const struct
> + {
> + const char *descr;
> + void (*fn) (void *);
> + } tests[] = {
> + { "default attributes (TLS guard expected)", do_test_default },
> + { "explicit guard size > 0 (TLS guard expected)", do_test_with_guard },
> + { "guardsize == 0 (no TLS guard expected)", do_test_no_guard },
> + { "cache reuse: no-guard then with-guard", do_test_cache_reuse },
> + };
> +
> + /* Run each sub-test in a forked subprocess for isolation. */
> + for (size_t i = 0; i < array_length (tests); i++)
> + {
> + printf ("info: fork: %s\n", tests[i].descr);
> + struct support_capture_subprocess r =
> + support_capture_subprocess (tests[i].fn, NULL);
> + support_capture_subprocess_check (&r, tests[i].descr, 0,
> + sc_allow_none);
> + support_capture_subprocess_free (&r);
> + }
> +
> + /* Then run them without fork so the stack cache is exercised. */
> + for (size_t i = 0; i < array_length (tests); i++)
> + {
> + printf ("info: %s\n", tests[i].descr);
> + tests[i].fn (NULL);
> + }
> +
> + return 0;
> +#else
> + puts ("SKIP: TLS guard not enabled for this architecture");
> + return EXIT_SUCCESS;
> +#endif
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/sysdeps/aarch64/nptl/pthreaddef.h b/sysdeps/aarch64/nptl/pthreaddef.h
> index 93d3e267a43..9ef8d0f0cca 100644
> --- a/sysdeps/aarch64/nptl/pthreaddef.h
> +++ b/sysdeps/aarch64/nptl/pthreaddef.h
> @@ -22,6 +22,9 @@
> /* Minimum guard size. */
> #define ARCH_MIN_GUARD_SIZE (64 * 1024)
>
> +/* TLS guard page between thread stack and static TLS. */
> +#define ARCH_HAS_TLS_GUARD 0
> +
> /* Required stack pointer alignment at beginning. */
> #define STACK_ALIGN 16
>
> diff --git a/sysdeps/alpha/nptl/pthreaddef.h b/sysdeps/alpha/nptl/pthreaddef.h
> index 72d2c281cc9..d1f4e2e1c0b 100644
> --- a/sysdeps/alpha/nptl/pthreaddef.h
> +++ b/sysdeps/alpha/nptl/pthreaddef.h
> @@ -21,6 +21,9 @@
> /* Minimum guard size. */
> #define ARCH_MIN_GUARD_SIZE 0
>
> +/* TLS guard page between thread stack and static TLS. */
> +#define ARCH_HAS_TLS_GUARD 0
> +
> /* Required stack pointer alignment at beginning. The ABI requires 16. */
> #define STACK_ALIGN 16
>
> diff --git a/sysdeps/arc/nptl/pthreaddef.h b/sysdeps/arc/nptl/pthreaddef.h
> index 5c374b6bf27..fe22afc4591 100644
> --- a/sysdeps/arc/nptl/pthreaddef.h
> +++ b/sysdeps/arc/nptl/pthreaddef.h
> @@ -22,6 +22,9 @@
> /* Minimum guard size. */
> #define ARCH_MIN_GUARD_SIZE 0
>
> +/* TLS guard page between thread stack and static TLS. */
> +#define ARCH_HAS_TLS_GUARD 0
> +
> /* Required stack pointer alignment at beginning. */
> #define STACK_ALIGN 4
>
> diff --git a/sysdeps/arm/nptl/pthreaddef.h b/sysdeps/arm/nptl/pthreaddef.h
> index a67721e7461..53eca415197 100644
> --- a/sysdeps/arm/nptl/pthreaddef.h
> +++ b/sysdeps/arm/nptl/pthreaddef.h
> @@ -21,6 +21,9 @@
> /* Minimum guard size. */
> #define ARCH_MIN_GUARD_SIZE 0
>
> +/* TLS guard page between thread stack and static TLS. */
> +#define ARCH_HAS_TLS_GUARD 0
> +
> /* Required stack pointer alignment at beginning. SSE requires 16
> bytes. */
> #define STACK_ALIGN 16
> diff --git a/sysdeps/csky/nptl/pthreaddef.h b/sysdeps/csky/nptl/pthreaddef.h
> index e57286423a3..19130f26cda 100644
> --- a/sysdeps/csky/nptl/pthreaddef.h
> +++ b/sysdeps/csky/nptl/pthreaddef.h
> @@ -22,6 +22,9 @@
> /* Minimum guard size. */
> #define ARCH_MIN_GUARD_SIZE 0
>
> +/* TLS guard page between thread stack and static TLS. */
> +#define ARCH_HAS_TLS_GUARD 0
> +
> /* Required stack pointer alignment at beginning. */
> #define STACK_ALIGN 8
>
> diff --git a/sysdeps/hppa/nptl/pthreaddef.h b/sysdeps/hppa/nptl/pthreaddef.h
> index 5b54f4864f7..7640b271695 100644
> --- a/sysdeps/hppa/nptl/pthreaddef.h
> +++ b/sysdeps/hppa/nptl/pthreaddef.h
> @@ -21,6 +21,9 @@
> /* Minimum guard size. */
> #define ARCH_MIN_GUARD_SIZE 0
>
> +/* TLS guard page between thread stack and static TLS. */
> +#define ARCH_HAS_TLS_GUARD 0
> +
> /* Required stack pointer alignment at beginning. */
> #define STACK_ALIGN 64
>
> diff --git a/sysdeps/loongarch/nptl/pthreaddef.h b/sysdeps/loongarch/nptl/pthreaddef.h
> index e500d60b777..df45b84957e 100644
> --- a/sysdeps/loongarch/nptl/pthreaddef.h
> +++ b/sysdeps/loongarch/nptl/pthreaddef.h
> @@ -22,6 +22,9 @@
> /* Minimum guard size. */
> #define ARCH_MIN_GUARD_SIZE 0
>
> +/* TLS guard page between thread stack and static TLS. */
> +#define ARCH_HAS_TLS_GUARD 0
> +
> /* Required stack pointer alignment at beginning. */
> #define STACK_ALIGN 16
>
> diff --git a/sysdeps/m68k/nptl/pthreaddef.h b/sysdeps/m68k/nptl/pthreaddef.h
> index d29a061b745..dad964b98cc 100644
> --- a/sysdeps/m68k/nptl/pthreaddef.h
> +++ b/sysdeps/m68k/nptl/pthreaddef.h
> @@ -21,6 +21,9 @@
> /* Minimum guard size. */
> #define ARCH_MIN_GUARD_SIZE 0
>
> +/* TLS guard page between thread stack and static TLS. */
> +#define ARCH_HAS_TLS_GUARD 0
> +
> /* Required stack pointer alignment at beginning. */
> #define STACK_ALIGN 16
>
> diff --git a/sysdeps/microblaze/nptl/pthreaddef.h b/sysdeps/microblaze/nptl/pthreaddef.h
> index 10c7257a574..9c7f7d96812 100644
> --- a/sysdeps/microblaze/nptl/pthreaddef.h
> +++ b/sysdeps/microblaze/nptl/pthreaddef.h
> @@ -25,6 +25,9 @@
> /* Minimum guard size. */
> #define ARCH_MIN_GUARD_SIZE 0
>
> +/* TLS guard page between thread stack and static TLS. */
> +#define ARCH_HAS_TLS_GUARD 0
> +
> /* Required stack pointer alignment at beginning. */
> #define STACK_ALIGN 16
>
> diff --git a/sysdeps/mips/nptl/pthreaddef.h b/sysdeps/mips/nptl/pthreaddef.h
> index 518e8486b91..ec57cf4179e 100644
> --- a/sysdeps/mips/nptl/pthreaddef.h
> +++ b/sysdeps/mips/nptl/pthreaddef.h
> @@ -21,6 +21,9 @@
> /* Minimum guard size. */
> #define ARCH_MIN_GUARD_SIZE 0
>
> +/* TLS guard page between thread stack and static TLS. */
> +#define ARCH_HAS_TLS_GUARD 0
> +
> /* Required stack pointer alignment at beginning. */
> #define STACK_ALIGN 16
>
> diff --git a/sysdeps/or1k/nptl/pthreaddef.h b/sysdeps/or1k/nptl/pthreaddef.h
> index cc92affca22..ca55c9c6605 100644
> --- a/sysdeps/or1k/nptl/pthreaddef.h
> +++ b/sysdeps/or1k/nptl/pthreaddef.h
> @@ -23,6 +23,9 @@
> /* Minimum guard size. */
> #define ARCH_MIN_GUARD_SIZE 0
>
> +/* TLS guard page between thread stack and static TLS. */
> +#define ARCH_HAS_TLS_GUARD 0
> +
> /* Required stack pointer alignment at beginning. */
> #define STACK_ALIGN 4
>
> diff --git a/sysdeps/powerpc/nptl/pthreaddef.h b/sysdeps/powerpc/nptl/pthreaddef.h
> index 5598dd3afc2..0ae1a8777d3 100644
> --- a/sysdeps/powerpc/nptl/pthreaddef.h
> +++ b/sysdeps/powerpc/nptl/pthreaddef.h
> @@ -21,6 +21,9 @@
> /* Minimum guard size. */
> #define ARCH_MIN_GUARD_SIZE 0
>
> +/* TLS guard page between thread stack and static TLS. */
> +#define ARCH_HAS_TLS_GUARD 0
> +
> /* Required stack pointer alignment at beginning. The ABI requires 16
> bytes (for both 32-bit and 64-bit PowerPC). */
> #define STACK_ALIGN 16
> diff --git a/sysdeps/riscv/nptl/pthreaddef.h b/sysdeps/riscv/nptl/pthreaddef.h
> index 219ef54b538..26abfd16df8 100644
> --- a/sysdeps/riscv/nptl/pthreaddef.h
> +++ b/sysdeps/riscv/nptl/pthreaddef.h
> @@ -22,6 +22,9 @@
> /* Minimum guard size. */
> #define ARCH_MIN_GUARD_SIZE 0
>
> +/* TLS guard page between thread stack and static TLS. */
> +#define ARCH_HAS_TLS_GUARD 0
> +
> /* Required stack pointer alignment at beginning. */
> #define STACK_ALIGN 16
>
> diff --git a/sysdeps/s390/nptl/pthreaddef.h b/sysdeps/s390/nptl/pthreaddef.h
> index f1d413db3d1..4f33be70c8e 100644
> --- a/sysdeps/s390/nptl/pthreaddef.h
> +++ b/sysdeps/s390/nptl/pthreaddef.h
> @@ -21,6 +21,9 @@
> /* Minimum guard size. */
> #define ARCH_MIN_GUARD_SIZE 0
>
> +/* TLS guard page between thread stack and static TLS. */
> +#define ARCH_HAS_TLS_GUARD 0
> +
> /* Required stack pointer alignment at beginning. SSE requires 16
> bytes. */
> #define STACK_ALIGN 16
> diff --git a/sysdeps/sh/nptl/pthreaddef.h b/sysdeps/sh/nptl/pthreaddef.h
> index ea6099a2e4c..96ecc406a30 100644
> --- a/sysdeps/sh/nptl/pthreaddef.h
> +++ b/sysdeps/sh/nptl/pthreaddef.h
> @@ -21,6 +21,9 @@
> /* Minimum guard size. */
> #define ARCH_MIN_GUARD_SIZE 0
>
> +/* TLS guard page between thread stack and static TLS. */
> +#define ARCH_HAS_TLS_GUARD 0
> +
> /* Required stack pointer alignment at beginning. */
> #define STACK_ALIGN 8
>
> diff --git a/sysdeps/sparc/sparc32/pthreaddef.h b/sysdeps/sparc/sparc32/pthreaddef.h
> index 04676ef307d..7b40593da79 100644
> --- a/sysdeps/sparc/sparc32/pthreaddef.h
> +++ b/sysdeps/sparc/sparc32/pthreaddef.h
> @@ -21,6 +21,9 @@
> /* Minimum guard size. */
> #define ARCH_MIN_GUARD_SIZE 0
>
> +/* TLS guard page between thread stack and static TLS. */
> +#define ARCH_HAS_TLS_GUARD 0
> +
> /* Required stack pointer alignment at beginning. */
> #define STACK_ALIGN 16
>
> diff --git a/sysdeps/sparc/sparc64/pthreaddef.h b/sysdeps/sparc/sparc64/pthreaddef.h
> index 94192f8b75d..27e465271bb 100644
> --- a/sysdeps/sparc/sparc64/pthreaddef.h
> +++ b/sysdeps/sparc/sparc64/pthreaddef.h
> @@ -21,6 +21,9 @@
> /* Minimum guard size. */
> #define ARCH_MIN_GUARD_SIZE 0
>
> +/* TLS guard page between thread stack and static TLS. */
> +#define ARCH_HAS_TLS_GUARD 0
> +
> /* Required stack pointer alignment at beginning. */
> #define STACK_ALIGN 16
>
> diff --git a/sysdeps/x86/nptl/pthreaddef.h b/sysdeps/x86/nptl/pthreaddef.h
> index 0764503f944..730453e6ce1 100644
> --- a/sysdeps/x86/nptl/pthreaddef.h
> +++ b/sysdeps/x86/nptl/pthreaddef.h
> @@ -21,6 +21,9 @@
> /* Minimum guard size. */
> #define ARCH_MIN_GUARD_SIZE 0
>
> +/* TLS guard page between thread stack and static TLS. */
> +#define ARCH_HAS_TLS_GUARD 1
> +
> /* Required stack pointer alignment at beginning. SSE requires 16
> bytes. */
> #define STACK_ALIGN 16
--
Cheers,
Carlos.
next prev parent reply other threads:[~2026-05-14 21:53 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-04 17:55 Adhemerval Zanella
2026-05-14 21:53 ` Carlos O'Donell [this message]
2026-05-19 18:18 ` Adhemerval Zanella Netto
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=6f714e38-aa86-4715-b8e9-95d92d2b53ae@redhat.com \
--to=carlos@redhat.com \
--cc=adhemerval.zanella@linaro.org \
--cc=jkl820.git@gmail.com \
--cc=libc-alpha@sourceware.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).