From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail-dy1-x132d.google.com (mail-dy1-x132d.google.com [IPv6:2607:f8b0:4864:20::132d]) by sourceware.org (Postfix) with ESMTPS id 492904BA2E2F for ; Tue, 19 May 2026 18:18:39 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 492904BA2E2F Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=linaro.org Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=linaro.org ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 492904BA2E2F Authentication-Results: sourceware.org; arc=none smtp.remote-ip=2607:f8b0:4864:20::132d ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1779214719; cv=none; b=OapVwCbbx1Kjcxv/Cw9gCWZX/RQV5EeNsf6TmdbbbfUR3OLiJ2WoL5Sq8fNnjw1MPZmaGCRDHLLnaG2mNtAwPk5iKw/hFane/aamHEUspC357+19P2OSx1zMB5ibwS5hAJZgnWk7ww9NOjYwNmqlo+3QOkv1Q6T3lvxdW882RNY= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1779214719; c=relaxed/simple; bh=ezsYAh6LBGk2Wyxqxn63hfzmDHpBR7VQ7GtQCIELg3c=; h=DKIM-Signature:Message-ID:Date:MIME-Version:Subject:To:From; b=bzaiw9PJKPN5nFlqqxBbcxk5/Jca/S0+WHZkPr9/JJtMqO78Cae7AOJj0VcXXd2XJrKD0ElG0ZoscON4l98He9jzFxomBdpvbC9+GP9V+QKXx/dpcwT9tIyI/uoTdIuFmBtbKLGmOlTE+EcB/v8sAT/+tsJAYnCld164NYHDhos= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256 header.s=google header.b=V9A/KyXh DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 492904BA2E2F Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256 header.s=google header.b=V9A/KyXh Received: by mail-dy1-x132d.google.com with SMTP id 5a478bee46e88-2f0ad52830cso4871296eec.1 for ; Tue, 19 May 2026 11:18:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1779214718; x=1779819518; darn=sourceware.org; h=content-transfer-encoding:in-reply-to:organization:from :content-language:references:cc:to:subject:user-agent:mime-version :date:message-id:from:to:cc:subject:date:message-id:reply-to; bh=RhB8ZR7P7915ldS6xlZDGL6q0qqFHhbFx3fsyn+idPY=; b=V9A/KyXh4SYNN9kf4LKIgW3jjteap489GbZI3p5h6j0RFNlPBgDKR2bVqwpwh21m8y ITeD0kxiRbd4spikET4x4xhjqwkQaOSoqjVDxjWAmOhUacqG8kn9vvt2hd9lsy1I7YSi QSZmZnZucyMdetvd2uryoIY5Vd75aq9+Icogrl+9SMSmM4YNZIp6PEV+hf6mJ1Md+3FO +QjSTZnw+Nr4sbYNkzRVaAnjcLvqmcJ6Psb4eRHLvJbE3Xfdf7jZgWxkjb3Er6NtOhUm 1djGFotuTfpkTfXmh0WLoycMYM7n40qFOMIv2RsfltgyC64IJd7v+XCWKUcVfnzySh6I +FqA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779214718; x=1779819518; h=content-transfer-encoding:in-reply-to:organization:from :content-language:references:cc:to:subject:user-agent:mime-version :date:message-id:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=RhB8ZR7P7915ldS6xlZDGL6q0qqFHhbFx3fsyn+idPY=; b=aXsOhSNYyIWusLniHu3mutsDw5abQqSRP73wY1lVwL6F/U7eqjIvuSrMW5RHSa58kQ ReV8dzSZUFB9HAngIYYAZRXqESoZYQ4qCsoDRi6CHCv3znlX+3ft0AimVsH5xPDz4dZE f6Kt0mk6T2IkF4zbjkdIsxWb1bU2JO85om21Zpm80eLvHkZrQBxYtqWGv8bCWZSZs6Sa cU3BMPnVwV1TRic0B2oSVtuu3d1xWxsLELPyZh2S70Ed7zn5p3WUos2PD6APqOXHqu5S OuOoseUhMBwKffpTgjR1cEoyqotYsZu8OsGM4jOGgz3M40AlRtsrqZ46LRtM+7zHNpU7 vjAA== X-Forwarded-Encrypted: i=1; AFNElJ9Ak7NKQ+PRclAbOENhw/unsbPGu/aKj2zueyPcJgB6g0w4DzNMNanM6oFA9DkXZcUAgyQu1sQGYs9Z@sourceware.org X-Gm-Message-State: AOJu0Yy8O2XNJqb/VhV9MY9FAsBtAo7D3Xu7LoIWppTInp2xhLotDYqW uJ2sCfZMCi1sN/VpyZXD9OsGHf7PYh2crY53aZP9VVXZx7IGzRQuqDfBW/jl4WI2AM4= X-Gm-Gg: Acq92OHgVIrX/TX/OxWqerDH1noi8LooKbqQueb+pCjIjVQttuPWLshariNhjd+E0dY M+wFTHZ7h30Od7bnsVyMgSfUPmf6pdc2PozTXZgXE/2fVHU4qeyeHyESTA3YB0BCxbvPLVk5xDC S2R3v4RvsH93f/XpXemWec+fKQBMu/npmypLp36z06XJ2n/jmwggK1Ud/ennDdrsAPSWBKtr5+t 4MDHXWDIfqIPUgsk0aoTIF8egFry4cwn7Lx78C2Nya9Q/2jeFr28PNxq1upW4tFIkEPVCp5LioD xbC4ZPlI0SyMfFtc+OCf5VpbBw0lGDv9l/i1W5pb6mDk1jh47w3XoK70Ad7c85V1sHwNPoXBBbp iUhJyhsmrB9ewbATWJbW1HoNd6luwPrsGJIXL3cv3andW6Cli1NEC2/IjkMxkWiRnzMMR+h+yWz xbcPAHGTAyq6FOVm67Y5zGhhV1bHnjJ61jkBp9Rn4mJwMMnjbwMozB+bKpPeQrbGXbq1zPvUj5s T9hZzBMFHqStShc2/yxUoFSX5NxOhI= X-Received: by 2002:a05:7300:a286:b0:2e2:d94d:6188 with SMTP id 5a478bee46e88-3039816dcddmr8818337eec.7.1779214717411; Tue, 19 May 2026 11:18:37 -0700 (PDT) Received: from ?IPV6:2804:1b3:a7c1:d905:158e:f36d:1e3:3a4d? ([2804:1b3:a7c1:d905:158e:f36d:1e3:3a4d]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-30294500a97sm19467222eec.9.2026.05.19.11.18.35 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Tue, 19 May 2026 11:18:36 -0700 (PDT) Message-ID: <7e004214-62ac-442a-ba1f-bace59de8525@linaro.org> Date: Tue, 19 May 2026 15:18:34 -0300 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PATCH] [RFC] nptl: Add TLS guard page between thread stack and static TLS [BZ #22850] To: Carlos O'Donell , libc-alpha@sourceware.org Cc: Jakob Koschel References: <20260504175532.423881-1-adhemerval.zanella@linaro.org> <6f714e38-aa86-4715-b8e9-95d92d2b53ae@redhat.com> Content-Language: en-US From: Adhemerval Zanella Netto Organization: Linaro In-Reply-To: <6f714e38-aa86-4715-b8e9-95d92d2b53ae@redhat.com> Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-9.8 required=5.0 tests=BAYES_00,BODY_8BITS,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,GIT_PATCH_0,KAM_ASCII_DIVIDERS,KAM_SHORT,RCVD_IN_DNSWL_NONE,SPF_HELO_NONE,SPF_NONE,TXREP shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org List-Id: 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 @@ >>      .  */ >>     #include >> +#include >>   #include >>   #include >>   #include >> @@ -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 >> +   .  */ >> + >> +/* 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 >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> + >> +#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 >> 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 > >