From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id DDF564B1A2CC for ; Thu, 14 May 2026 21:53:29 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org DDF564B1A2CC Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org DDF564B1A2CC Authentication-Results: sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778795610; cv=none; b=jqaj7RTGj1+mKJAZvgzzSAmo5lfZcMqZWxFBqpobyc0FEoqhK/WsuPYR1KIF425KKcHyhlHngvLDSLY/Mr8KcNReAf6677zTLI3ak/+nQH/cciIprx2b/KpylU0QjBvdGPZWJYz8EpzUieNm56Tg/4ivgWGvPbBMHam6HJXavpw= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778795610; c=relaxed/simple; bh=qzXJyXVhHwPNkWbsjzUs+tFQ53cO0coO8lKghZajWZY=; h=DKIM-Signature:Message-ID:Date:MIME-Version:Subject:To:From; b=a27jdClIruwWcOkZUEb7oOWO52F8136xUeuVyfdARLX0P954+UdGyVIDxb0LQ2CiKRR65M9CT6MBXM46FD6kD5v1IopHhN1Gkpqsq+Qm/gqojhBTfykHy5Pq+vQjST+95mqCUgZHjttvyGj7+mejPy4ructTnduBZGlutiZHbEA= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Nv2EsHPD DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org DDF564B1A2CC Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Nv2EsHPD DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1778795609; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=FY6/RWQlg9IKLyK6trGr8ChQcgWmWY1YEcTFWCid/C0=; b=Nv2EsHPD9D2cx1QeJRN4z/Py0l8pq1hLA9WQdY7mECoSHdB+yw/Eet3mUc0cj7jH2RCL3Q R9ZytLH6MDLi7Ba8FeWAdO0IETB2Vn6xDd4/EeBuw9kmrFLrBGMHKNRdooEixZuph17wtq QCzncdrpPQVfmW0IBkPG9roil7nSHl8= Received: from mail-qv1-f71.google.com (mail-qv1-f71.google.com [209.85.219.71]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-655-XXVGMKkRNFGcQGXDkMbk_A-1; Thu, 14 May 2026 17:53:28 -0400 X-MC-Unique: XXVGMKkRNFGcQGXDkMbk_A-1 X-Mimecast-MFC-AGG-ID: XXVGMKkRNFGcQGXDkMbk_A_1778795607 Received: by mail-qv1-f71.google.com with SMTP id 6a1803df08f44-8ca122dade4so3949126d6.2 for ; Thu, 14 May 2026 14:53:27 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778795607; x=1779400407; 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=FY6/RWQlg9IKLyK6trGr8ChQcgWmWY1YEcTFWCid/C0=; b=rW4XyQw4/w3kxPRWdeKMwOte5oETckqI0lOQjakk4Yqdv2nmXm4QVYlz08k9RF0jm4 BdXarm/Pbq8BIPhoIkgzn7HPy9+7CsO8diJh8y5PPpEN6VEniuMNEZrt+hLOBkQGNlPf Kh6wAGRSHPVkLqtEPWlnD1EzIIgyUQ+AT2s4TiGjU1xkTyYFs0YvEVNMkxsuHJsd/V7+ cR+BXgixYXBVvsOSVe/Mju8y0nJLIWzpY9ngEJRsOUJ4/6aUwCo0j+9+fObNl9z8FPr8 EXaUvc6zK7lLYgOdH7POP0EanTbqrpbnaMY/MxbcyQFTACoA0VIrNupD1G4hL8KId7oc R58w== X-Forwarded-Encrypted: i=1; AFNElJ/XCMj6OZ/+lvGL6pbocFIB5MKxg66jrFVFV+nwtknPnciSS1Zr4eUj7VdQ/oEbLXmMacriY3hXlQfY@sourceware.org X-Gm-Message-State: AOJu0YyOF8NNnUw818Pj8ojXmKHpoVdTFFG7wXwsTsuMA6WkRc9E/muK 3hxh57k58idFyN4frx1XJGAkgw8k7nSBbtIrzxHvg8krgQ8aJeTZ9cxuayGTLpfGKZT13MeqWGX GFyOCfGXmBGbroGk3s/q2z/XVjTcLreH3p7A941CD/xeGYN71sJuus2HRUUu8HoZmMm1tew== X-Gm-Gg: Acq92OHUBAeXunCjzxhxbtyH+veTuHOIiBEXmRdEnBTrVgKYthbIC0XcjKRHzGiq1rN Nty8qXa+ynXDquHfLfZdCmCq4aE12Th71f2k+Usbc34mhh1mpa6n5HGTosz11kZ43CGpbP1TOcX R9XKYMkv5SxP6DBEJaNdZs2pf3vnSsSRSDCwEuVv3360XKXFRQFWPETkI80W88c/hgeCrSdJf9l kea0IOORb571yrYG+/4wyWWiU5H9ilLVFqdc4pqoKhPrH+KyhdoVLE4c14zxD5TTHPUxTUqsOfY 8toqBI2ZtXUsJnAumieCA3zGh2DjHcjnWqlucHmv9V0ERHvE/WW9JVymlFs74E6RWqRX8Q3xMq9 Q2j1i9tj8vhUxZuEVH3Aj7Nfu/1mSlvGB2L/aFgCHj/N4U9HK+eowCq49xilykeS61AgkcBHmGo nGnQGtOOs98lL7fIhDY4Arqw== X-Received: by 2002:a05:6214:3d13:b0:8a1:8ddd:e12e with SMTP id 6a1803df08f44-8ca0faee66fmr28120566d6.48.1778795606189; Thu, 14 May 2026 14:53:26 -0700 (PDT) X-Received: by 2002:a05:6214:3d13:b0:8a1:8ddd:e12e with SMTP id 6a1803df08f44-8ca0faee66fmr28119396d6.48.1778795605074; Thu, 14 May 2026 14:53:25 -0700 (PDT) Received: from [192.168.0.116] ([198.48.244.52]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-8c90bb80e7bsm33698286d6.33.2026.05.14.14.53.23 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Thu, 14 May 2026 14:53:24 -0700 (PDT) Message-ID: <6f714e38-aa86-4715-b8e9-95d92d2b53ae@redhat.com> Date: Thu, 14 May 2026 17:53:22 -0400 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: Adhemerval Zanella , libc-alpha@sourceware.org Cc: Jakob Koschel References: <20260504175532.423881-1-adhemerval.zanella@linaro.org> From: Carlos O'Donell Organization: Red Hat, LLC. In-Reply-To: <20260504175532.423881-1-adhemerval.zanella@linaro.org> X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: x24iIxGEU_nVnFouWHPex0LBevFDPXC5lvaAxeTXsmc_1778795607 X-Mimecast-Originator: redhat.com Content-Language: en-US Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-11.5 required=5.0 tests=BAYES_00,DKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,GIT_PATCH_0,KAM_ASCII_DIVIDERS,KAM_SHORT,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H4,RCVD_IN_MSPIKE_WL,SPF_HELO_PASS,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 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. > > 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. > 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 -- Cheers, Carlos.