@@ -387,6 +387,7 @@ tests-internal := \
tst-sem13 \
tst-setgetname \
tst-signal7 \
+ tst-tls-guard \
# tests-internal
xtests = \
@@ -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
@@ -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
{
@@ -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)
new file mode 100644
@@ -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>
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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