1f08c3bdfSopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 2f08c3bdfSopenharmony_ci/* 3f08c3bdfSopenharmony_ci * Copyright (c) Zilogic Systems Pvt. Ltd., 2020 4f08c3bdfSopenharmony_ci * Email: code@zilogic.com 5f08c3bdfSopenharmony_ci */ 6f08c3bdfSopenharmony_ci 7f08c3bdfSopenharmony_ci/* 8f08c3bdfSopenharmony_ci * Test mmap() MAP_GROWSDOWN flag 9f08c3bdfSopenharmony_ci * 10f08c3bdfSopenharmony_ci * # Test1: 11f08c3bdfSopenharmony_ci * 12f08c3bdfSopenharmony_ci * We assign the memory region partially allocated with MAP_GROWSDOWN flag to 13f08c3bdfSopenharmony_ci * a thread as a stack and expect the mapping to grow when we touch the 14f08c3bdfSopenharmony_ci * guard page by calling a recusive function in the thread that uses the 15f08c3bdfSopenharmony_ci * growable mapping as a stack. 16f08c3bdfSopenharmony_ci * 17f08c3bdfSopenharmony_ci * The kernel only grows the memory region when the stack pointer is within 18f08c3bdfSopenharmony_ci * guard page when the guard page is touched so simply faulting the guard 19f08c3bdfSopenharmony_ci * page will not cause the mapping to grow. 20f08c3bdfSopenharmony_ci * 21f08c3bdfSopenharmony_ci * Newer kernels does not allow a MAP_GROWSDOWN mapping to grow closer than 22f08c3bdfSopenharmony_ci * 'stack_guard_gap' pages to an existing mapping. So when we map the stack we 23f08c3bdfSopenharmony_ci * make sure there is enough of free address space before the lowest stack 24f08c3bdfSopenharmony_ci * address. 25f08c3bdfSopenharmony_ci * 26f08c3bdfSopenharmony_ci * Kernel default 'stack_guard_gap' size is '256 * getpagesize()'. 27f08c3bdfSopenharmony_ci * 28f08c3bdfSopenharmony_ci * The stack memory map would look like: 29f08c3bdfSopenharmony_ci * 30f08c3bdfSopenharmony_ci * | - - - reserved size - - - | 31f08c3bdfSopenharmony_ci * 32f08c3bdfSopenharmony_ci * +-- - - - --+------------+-------------+ 33f08c3bdfSopenharmony_ci * | 256 pages | unmapped | mapped | 34f08c3bdfSopenharmony_ci * +-- - - - --+------------+-------------+ 35f08c3bdfSopenharmony_ci * | mapped size | 36f08c3bdfSopenharmony_ci * ^ | - - stack size - - | 37f08c3bdfSopenharmony_ci * start 38f08c3bdfSopenharmony_ci * ^ ^ 39f08c3bdfSopenharmony_ci * stack bottom stack top 40f08c3bdfSopenharmony_ci * 41f08c3bdfSopenharmony_ci * # Test2: 42f08c3bdfSopenharmony_ci * 43f08c3bdfSopenharmony_ci * We allocate stack as we do in the first test but we mmap a page in the 44f08c3bdfSopenharmony_ci * space the stack is supposed to grow into and we expect the thread to 45f08c3bdfSopenharmony_ci * segfault when the guard page is faulted. 46f08c3bdfSopenharmony_ci */ 47f08c3bdfSopenharmony_ci 48f08c3bdfSopenharmony_ci#include <unistd.h> 49f08c3bdfSopenharmony_ci#include <pthread.h> 50f08c3bdfSopenharmony_ci#include <sys/mman.h> 51f08c3bdfSopenharmony_ci#include <sys/wait.h> 52f08c3bdfSopenharmony_ci#include <sys/types.h> 53f08c3bdfSopenharmony_ci#include <stdlib.h> 54f08c3bdfSopenharmony_ci#include <stdbool.h> 55f08c3bdfSopenharmony_ci 56f08c3bdfSopenharmony_ci#include "tst_test.h" 57f08c3bdfSopenharmony_ci#include "tst_safe_pthread.h" 58f08c3bdfSopenharmony_ci 59f08c3bdfSopenharmony_cistatic long page_size; 60f08c3bdfSopenharmony_ci 61f08c3bdfSopenharmony_cistatic bool __attribute__((noinline)) check_stackgrow_up(void) 62f08c3bdfSopenharmony_ci{ 63f08c3bdfSopenharmony_ci char local_var; 64f08c3bdfSopenharmony_ci static char *addr; 65f08c3bdfSopenharmony_ci 66f08c3bdfSopenharmony_ci if (!addr) { 67f08c3bdfSopenharmony_ci addr = &local_var; 68f08c3bdfSopenharmony_ci return check_stackgrow_up(); 69f08c3bdfSopenharmony_ci } 70f08c3bdfSopenharmony_ci 71f08c3bdfSopenharmony_ci return (addr < &local_var); 72f08c3bdfSopenharmony_ci} 73f08c3bdfSopenharmony_ci 74f08c3bdfSopenharmony_cistatic void setup(void) 75f08c3bdfSopenharmony_ci{ 76f08c3bdfSopenharmony_ci if (check_stackgrow_up()) 77f08c3bdfSopenharmony_ci tst_brk(TCONF, "Test can't be performed with stack grows up architecture"); 78f08c3bdfSopenharmony_ci 79f08c3bdfSopenharmony_ci page_size = getpagesize(); 80f08c3bdfSopenharmony_ci} 81f08c3bdfSopenharmony_ci 82f08c3bdfSopenharmony_ci/* 83f08c3bdfSopenharmony_ci * Returns stack lowest address. Note that the address is not mapped and will 84f08c3bdfSopenharmony_ci * be mapped on page fault when we grow the stack to the lowest address possible. 85f08c3bdfSopenharmony_ci */ 86f08c3bdfSopenharmony_cistatic void *allocate_stack(size_t stack_size, size_t mapped_size) 87f08c3bdfSopenharmony_ci{ 88f08c3bdfSopenharmony_ci void *start, *stack_top, *stack_bottom; 89f08c3bdfSopenharmony_ci 90f08c3bdfSopenharmony_ci long reserved_size = 256 * page_size + stack_size; 91f08c3bdfSopenharmony_ci 92f08c3bdfSopenharmony_ci start = SAFE_MMAP(NULL, reserved_size, PROT_READ | PROT_WRITE, 93f08c3bdfSopenharmony_ci MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 94f08c3bdfSopenharmony_ci SAFE_MUNMAP(start, reserved_size); 95f08c3bdfSopenharmony_ci 96f08c3bdfSopenharmony_ci SAFE_MMAP((start + reserved_size - mapped_size), mapped_size, PROT_READ | PROT_WRITE, 97f08c3bdfSopenharmony_ci MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN, 98f08c3bdfSopenharmony_ci -1, 0); 99f08c3bdfSopenharmony_ci 100f08c3bdfSopenharmony_ci stack_top = start + reserved_size; 101f08c3bdfSopenharmony_ci stack_bottom = start + reserved_size - stack_size; 102f08c3bdfSopenharmony_ci 103f08c3bdfSopenharmony_ci tst_res(TINFO, "start = %p, stack_top = %p, stack bottom = %p", 104f08c3bdfSopenharmony_ci start, stack_top, stack_bottom); 105f08c3bdfSopenharmony_ci tst_res(TINFO, "mapped pages %zu, stack pages %zu", 106f08c3bdfSopenharmony_ci mapped_size/page_size, stack_size/page_size); 107f08c3bdfSopenharmony_ci 108f08c3bdfSopenharmony_ci return stack_bottom; 109f08c3bdfSopenharmony_ci} 110f08c3bdfSopenharmony_ci 111f08c3bdfSopenharmony_cistatic __attribute__((noinline)) void *check_depth_recursive(void *limit) 112f08c3bdfSopenharmony_ci{ 113f08c3bdfSopenharmony_ci if ((off_t) &limit < (off_t) limit) { 114f08c3bdfSopenharmony_ci tst_res(TINFO, "&limit = %p, limit = %p", &limit, limit); 115f08c3bdfSopenharmony_ci return NULL; 116f08c3bdfSopenharmony_ci } 117f08c3bdfSopenharmony_ci 118f08c3bdfSopenharmony_ci return check_depth_recursive(limit); 119f08c3bdfSopenharmony_ci} 120f08c3bdfSopenharmony_ci 121f08c3bdfSopenharmony_ci/* 122f08c3bdfSopenharmony_ci * We set the limit one page above the stack bottom to make sure that the stack 123f08c3bdfSopenharmony_ci * frame will not overflow to the next page, which would potentially cause 124f08c3bdfSopenharmony_ci * segfault if we are unlucky and there is a mapping right after the guard gap. 125f08c3bdfSopenharmony_ci * 126f08c3bdfSopenharmony_ci * Generally the stack frame would be much smaller than page_size so moving the 127f08c3bdfSopenharmony_ci * pointer by a few bytes would probably be enough, but we do not want to take 128f08c3bdfSopenharmony_ci * any chances. 129f08c3bdfSopenharmony_ci */ 130f08c3bdfSopenharmony_cistatic void grow_stack(void *stack, size_t size) 131f08c3bdfSopenharmony_ci{ 132f08c3bdfSopenharmony_ci pthread_t test_thread; 133f08c3bdfSopenharmony_ci pthread_attr_t attr; 134f08c3bdfSopenharmony_ci int ret; 135f08c3bdfSopenharmony_ci void *limit = stack + page_size; 136f08c3bdfSopenharmony_ci 137f08c3bdfSopenharmony_ci ret = pthread_attr_init(&attr); 138f08c3bdfSopenharmony_ci if (ret) 139f08c3bdfSopenharmony_ci tst_brk(TBROK, "pthread_attr_init failed during setup"); 140f08c3bdfSopenharmony_ci 141f08c3bdfSopenharmony_ci ret = pthread_attr_setstack(&attr, stack, size); 142f08c3bdfSopenharmony_ci if (ret) 143f08c3bdfSopenharmony_ci tst_brk(TBROK, "pthread_attr_setstack failed during setup"); 144f08c3bdfSopenharmony_ci 145f08c3bdfSopenharmony_ci SAFE_PTHREAD_CREATE(&test_thread, &attr, check_depth_recursive, limit); 146f08c3bdfSopenharmony_ci SAFE_PTHREAD_JOIN(test_thread, NULL); 147f08c3bdfSopenharmony_ci 148f08c3bdfSopenharmony_ci exit(0); 149f08c3bdfSopenharmony_ci} 150f08c3bdfSopenharmony_ci 151f08c3bdfSopenharmony_cistatic void grow_stack_success(size_t stack_size, size_t mapped_size) 152f08c3bdfSopenharmony_ci{ 153f08c3bdfSopenharmony_ci pid_t child_pid; 154f08c3bdfSopenharmony_ci int wstatus; 155f08c3bdfSopenharmony_ci void *stack; 156f08c3bdfSopenharmony_ci 157f08c3bdfSopenharmony_ci child_pid = SAFE_FORK(); 158f08c3bdfSopenharmony_ci if (!child_pid) { 159f08c3bdfSopenharmony_ci stack = allocate_stack(stack_size, mapped_size); 160f08c3bdfSopenharmony_ci grow_stack(stack, stack_size); 161f08c3bdfSopenharmony_ci } 162f08c3bdfSopenharmony_ci 163f08c3bdfSopenharmony_ci SAFE_WAIT(&wstatus); 164f08c3bdfSopenharmony_ci if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0) 165f08c3bdfSopenharmony_ci tst_res(TPASS, "Stack grows in unmapped region"); 166f08c3bdfSopenharmony_ci else 167f08c3bdfSopenharmony_ci tst_res(TFAIL, "Child: %s", tst_strstatus(wstatus)); 168f08c3bdfSopenharmony_ci 169f08c3bdfSopenharmony_ci} 170f08c3bdfSopenharmony_ci 171f08c3bdfSopenharmony_ci/* 172f08c3bdfSopenharmony_ci * We map a page at the bottom of the stack which will cause the thread to be 173f08c3bdfSopenharmony_ci * killed with SIGSEGV on faulting the guard page. 174f08c3bdfSopenharmony_ci */ 175f08c3bdfSopenharmony_cistatic void grow_stack_fail(size_t stack_size, size_t mapped_size) 176f08c3bdfSopenharmony_ci{ 177f08c3bdfSopenharmony_ci pid_t child_pid; 178f08c3bdfSopenharmony_ci int wstatus; 179f08c3bdfSopenharmony_ci void *stack; 180f08c3bdfSopenharmony_ci 181f08c3bdfSopenharmony_ci child_pid = SAFE_FORK(); 182f08c3bdfSopenharmony_ci if (!child_pid) { 183f08c3bdfSopenharmony_ci tst_no_corefile(0); 184f08c3bdfSopenharmony_ci stack = allocate_stack(stack_size, mapped_size); 185f08c3bdfSopenharmony_ci 186f08c3bdfSopenharmony_ci SAFE_MMAP(stack, page_size, PROT_READ | PROT_WRITE, 187f08c3bdfSopenharmony_ci MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 188f08c3bdfSopenharmony_ci 189f08c3bdfSopenharmony_ci tst_res(TINFO, "mapped page at %p", stack); 190f08c3bdfSopenharmony_ci 191f08c3bdfSopenharmony_ci grow_stack(stack, stack_size); 192f08c3bdfSopenharmony_ci } 193f08c3bdfSopenharmony_ci 194f08c3bdfSopenharmony_ci SAFE_WAIT(&wstatus); 195f08c3bdfSopenharmony_ci if (WIFSIGNALED(wstatus) && WTERMSIG(wstatus) == SIGSEGV) 196f08c3bdfSopenharmony_ci tst_res(TPASS, "Child killed by %s as expected", tst_strsig(SIGSEGV)); 197f08c3bdfSopenharmony_ci else 198f08c3bdfSopenharmony_ci tst_res(TFAIL, "Child: %s", tst_strstatus(wstatus)); 199f08c3bdfSopenharmony_ci} 200f08c3bdfSopenharmony_ci 201f08c3bdfSopenharmony_cistatic void run_test(void) 202f08c3bdfSopenharmony_ci{ 203f08c3bdfSopenharmony_ci size_t pthread_stack = LTP_ALIGN(PTHREAD_STACK_MIN, getpagesize()); 204f08c3bdfSopenharmony_ci size_t stack_size = 8 * pthread_stack; 205f08c3bdfSopenharmony_ci 206f08c3bdfSopenharmony_ci grow_stack_success(stack_size, pthread_stack); 207f08c3bdfSopenharmony_ci grow_stack_success(stack_size, stack_size/2); 208f08c3bdfSopenharmony_ci grow_stack_fail(stack_size, pthread_stack); 209f08c3bdfSopenharmony_ci grow_stack_fail(stack_size, stack_size/2); 210f08c3bdfSopenharmony_ci} 211f08c3bdfSopenharmony_ci 212f08c3bdfSopenharmony_cistatic struct tst_test test = { 213f08c3bdfSopenharmony_ci .setup = setup, 214f08c3bdfSopenharmony_ci .test_all = run_test, 215f08c3bdfSopenharmony_ci .forks_child = 1, 216f08c3bdfSopenharmony_ci}; 217