216 lines
5.9 KiB
C
216 lines
5.9 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (c) Zilogic Systems Pvt. Ltd., 2020
|
|
* Email: code@zilogic.com
|
|
*/
|
|
|
|
/*
|
|
* Test mmap() MAP_GROWSDOWN flag
|
|
*
|
|
* # Test1:
|
|
*
|
|
* We assign the memory region partially allocated with MAP_GROWSDOWN flag to
|
|
* a thread as a stack and expect the mapping to grow when we touch the
|
|
* guard page by calling a recusive function in the thread that uses the
|
|
* growable mapping as a stack.
|
|
*
|
|
* The kernel only grows the memory region when the stack pointer is within
|
|
* guard page when the guard page is touched so simply faulting the guard
|
|
* page will not cause the mapping to grow.
|
|
*
|
|
* Newer kernels does not allow a MAP_GROWSDOWN mapping to grow closer than
|
|
* 'stack_guard_gap' pages to an existing mapping. So when we map the stack we
|
|
* make sure there is enough of free address space before the lowest stack
|
|
* address.
|
|
*
|
|
* Kernel default 'stack_guard_gap' size is '256 * getpagesize()'.
|
|
*
|
|
* The stack memory map would look like:
|
|
*
|
|
* | - - - reserved size - - - |
|
|
*
|
|
* +-- - - - --+------------+-------------+
|
|
* | 256 pages | unmapped | mapped |
|
|
* +-- - - - --+------------+-------------+
|
|
* | mapped size |
|
|
* ^ | - - stack size - - |
|
|
* start
|
|
* ^ ^
|
|
* stack bottom stack top
|
|
*
|
|
* # Test2:
|
|
*
|
|
* We allocate stack as we do in the first test but we mmap a page in the
|
|
* space the stack is supposed to grow into and we expect the thread to
|
|
* segfault when the guard page is faulted.
|
|
*/
|
|
|
|
#include <unistd.h>
|
|
#include <pthread.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/types.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
|
|
#include "tst_test.h"
|
|
#include "tst_safe_pthread.h"
|
|
|
|
static long page_size;
|
|
|
|
static bool __attribute__((noinline)) check_stackgrow_up(void)
|
|
{
|
|
char local_var;
|
|
static char *addr;
|
|
|
|
if (!addr) {
|
|
addr = &local_var;
|
|
return check_stackgrow_up();
|
|
}
|
|
|
|
return (addr < &local_var);
|
|
}
|
|
|
|
static void setup(void)
|
|
{
|
|
if (check_stackgrow_up())
|
|
tst_brk(TCONF, "Test can't be performed with stack grows up architecture");
|
|
|
|
page_size = getpagesize();
|
|
}
|
|
|
|
/*
|
|
* Returns stack lowest address. Note that the address is not mapped and will
|
|
* be mapped on page fault when we grow the stack to the lowest address possible.
|
|
*/
|
|
static void *allocate_stack(size_t stack_size, size_t mapped_size)
|
|
{
|
|
void *start, *stack_top, *stack_bottom;
|
|
|
|
long reserved_size = 256 * page_size + stack_size;
|
|
|
|
start = SAFE_MMAP(NULL, reserved_size, PROT_READ | PROT_WRITE,
|
|
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
SAFE_MUNMAP(start, reserved_size);
|
|
|
|
SAFE_MMAP((start + reserved_size - mapped_size), mapped_size, PROT_READ | PROT_WRITE,
|
|
MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN,
|
|
-1, 0);
|
|
|
|
stack_top = start + reserved_size;
|
|
stack_bottom = start + reserved_size - stack_size;
|
|
|
|
tst_res(TINFO, "start = %p, stack_top = %p, stack bottom = %p",
|
|
start, stack_top, stack_bottom);
|
|
tst_res(TINFO, "mapped pages %zu, stack pages %zu",
|
|
mapped_size/page_size, stack_size/page_size);
|
|
|
|
return stack_bottom;
|
|
}
|
|
|
|
static __attribute__((noinline)) void *check_depth_recursive(void *limit)
|
|
{
|
|
if ((off_t) &limit < (off_t) limit) {
|
|
tst_res(TINFO, "&limit = %p, limit = %p", &limit, limit);
|
|
return NULL;
|
|
}
|
|
|
|
return check_depth_recursive(limit);
|
|
}
|
|
|
|
/*
|
|
* We set the limit one page above the stack bottom to make sure that the stack
|
|
* frame will not overflow to the next page, which would potentially cause
|
|
* segfault if we are unlucky and there is a mapping right after the guard gap.
|
|
*
|
|
* Generally the stack frame would be much smaller than page_size so moving the
|
|
* pointer by a few bytes would probably be enough, but we do not want to take
|
|
* any chances.
|
|
*/
|
|
static void grow_stack(void *stack, size_t size)
|
|
{
|
|
pthread_t test_thread;
|
|
pthread_attr_t attr;
|
|
int ret;
|
|
void *limit = stack + page_size;
|
|
|
|
ret = pthread_attr_init(&attr);
|
|
if (ret)
|
|
tst_brk(TBROK, "pthread_attr_init failed during setup");
|
|
|
|
ret = pthread_attr_setstack(&attr, stack, size);
|
|
if (ret)
|
|
tst_brk(TBROK, "pthread_attr_setstack failed during setup");
|
|
|
|
SAFE_PTHREAD_CREATE(&test_thread, &attr, check_depth_recursive, limit);
|
|
SAFE_PTHREAD_JOIN(test_thread, NULL);
|
|
|
|
exit(0);
|
|
}
|
|
|
|
static void grow_stack_success(size_t stack_size, size_t mapped_size)
|
|
{
|
|
pid_t child_pid;
|
|
int wstatus;
|
|
void *stack;
|
|
|
|
child_pid = SAFE_FORK();
|
|
if (!child_pid) {
|
|
stack = allocate_stack(stack_size, mapped_size);
|
|
grow_stack(stack, stack_size);
|
|
}
|
|
|
|
SAFE_WAIT(&wstatus);
|
|
if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0)
|
|
tst_res(TPASS, "Stack grows in unmapped region");
|
|
else
|
|
tst_res(TFAIL, "Child: %s", tst_strstatus(wstatus));
|
|
|
|
}
|
|
|
|
/*
|
|
* We map a page at the bottom of the stack which will cause the thread to be
|
|
* killed with SIGSEGV on faulting the guard page.
|
|
*/
|
|
static void grow_stack_fail(size_t stack_size, size_t mapped_size)
|
|
{
|
|
pid_t child_pid;
|
|
int wstatus;
|
|
void *stack;
|
|
|
|
child_pid = SAFE_FORK();
|
|
if (!child_pid) {
|
|
tst_no_corefile(0);
|
|
stack = allocate_stack(stack_size, mapped_size);
|
|
|
|
SAFE_MMAP(stack, page_size, PROT_READ | PROT_WRITE,
|
|
MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
|
|
tst_res(TINFO, "mapped page at %p", stack);
|
|
|
|
grow_stack(stack, stack_size);
|
|
}
|
|
|
|
SAFE_WAIT(&wstatus);
|
|
if (WIFSIGNALED(wstatus) && WTERMSIG(wstatus) == SIGSEGV)
|
|
tst_res(TPASS, "Child killed by %s as expected", tst_strsig(SIGSEGV));
|
|
else
|
|
tst_res(TFAIL, "Child: %s", tst_strstatus(wstatus));
|
|
}
|
|
|
|
static void run_test(void)
|
|
{
|
|
size_t stack_size = 8 * PTHREAD_STACK_MIN;
|
|
|
|
grow_stack_success(stack_size, PTHREAD_STACK_MIN);
|
|
grow_stack_success(stack_size, stack_size/2);
|
|
grow_stack_fail(stack_size, PTHREAD_STACK_MIN);
|
|
grow_stack_fail(stack_size, stack_size/2);
|
|
}
|
|
|
|
static struct tst_test test = {
|
|
.setup = setup,
|
|
.test_all = run_test,
|
|
.forks_child = 1,
|
|
};
|