public inbox for libc-alpha@sourceware.org
 help / color / mirror / Atom feed
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.


  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).