[PATCH v2] nptl: Add support for setup guard pages with MADV_GUARD_INSTALL
DJ Delorie
dj@redhat.com
Wed Jan 22 04:13:52 GMT 2025
One suggestion about comment layout.
One needed change near the end wrt THREAD_GETMEM ?
> diff --git a/nptl/Makefile b/nptl/Makefile
> tst-dlsym1 \
> tst-exec4 \
> tst-exec5 \
> + tst-guard1 \
> tst-initializers1 \
> tst-initializers1-c11 \
> tst-initializers1-c89 \
Ok.
> diff --git a/nptl/TODO-testing b/nptl/TODO-testing
> -pthread_attr_setguardsize
> -
> - test effectiveness
> -
Ok.
> diff --git a/nptl/allocatestack.c b/nptl/allocatestack.c
> @@ -146,10 +146,37 @@ get_cached_stack (size_t *sizep, void **memp)
>
> +/* 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;
> +
> +static inline int stack_prot (void)
> +{
> + return (PROT_READ | PROT_WRITE
> + | ((GL(dl_stack_flags) & PF_X) ? PROT_EXEC : 0));
> +}
Ok.
> +static void *
> +allocate_thread_stack (size_t size, size_t guardsize)
> +{
> + /* MADV_ADVISE_GUARD does not require an additional PROT_NONE mapping. */
> + int prot = stack_prot ();
> +
> + if (atomic_load_relaxed (&allocate_stack_mode) == ALLOCATE_GUARD_PROT_NONE)
> + /* If a guard page is required, avoid committing memory by first allocate
> + with PROT_NONE and then reserve with required permission excluding the
> + guard page. */
> + prot = guardsize == 0 ? prot : PROT_NONE;
> +
> + return __mmap (NULL, size, prot, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1,
> + 0);
> +}
Ok.
> /* Return the guard page position on allocated stack. */
> static inline char *
> __attribute ((always_inline))
> -guard_position (void *mem, size_t size, size_t guardsize, struct pthread *pd,
> +guard_position (void *mem, size_t size, size_t guardsize, const struct pthread *pd,
> size_t pagesize_m1)
Ok.
> #if _STACK_GROWS_DOWN
> @@ -159,27 +186,131 @@ guard_position (void *mem, size_t size, size_t guardsize, struct pthread *pd,
> #endif
> }
>
> -/* Based on stack allocated with PROT_NONE, setup the required portions with
> - 'prot' flags based on the guard page position. */
> -static inline int
> -setup_stack_prot (char *mem, size_t size, char *guard, size_t guardsize,
> - const int prot)
> +/* 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. */
> +static inline bool
> +setup_stack_prot (char *mem, size_t size, struct pthread *pd,
> + size_t guardsize, size_t pagesize_m1)
> {
Ok.
> - char *guardend = guard + guardsize;
> + if (__glibc_unlikely (guardsize == 0))
> + return true;
Ok.
> + char *guard = guard_position (mem, size, guardsize, pd, pagesize_m1);
> + if (atomic_load_relaxed (&allocate_stack_mode) == ALLOCATE_GUARD_MADV_GUARD)
> + {
> + if (__madvise (guard, guardsize, MADV_GUARD_INSTALL) == 0)
> + {
> + pd->stack_mode = ALLOCATE_GUARD_MADV_GUARD;
> + return true;
> + }
Ok.
> + /* 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. */
> + atomic_store_relaxed (&allocate_stack_mode, ALLOCATE_GUARD_PROT_NONE);
> + if (__mprotect (guard, guardsize, PROT_NONE) != 0)
> + return false;
> + }
Ok.
> + else
> + {
> + const int prot = stack_prot ();
> + 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. */
> - if (__mprotect (guardend, size - guardsize, prot) != 0)
> - return errno;
> + /* As defined at guard_position, for architectures with downward stack
> + the guard page is always at start of the allocated area. */
> + if (__mprotect (guardend, size - guardsize, prot) != 0)
> + return false;
> #else
> - size_t mprots1 = (uintptr_t) guard - (uintptr_t) mem;
> - if (__mprotect (mem, mprots1, prot) != 0)
> - return errno;
> - size_t mprots2 = ((uintptr_t) mem + size) - (uintptr_t) guardend;
> - if (__mprotect (guardend, mprots2, prot) != 0)
> - return errno;
> + size_t mprots1 = (uintptr_t) guard - (uintptr_t) mem;
> + if (__mprotect (mem, mprots1, prot) != 0)
> + return false;
> + size_t mprots2 = ((uintptr_t) mem + size) - (uintptr_t) guardend;
> + if (__mprotect (guardend, mprots2, prot) != 0)
> + return false;
Ok.
> #endif
> - return 0;
> + }
> +
> + pd->stack_mode = ALLOCATE_GUARD_PROT_NONE;
> + return true;
> +}
Ok.
> +/* 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. */
> +static inline bool
> +adjust_stack_prot (char *mem, size_t size, const struct pthread *pd,
> + size_t guardsize, 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:
> +
> + |guard|stack---------------------------------|
> + |new guard--|stack---------------------------|
> +
> + while for _STACK_GROWS_UP:
> +
> + |stack---------------------------|guard|-----|
> + |stack--------------------|new guard---|-----|
> +
> + Both madvise and mprotect allows overlap the required region,
> + so use the new guard placement with the new size. */
> + if (guardsize > pd->guardsize)
> + {
> + char *guard = guard_position (mem, size, guardsize, pd, pagesize_m1);
> + if (pd->stack_mode == ALLOCATE_GUARD_MADV_GUARD)
> + return __madvise (guard, guardsize, MADV_GUARD_INSTALL) == 0;
> + else if (pd->stack_mode == ALLOCATE_GUARD_PROT_NONE)
> + return __mprotect (guard, guardsize, PROT_NONE) == 0;
This will fall through if ALLOCATE_GUARD_USER, but the fallthrough path
doesn't do anything, so ok.
> + }
> + /* The current guard area is larger than the required one. For
> + _STACK_GROWS_DOWN is means change the guard as:
> +
> + |guard-------|stack-------------------------|
> + |new guard|stack----------------------------|
Just a note: this would be clearer if the word "stack" were at the top,
so it doesn't imply that the stack contents must be moved:
> + |guard-------|-------------------------stack|
> + |new guard|----------------------------stack|
> +
> + And for _STACK_GROWS_UP:
> +
> + |stack---------------------|guard-------|---|
> + |stack------------------------|new guard|---|
> +
> + 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. */
> + else if (pd->guardsize > guardsize)
> + {
> + size_t slacksize = pd->guardsize - guardsize;
> + if (pd->stack_mode == ALLOCATE_GUARD_MADV_GUARD)
> + {
> + 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;
> + }
Ok.
> + else if (pd->stack_mode == ALLOCATE_GUARD_PROT_NONE)
> + {
> + const int prot = stack_prot ();
> +#if _STACK_GROWS_DOWN
> + return __mprotect (mem + guardsize, slacksize, prot) == 0;
> +#else
> + char *new_guard = (char *)(((uintptr_t) pd - guardsize)
> + & ~pagesize_m1);
> + char *old_guard = (char *)(((uintptr_t) pd - pd->guardsize)
> + & ~pagesize_m1);
> + /* The guard size difference might be > 0, but once rounded
> + 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;
> +#endif
Ok.
> + }
> + }
> + return true;
> }
Ok.
> @@ -291,7 +422,7 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
>
> /* This is a user-provided stack. It will not be queued in the
> stack cache nor will the memory (except the TLS memory) be freed. */
> - pd->user_stack = true;
> + pd->stack_mode = ALLOCATE_GUARD_USER;
Ok.
> @@ -325,10 +456,7 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
> /* Allocate some anonymous memory. If possible use the cache. */
> size_t guardsize;
> size_t reported_guardsize;
> - size_t reqsize;
> void *mem;
> - const int prot = (PROT_READ | PROT_WRITE
> - | ((GL(dl_stack_flags) & PF_X) ? PROT_EXEC : 0));
Ok.
> @@ -358,16 +486,10 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
> return EINVAL;
>
> /* Try to get a stack from the cache. */
> - reqsize = size;
> pd = get_cached_stack (&size, &mem);
> if (pd == NULL)
> {
> - /* If a guard page is required, avoid committing memory by first
> - allocate with PROT_NONE and then reserve with required permission
> - excluding the guard page. */
> - mem = __mmap (NULL, size, (guardsize == 0) ? prot : PROT_NONE,
> - MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
> -
> + mem = allocate_thread_stack (size, guardsize);
Ok.
> @@ -394,15 +516,10 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
> #endif
>
> /* Now mprotect the required region excluding the guard area. */
> - if (__glibc_likely (guardsize > 0))
> + if (!setup_stack_prot (mem, size, pd, guardsize, pagesize_m1))
> {
> - char *guard = guard_position (mem, size, guardsize, pd,
> - pagesize_m1);
> - if (setup_stack_prot (mem, size, guard, guardsize, prot) != 0)
> - {
> - __munmap (mem, size);
> - return errno;
> - }
> + __munmap (mem, size);
> + return errno;
> }
Ok.
> @@ -456,59 +573,31 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
> which will be read next. */
> }
>
> - /* Create or resize the guard area if necessary. */
> - if (__glibc_unlikely (guardsize > pd->guardsize))
> + /* Create or resize the guard area if necessary on an already
> + allocated stack. */
> + if (!adjust_stack_prot (mem, size, pd, guardsize, pagesize_m1))
Ok.
> {
> - char *guard = guard_position (mem, size, guardsize, pd,
> - pagesize_m1);
> - if (__mprotect (guard, guardsize, PROT_NONE) != 0)
> - {
> - mprot_error:
> - lll_lock (GL (dl_stack_cache_lock), LLL_PRIVATE);
> -
> - /* Remove the thread from the list. */
> - __nptl_stack_list_del (&pd->list);
> + lll_lock (GL (dl_stack_cache_lock), LLL_PRIVATE);
>
> - lll_unlock (GL (dl_stack_cache_lock), LLL_PRIVATE);
> + /* Remove the thread from the list. */
> + __nptl_stack_list_del (&pd->list);
>
> - /* Get rid of the TLS block we allocated. */
> - _dl_deallocate_tls (TLS_TPADJ (pd), false);
> + lll_unlock (GL (dl_stack_cache_lock), LLL_PRIVATE);
>
> - /* Free the stack memory regardless of whether the size
> - of the cache is over the limit or not. If this piece
> - of memory caused problems we better do not use it
> - anymore. Uh, and we ignore possible errors. There
> - is nothing we could do. */
> - (void) __munmap (mem, size);
> + /* Get rid of the TLS block we allocated. */
> + _dl_deallocate_tls (TLS_TPADJ (pd), false);
>
> - return errno;
> - }
> + /* Free the stack memory regardless of whether the size
> + of the cache is over the limit or not. If this piece
> + of memory caused problems we better do not use it
> + anymore. Uh, and we ignore possible errors. There
> + is nothing we could do. */
> + (void) __munmap (mem, size);
>
> - pd->guardsize = guardsize;
> + return errno;
> }
Ok.
> - else if (__builtin_expect (pd->guardsize - guardsize > size - reqsize,
> - 0))
> - {
> - /* The old guard area is too large. */
> -
> -#if _STACK_GROWS_DOWN
> - if (__mprotect ((char *) mem + guardsize, pd->guardsize - guardsize,
> - prot) != 0)
> - goto mprot_error;
> -#elif _STACK_GROWS_UP
> - char *new_guard = (char *)(((uintptr_t) pd - guardsize)
> - & ~pagesize_m1);
> - char *old_guard = (char *)(((uintptr_t) pd - pd->guardsize)
> - & ~pagesize_m1);
> - /* The guard size difference might be > 0, but once rounded
> - to the nearest page the size difference might be zero. */
> - if (new_guard > old_guard
> - && __mprotect (old_guard, new_guard - old_guard, prot) != 0)
> - goto mprot_error;
> -#endif
>
> - pd->guardsize = guardsize;
> - }
> + pd->guardsize = guardsize;
> /* The pthread_getattr_np() calls need to get passed the size
> requested in the attribute, regardless of how large the
> actually used guardsize is. */
Ok.
> @@ -568,19 +657,21 @@ 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;
> #if _STACK_GROWS_DOWN
> - void *stack = pd->stackblock + pd->guardsize;
> + void *stack = pd->stackblock + adjust;
> #else
> void *stack = pd->stackblock;
> #endif
> - size_t stacksize = pd->stackblock_size - pd->guardsize;
> + size_t stacksize = pd->stackblock_size - adjust;
Ok.
> if (!set)
> - __set_vma_name (stack, stacksize, NULL);
> + __set_vma_name (stack, stacksize, " glibc: unused stack");
> else
> {
> unsigned int tid = pd->tid;
> - if (pd->user_stack)
> + if (pd->stack_mode == ALLOCATE_GUARD_USER)
> SET_STACK_NAME (" glibc: pthread user stack: ", stack, stacksize, tid);
> else
> SET_STACK_NAME (" glibc: pthread stack: ", stack, stacksize, tid);
Ok.
> diff --git a/nptl/descr.h b/nptl/descr.h
> index d0d30929e2..9c1ed54c56 100644
> --- a/nptl/descr.h
> +++ b/nptl/descr.h
> @@ -125,6 +125,12 @@ struct priority_protection_data
> unsigned int priomap[];
> };
>
> +enum allocate_stack_mode_t
> +{
> + ALLOCATE_GUARD_MADV_GUARD = 0,
> + ALLOCATE_GUARD_PROT_NONE = 1,
> + ALLOCATE_GUARD_USER = 2,
> +};
Ok.
> /* Thread descriptor data structure. */
> struct pthread
> @@ -324,7 +330,7 @@ struct pthread
> bool report_events;
>
> /* True if the user provided the stack. */
> - bool user_stack;
> + enum allocate_stack_mode_t stack_mode;
Struct is not packed, so OK.
> diff --git a/nptl/nptl-stack.c b/nptl/nptl-stack.c
> index 503357f25d..c049c5133c 100644
> --- a/nptl/nptl-stack.c
> +++ b/nptl/nptl-stack.c
> @@ -120,7 +120,7 @@ __nptl_deallocate_stack (struct pthread *pd)
> not reset the 'used' flag in the 'tid' field. This is done by
> the kernel. If no thread has been created yet this field is
> still zero. */
> - if (__glibc_likely (! pd->user_stack))
> + if (__glibc_likely (pd->stack_mode != ALLOCATE_GUARD_USER))
Ok.
> diff --git a/nptl/pthread_create.c b/nptl/pthread_create.c
> index 01e8a86980..0808f2e628 100644
> --- a/nptl/pthread_create.c
> +++ b/nptl/pthread_create.c
> @@ -554,7 +554,7 @@ start_thread (void *arg)
> to avoid creating a new free-state block during thread release. */
> __getrandom_vdso_release (pd);
>
> - if (!pd->user_stack)
> + if (pd->stack_mode != ALLOCATE_GUARD_USER)
Ok.
> diff --git a/nptl/tst-guard1.c b/nptl/tst-guard1.c
> +/* Basic tests for pthread guard area.
> + Copyright (C) 2025 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + modify it under the terms of the GNU Lesser General Public
> + License as published by the Free Software Foundation; either
> + version 2.1 of the License, or (at your option) any later version.
> +
> + The GNU C Library is distributed in the hope that it will be useful,
> + but WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +#include <array_length.h>
> +#include <pthreaddef.h>
> +#include <setjmp.h>
> +#include <stackinfo.h>
> +#include <stdio.h>
> +#include <support/check.h>
> +#include <support/test-driver.h>
> +#include <support/xsignal.h>
> +#include <support/xthread.h>
> +#include <support/xunistd.h>
> +#include <sys/mman.h>
> +#include <stdlib.h>
Ok.
> +static long int pagesz;
Ok.
> +/* To check if the guard region is inaccessible, the thread tries read/writes
> + on it and checks if a SIGSEGV is generated. */
> +
> +static volatile sig_atomic_t signal_jump_set;
> +static sigjmp_buf signal_jmp_buf;
> +
> +static void
> +sigsegv_handler (int sig)
> +{
> + if (signal_jump_set == 0)
> + return;
> +
> + siglongjmp (signal_jmp_buf, sig);
> +}
Ok.
> +static bool
> +try_access_buf (char *ptr, bool write)
> +{
> + signal_jump_set = true;
> +
> + bool failed = sigsetjmp (signal_jmp_buf, 0) != 0;
> + if (!failed)
> + {
> + if (write)
> + *(volatile char *)(ptr) = 'x';
> + else
> + *(volatile char *)(ptr);
> + }
> +
> + signal_jump_set = false;
> + return !failed;
> +}
Ok.
> +static bool
> +try_read_buf (char *ptr)
> +{
> + return try_access_buf (ptr, false);
> +}
> +
> +static bool
> +try_write_buf (char *ptr)
> +{
> + return try_access_buf (ptr, true);
> +}
Ok.
> +static bool
> +try_read_write_buf (char *ptr)
> +{
> + return try_read_buf (ptr) && try_write_buf(ptr);
> +}
Ok.
> +
> +/* Return the guard region of the current thread (it only makes sense on
> + a thread created by pthread_created). */
> +
> +struct stack_t
> +{
> + char *stack;
> + size_t stacksize;
> + char *guard;
> + size_t guardsize;
> +};
Ok.
> +static inline size_t
> +adjust_stacksize (size_t stacksize)
> +{
> + /* For some ABIs, The guard page depends of the thread descriptor, which in
> + turn rely on the require static TLS. The only supported _STACK_GROWS_UP
> + ABI, hppa, defines TLS_DTV_AT_TP and it is not straightforward to
> + calculate the guard region with current pthread APIs. So to get a
> + correct stack size assumes an extra page after the guard area. */
> +#if _STACK_GROWS_DOWN
> + return stacksize;
> +#elif _STACK_GROWS_UP
> + return stacksize - pagesz;
> +#endif
> +}
Ok.
> +struct stack_t
> +get_current_stack_info (void)
> +{
> + 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);
> + /* The guardsize is reported as the current page size, although it might
> + be adjusted to a larger value (aarch64 for instance). */
> + if (guardsize != 0 && guardsize < ARCH_MIN_GUARD_SIZE)
> + guardsize = ARCH_MIN_GUARD_SIZE;
> +
> +#if _STACK_GROWS_DOWN
> + void *guard = guardsize ? stack - guardsize : 0;
> +#elif _STACK_GROWS_UP
> + stacksize = adjust_stacksize (stacksize);
> + void *guard = guardsize ? stack + stacksize : 0;
> +#endif
> +
> + pthread_attr_destroy (&attr);
> +
> + return (struct stack_t) { stack, stacksize, guard, guardsize };
> +}
Ok.
> +struct thread_args_t
> +{
> + size_t stacksize;
> + size_t guardsize;
> +};
> +
> +struct thread_args_t
> +get_thread_args (const pthread_attr_t *attr)
> +{
> + size_t stacksize;
> + size_t guardsize;
> +
> + TEST_COMPARE (pthread_attr_getstacksize (attr, &stacksize), 0);
> + TEST_COMPARE (pthread_attr_getguardsize (attr, &guardsize), 0);
> + if (guardsize < ARCH_MIN_GUARD_SIZE)
> + guardsize = ARCH_MIN_GUARD_SIZE;
> +
> + return (struct thread_args_t) { stacksize, guardsize };
> +}
Ok.
> +static void
> +set_thread_args (pthread_attr_t *attr, const struct thread_args_t *args)
> +{
> + xpthread_attr_setstacksize (attr, args->stacksize);
> + xpthread_attr_setguardsize (attr, args->guardsize);
> +}
Ok.
> +static void *
> +tf (void *closure)
> +{
> + struct thread_args_t *args = closure;
> +
> + struct stack_t s = get_current_stack_info ();
> + if (test_verbose)
> + printf ("debug: [tid=%jd] stack = { .stack=%p, stacksize=%#zx, guard=%p, "
> + "guardsize=%#zx }\n",
> + (intmax_t) gettid (),
> + s.stack,
> + s.stacksize,
> + s.guard,
> + s.guardsize);
> +
> + if (args != NULL)
> + {
> + TEST_COMPARE (adjust_stacksize (args->stacksize), s.stacksize);
> + TEST_COMPARE (args->guardsize, s.guardsize);
> + }
> +
> + /* Ensure we can access the stack area. */
> + TEST_COMPARE (try_read_write_buf (s.stack), true);
> + TEST_COMPARE (try_read_write_buf (&s.stack[s.stacksize / 2]), true);
> + TEST_COMPARE (try_read_write_buf (&s.stack[s.stacksize - 1]), true);
> +
> + /* Check if accessing the guard area results in SIGSEGV. */
> + if (s.guardsize > 0)
> + {
> + TEST_COMPARE (try_read_write_buf (s.guard), false);
> + TEST_COMPARE (try_read_write_buf (&s.guard[s.guardsize / 2]), false);
> + TEST_COMPARE (try_read_write_buf (&s.guard[s.guardsize] - 1), false);
> + }
> +
> + return NULL;
> +}
Ok.
> +/* Test 1: caller provided stack without guard. */
> +static void
> +do_test1 (void)
> +{
> + pthread_attr_t attr;
> + xpthread_attr_init (&attr);
> +
> + size_t stacksize = support_small_thread_stack_size ();
> + void *stack = xmmap (0,
> + stacksize,
> + PROT_READ | PROT_WRITE,
> + MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK,
> + -1);
> + xpthread_attr_setstack (&attr, stack, stacksize);
> + xpthread_attr_setguardsize (&attr, 0);
> +
> + struct thread_args_t args = { stacksize, 0 };
> + pthread_t t = xpthread_create (&attr, tf, &args);
> + void *status = xpthread_join (t);
> + TEST_VERIFY (status == 0);
> +
> + xpthread_attr_destroy (&attr);
> + xmunmap (stack, stacksize);
> +}
Ok.
> +/* Test 2: same as 1., but with a guard area. */
> +static void
> +do_test2 (void)
> +{
> + pthread_attr_t attr;
> + xpthread_attr_init (&attr);
> +
> + size_t stacksize = support_small_thread_stack_size ();
> + void *stack = xmmap (0,
> + stacksize,
> + PROT_READ | PROT_WRITE,
> + MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK,
> + -1);
> + xpthread_attr_setstack (&attr, stack, stacksize);
> + xpthread_attr_setguardsize (&attr, pagesz);
> +
> + struct thread_args_t args = { stacksize, 0 };
This assumes glibc won't set a guard on a user stack, ok.
> + pthread_t t = xpthread_create (&attr, tf, &args);
> + void *status = xpthread_join (t);
> + TEST_VERIFY (status == 0);
> +
> + xpthread_attr_destroy (&attr);
> + xmunmap (stack, stacksize);
> +}
Ok.
> +/* Test 3: pthread_create with default values. */
> +static void
> +do_test3 (void)
> +{
> + pthread_t t = xpthread_create (NULL, tf, NULL);
> + void *status = xpthread_join (t);
> + TEST_VERIFY (status == 0);
> +}
Ok.
> +/* Test 4: pthread_create without a guard area. */
> +static void
> +do_test4 (void)
> +{
> + pthread_attr_t attr;
> + xpthread_attr_init (&attr);
> + struct thread_args_t args = get_thread_args (&attr);
> + args.stacksize += args.guardsize;
> + args.guardsize = 0;
> + set_thread_args (&attr, &args);
> +
> + pthread_t t = xpthread_create (&attr, tf, &args);
> + void *status = xpthread_join (t);
> + TEST_VERIFY (status == 0);
> +
> + xpthread_attr_destroy (&attr);
> +}
Ok.
> +/* Test 5: pthread_create with non default stack and guard size value. */
> +static void
> +do_test5 (void)
> +{
> + pthread_attr_t attr;
> + xpthread_attr_init (&attr);
> + struct thread_args_t args = get_thread_args (&attr);
> + args.guardsize += pagesz;
> + args.stacksize += pagesz;
> + set_thread_args (&attr, &args);
> +
> + pthread_t t = xpthread_create (&attr, tf, &args);
> + void *status = xpthread_join (t);
> + TEST_VERIFY (status == 0);
> +
> + xpthread_attr_destroy (&attr);
> +}
Ok.
> +/* Test 6: thread with the required size (stack + guard) that matches the
> + test 3, but with a larger guard area. The pthread_create will need to
> + increase the guard area. */
> +static void
> +do_test6 (void)
> +{
> + pthread_attr_t attr;
> + xpthread_attr_init (&attr);
> + struct thread_args_t args = get_thread_args (&attr);
> + args.guardsize += pagesz;
> + args.stacksize -= pagesz;
> + set_thread_args (&attr, &args);
> +
> + pthread_t t = xpthread_create (&attr, tf, &args);
> + void *status = xpthread_join (t);
> + TEST_VERIFY (status == 0);
> +
> + xpthread_attr_destroy (&attr);
> +}
Ok.
> +/* Test 7: pthread_create with default values, the requires size matches the
> + one from test 3 and 6 (but with a reduced guard ares). The
> + pthread_create should use the cached stack from previous tests, but it
> + would require to reduce the guard area. */
> +static void
> +do_test7 (void)
> +{
> + pthread_t t = xpthread_create (NULL, tf, NULL);
> + void *status = xpthread_join (t);
> + TEST_VERIFY (status == 0);
> +}
Ok.
> +static int
> +do_test (void)
> +{
> + pagesz = sysconf (_SC_PAGESIZE);
> +
> + {
> + struct sigaction sa = {
> + .sa_handler = sigsegv_handler,
> + .sa_flags = SA_NODEFER,
> + };
> + sigemptyset (&sa.sa_mask);
> + xsigaction (SIGSEGV, &sa, NULL);
> + /* Some system generates SIGBUS accessing the guard area when it is
> + setup with madvise. */
> + xsigaction (SIGBUS, &sa, NULL);
> + }
Ok.
> + static const struct {
> + const char *descr;
> + void (*test)(void);
> + } tests[] = {
> + { "user provided stack without guard", do_test1 },
> + { "user provided stack with guard", do_test2 },
> + { "default attribute", do_test3 },
> + { "default attribute without guard", do_test4 },
> + { "non default stack and guard sizes", do_test5 },
> + { "reused stack with larger guard", do_test6 },
> + { "reused stack with smaller guard", do_test7 },
> + };
> +
> + for (int i = 0; i < array_length (tests); i++)
> + {
> + printf ("debug: test%01d: %s\n", i, tests[i].descr);
> + tests[i].test();
> + }
> +
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
Ok.
> diff --git a/sysdeps/nptl/dl-tls_init_tp.c b/sysdeps/nptl/dl-tls_init_tp.c
> index c57738e9f3..20cc9202ec 100644
> --- a/sysdeps/nptl/dl-tls_init_tp.c
> +++ b/sysdeps/nptl/dl-tls_init_tp.c
> @@ -72,7 +72,7 @@ __tls_init_tp (void)
> /* Early initialization of the TCB. */
> pd->tid = INTERNAL_SYSCALL_CALL (set_tid_address, &pd->tid);
> THREAD_SETMEM (pd, specific[0], &pd->specific_1stblock[0]);
> - THREAD_SETMEM (pd, user_stack, true);
> + THREAD_SETMEM (pd, stack_mode, ALLOCATE_GUARD_USER);
>
Ok.
> diff --git a/sysdeps/nptl/fork.h b/sysdeps/nptl/fork.h
> index 6156af79e1..3c79179437 100644
> --- a/sysdeps/nptl/fork.h
> +++ b/sysdeps/nptl/fork.h
> @@ -155,7 +155,7 @@ reclaim_stacks (void)
> INIT_LIST_HEAD (&GL (dl_stack_used));
> INIT_LIST_HEAD (&GL (dl_stack_user));
>
> - if (__glibc_unlikely (THREAD_GETMEM (self, user_stack)))
> + if (__glibc_unlikely (self->stack_mode == ALLOCATE_GUARD_USER))
This should be THREAD_GETMEM(self, stack_mode) ?
> diff --git a/sysdeps/unix/sysv/linux/bits/mman-linux.h b/sysdeps/unix/sysv/linux/bits/mman-linux.h
> index 8e072eb4cd..fe0496d802 100644
> --- a/sysdeps/unix/sysv/linux/bits/mman-linux.h
> +++ b/sysdeps/unix/sysv/linux/bits/mman-linux.h
> @@ -113,6 +113,8 @@
> locked pages too. */
> # define MADV_COLLAPSE 25 /* Synchronous hugepage collapse. */
> # define MADV_HWPOISON 100 /* Poison a page for testing. */
> +# define MADV_GUARD_INSTALL 102 /* Fatal signal on access to range */
> +# define MADV_GUARD_REMOVE 103 /* Unguard range */
> #endif
Matches kernel, ok.
More information about the Libc-alpha
mailing list