mirror of git://sourceware.org/git/glibc.git
370 lines
9.6 KiB
C
370 lines
9.6 KiB
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>
|
|
|
|
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 <support/test-driver.c>
|