[RFC] nptl: Add TLS guard page between thread stack and static TLS [BZ #22850]

Message ID 20260504175532.423881-1-adhemerval.zanella@linaro.org (mailing list archive)
State New
Headers
Series [RFC] nptl: Add TLS guard page between thread stack and static TLS [BZ #22850] |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
redhat-pt-bot/TryBot-32bit success Build for i686

Commit Message

Adhemerval Zanella May 4, 2026, 5:55 p.m. UTC
  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.

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)

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)

hppa (_STACK_GROWS_UP) is not affected.

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

The implementation is mostly on allocatestack.c:

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

  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.

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

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

Patch

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