public inbox for libc-alpha@sourceware.org
 help / color / mirror / Atom feed
From: Adhemerval Zanella Netto <adhemerval.zanella@linaro.org>
To: Carlos O'Donell <carlos@redhat.com>, 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: Tue, 19 May 2026 15:18:34 -0300	[thread overview]
Message-ID: <7e004214-62ac-442a-ba1f-bace59de8525@linaro.org> (raw)
In-Reply-To: <6f714e38-aa86-4715-b8e9-95d92d2b53ae@redhat.com>



On 14/05/26 18:53, Carlos O'Donell wrote:
> 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.

I only enabled this hardening for x86_64, but I think we should consider
enable at least for the aforementioned ABI as well.

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

I am not sure if it would be profitable for these ABIs, albeit it does
add some extra hardening.

Should I send a newer version that enabled this by default or just keep for
x86_64 and maybe for s390x, sparc64, and powerpc64? I am not sure about
32-bit abi because of the extra mapping on older kernels, but at same time
I think hard unlikely people are running multithread workloads with a *lot* 
threads in these sytems.

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


      reply	other threads:[~2026-05-19 18:18 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
2026-05-19 18:18   ` Adhemerval Zanella Netto [this message]

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=7e004214-62ac-442a-ba1f-bace59de8525@linaro.org \
    --to=adhemerval.zanella@linaro.org \
    --cc=carlos@redhat.com \
    --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).