/* 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
. */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static long int pagesz;
/* 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);
}
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;
}
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);
}
static bool
try_read_write_buf (char *ptr)
{
return try_read_buf (ptr) && try_write_buf(ptr);
}
/* 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;
};
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
}
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 };
}
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 };
}
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);
}
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_buf (s.stack), true);
TEST_COMPARE (try_read_buf (&s.stack[s.stacksize / 2]), true);
TEST_COMPARE (try_read_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;
}
/* 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);
}
/* 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 };
pthread_t t = xpthread_create (&attr, tf, &args);
void *status = xpthread_join (t);
TEST_VERIFY (status == 0);
xpthread_attr_destroy (&attr);
xmunmap (stack, stacksize);
}
/* 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);
}
/* 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);
}
/* 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);
}
/* 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);
}
/* 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);
}
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);
}
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