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