1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Test that loads/stores expand the stack segment, or trigger a SEGV, in 4 * various conditions. 5 * 6 * Based on test code by Tom Lane. 7 */ 8 9 #undef NDEBUG 10 #include <assert.h> 11 12 #include <err.h> 13 #include <errno.h> 14 #include <stdio.h> 15 #include <signal.h> 16 #include <stdlib.h> 17 #include <string.h> 18 #include <sys/resource.h> 19 #include <sys/time.h> 20 #include <sys/types.h> 21 #include <sys/wait.h> 22 #include <unistd.h> 23 24 #define _KB (1024) 25 #define _MB (1024 * 1024) 26 27 volatile char *stack_top_ptr; 28 volatile unsigned long stack_top_sp; 29 volatile char c; 30 31 enum access_type { 32 LOAD, 33 STORE, 34 }; 35 36 /* 37 * Consume stack until the stack pointer is below @target_sp, then do an access 38 * (load or store) at offset @delta from either the base of the stack or the 39 * current stack pointer. 40 */ 41 __attribute__ ((noinline)) 42 int consume_stack(unsigned long target_sp, unsigned long stack_high, int delta, enum access_type type) 43 { 44 unsigned long target; 45 char stack_cur; 46 47 if ((unsigned long)&stack_cur > target_sp) 48 return consume_stack(target_sp, stack_high, delta, type); 49 else { 50 // We don't really need this, but without it GCC might not 51 // generate a recursive call above. 52 stack_top_ptr = &stack_cur; 53 54 #ifdef __powerpc__ 55 asm volatile ("mr %[sp], %%r1" : [sp] "=r" (stack_top_sp)); 56 #else 57 asm volatile ("mov %%rsp, %[sp]" : [sp] "=r" (stack_top_sp)); 58 #endif 59 target = stack_high - delta + 1; 60 volatile char *p = (char *)target; 61 62 if (type == STORE) 63 *p = c; 64 else 65 c = *p; 66 67 // Do something to prevent the stack frame being popped prior to 68 // our access above. 69 getpid(); 70 } 71 72 return 0; 73 } 74 75 static int search_proc_maps(char *needle, unsigned long *low, unsigned long *high) 76 { 77 unsigned long start, end; 78 static char buf[4096]; 79 char name[128]; 80 FILE *f; 81 int rc; 82 83 f = fopen("/proc/self/maps", "r"); 84 if (!f) { 85 perror("fopen"); 86 return -1; 87 } 88 89 while (fgets(buf, sizeof(buf), f)) { 90 rc = sscanf(buf, "%lx-%lx %*c%*c%*c%*c %*x %*d:%*d %*d %127s\n", 91 &start, &end, name); 92 if (rc == 2) 93 continue; 94 95 if (rc != 3) { 96 printf("sscanf errored\n"); 97 rc = -1; 98 break; 99 } 100 101 if (strstr(name, needle)) { 102 *low = start; 103 *high = end - 1; 104 rc = 0; 105 break; 106 } 107 } 108 109 fclose(f); 110 111 return rc; 112 } 113 114 int child(unsigned int stack_used, int delta, enum access_type type) 115 { 116 unsigned long low, stack_high; 117 118 assert(search_proc_maps("[stack]", &low, &stack_high) == 0); 119 120 assert(consume_stack(stack_high - stack_used, stack_high, delta, type) == 0); 121 122 printf("Access OK: %s delta %-7d used size 0x%06x stack high 0x%lx top_ptr %p top sp 0x%lx actual used 0x%lx\n", 123 type == LOAD ? "load" : "store", delta, stack_used, stack_high, 124 stack_top_ptr, stack_top_sp, stack_high - stack_top_sp + 1); 125 126 return 0; 127 } 128 129 static int test_one(unsigned int stack_used, int delta, enum access_type type) 130 { 131 pid_t pid; 132 int rc; 133 134 pid = fork(); 135 if (pid == 0) 136 exit(child(stack_used, delta, type)); 137 138 assert(waitpid(pid, &rc, 0) != -1); 139 140 if (WIFEXITED(rc) && WEXITSTATUS(rc) == 0) 141 return 0; 142 143 // We don't expect a non-zero exit that's not a signal 144 assert(!WIFEXITED(rc)); 145 146 printf("Faulted: %s delta %-7d used size 0x%06x signal %d\n", 147 type == LOAD ? "load" : "store", delta, stack_used, 148 WTERMSIG(rc)); 149 150 return 1; 151 } 152 153 // This is fairly arbitrary but is well below any of the targets below, 154 // so that the delta between the stack pointer and the target is large. 155 #define DEFAULT_SIZE (32 * _KB) 156 157 static void test_one_type(enum access_type type, unsigned long page_size, unsigned long rlim_cur) 158 { 159 unsigned long delta; 160 161 // We should be able to access anywhere within the rlimit 162 for (delta = page_size; delta <= rlim_cur; delta += page_size) 163 assert(test_one(DEFAULT_SIZE, delta, type) == 0); 164 165 assert(test_one(DEFAULT_SIZE, rlim_cur, type) == 0); 166 167 // But if we go past the rlimit it should fail 168 assert(test_one(DEFAULT_SIZE, rlim_cur + 1, type) != 0); 169 } 170 171 static int test(void) 172 { 173 unsigned long page_size; 174 struct rlimit rlimit; 175 176 page_size = getpagesize(); 177 getrlimit(RLIMIT_STACK, &rlimit); 178 printf("Stack rlimit is 0x%lx\n", rlimit.rlim_cur); 179 180 printf("Testing loads ...\n"); 181 test_one_type(LOAD, page_size, rlimit.rlim_cur); 182 printf("Testing stores ...\n"); 183 test_one_type(STORE, page_size, rlimit.rlim_cur); 184 185 printf("All OK\n"); 186 187 return 0; 188 } 189 190 #ifdef __powerpc__ 191 #include "utils.h" 192 193 int main(void) 194 { 195 return test_harness(test, "stack_expansion_ldst"); 196 } 197 #else 198 int main(void) 199 { 200 return test(); 201 } 202 #endif 203