stack_clash.c revision c3b43efc8174e236c02ffeb27434bbc816f2ee43
1/* 2 * Copyright (c) 2017 Pavel Boldin <pboldin@cloudlinux.com> 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 * 17 * Adapted from code by Michal Hocko. 18 */ 19 20/* This is a regression test of the Stack Clash [1] vulnerability. This tests 21 * that there is at least 256 PAGE_SIZE of stack guard gap which is considered 22 * hard to hop above. Code adapted from the Novell's bugzilla [2]. 23 * 24 * The code `mmap(2)`s region close to the stack end. The code then allocates 25 * memory on stack until it hits guard page and SIGSEGV or SIGBUS is generated 26 * by the kernel. The signal handler checks that fault address is further than 27 * THRESHOLD from the mmapped area. 28 * 29 * We read /proc/self/maps to examine exact top of the stack and `mmap(2)` 30 * our region exactly GAP_PAGES * PAGE_SIZE away. We read /proc/cmdline to 31 * see if a different stack_guard_gap size is configured. We set stack limit 32 * to infinity and preallocate REQ_STACK_SIZE bytes of stack so that no calls 33 * after `mmap` are moving stack further. 34 * 35 * [1] https://blog.qualys.com/securitylabs/2017/06/19/the-stack-clash 36 * [2] https://bugzilla.novell.com/show_bug.cgi?id=CVE-2017-1000364 37 */ 38 39#include <sys/mman.h> 40#include <sys/wait.h> 41#include <stdio.h> 42#include <unistd.h> 43#include <alloca.h> 44#include <signal.h> 45#include <stdlib.h> 46 47#include "tst_test.h" 48#include "tst_safe_stdio.h" 49 50static unsigned long page_size; 51static unsigned long page_mask; 52static unsigned long GAP_PAGES = 256; 53static unsigned long THRESHOLD; 54static int STACK_GROWSDOWN; 55 56#define SIGNAL_STACK_SIZE (1UL<<20) 57#define FRAME_SIZE 1024 58#define REQ_STACK_SIZE (1024 * 1024) 59 60#define EXIT_TESTBROKE TBROK 61 62void exhaust_stack_into_sigsegv(void) 63{ 64 volatile char * ptr = alloca(FRAME_SIZE - sizeof(long)); 65 *ptr = '\0'; 66 exhaust_stack_into_sigsegv(); 67} 68 69#define MAPPED_LEN page_size 70static unsigned long mapped_addr; 71 72void segv_handler(int sig, siginfo_t *info, void *data LTP_ATTRIBUTE_UNUSED) 73{ 74 unsigned long fault_addr = (unsigned long)info->si_addr; 75 unsigned long mmap_end = mapped_addr + MAPPED_LEN; 76 ssize_t diff; 77 78 if (sig != SIGSEGV && sig != SIGBUS) 79 return; 80 81 if (STACK_GROWSDOWN) 82 diff = fault_addr - mmap_end; 83 else 84 diff = mapped_addr - fault_addr; 85 86 tst_res(TINFO, 87 "mmap = [%lx, %lx), addr = %lx, diff = %lx, THRESHOLD = %lx", 88 mapped_addr, mmap_end, fault_addr, diff, THRESHOLD); 89 if (diff < 0 || (unsigned long)diff < THRESHOLD) 90 _exit(EXIT_FAILURE); 91 else 92 _exit(EXIT_SUCCESS); 93} 94 95unsigned long read_stack_addr_from_proc(unsigned long *stack_size) 96{ 97 FILE *fh; 98 char buf[1024]; 99 unsigned long stack_top = -1UL, start, end; 100 101 fh = SAFE_FOPEN("/proc/self/maps", "r"); 102 103 while (!feof(fh)) { 104 if (fgets(buf, sizeof(buf), fh) == NULL) { 105 tst_brk(TBROK | TERRNO, "fgets"); 106 goto out; 107 } 108 109 if (!strstr(buf, "[stack")) 110 continue; 111 112 if (sscanf(buf, "%lx-%lx", &start, &end) != 2) { 113 tst_brk(TBROK | TERRNO, "sscanf"); 114 goto out; 115 } 116 117 *stack_size = end - start; 118 119 if (STACK_GROWSDOWN) 120 stack_top = start; 121 else 122 stack_top = end; 123 break; 124 } 125 126out: 127 SAFE_FCLOSE(fh); 128 return stack_top; 129} 130 131void dump_proc_self_maps(void) 132{ 133 static char buf[64]; 134 static const char *cmd[] = {"cat", buf, NULL}; 135 sprintf(buf, "/proc/%d/maps", getpid()); 136 tst_run_cmd(cmd, NULL, NULL, 0); 137} 138 139void preallocate_stack(unsigned long required) 140{ 141 volatile char *garbage; 142 143 garbage = alloca(required); 144 garbage[0] = garbage[required - 1] = '\0'; 145} 146 147void do_child(void) 148{ 149 unsigned long stack_addr, stack_size; 150 stack_t signal_stack; 151 struct sigaction segv_sig = {.sa_sigaction = segv_handler, .sa_flags = SA_ONSTACK|SA_SIGINFO}; 152 void *map; 153 unsigned long gap = GAP_PAGES * page_size; 154 struct rlimit rlimit; 155 156 rlimit.rlim_cur = rlimit.rlim_max = RLIM_INFINITY; 157 SAFE_SETRLIMIT(RLIMIT_STACK, &rlimit); 158 159 preallocate_stack(REQ_STACK_SIZE); 160 161 stack_addr = read_stack_addr_from_proc(&stack_size); 162 if (stack_addr == -1UL) { 163 tst_brk(TBROK, "can't read stack top from /proc/self/maps"); 164 return; 165 } 166 167 if (STACK_GROWSDOWN) 168 mapped_addr = stack_addr - gap - MAPPED_LEN; 169 else 170 mapped_addr = stack_addr + gap; 171 172 mapped_addr &= page_mask; 173 map = SAFE_MMAP((void *)mapped_addr, MAPPED_LEN, 174 PROT_READ|PROT_WRITE, 175 MAP_ANON|MAP_PRIVATE|MAP_FIXED, -1, 0); 176 tst_res(TINFO, "Stack:0x%lx+0x%lx mmap:%p+0x%lx", 177 stack_addr, stack_size, map, MAPPED_LEN); 178 179 signal_stack.ss_sp = SAFE_MALLOC(SIGNAL_STACK_SIZE); 180 signal_stack.ss_size = SIGNAL_STACK_SIZE; 181 signal_stack.ss_flags = 0; 182 if (sigaltstack(&signal_stack, NULL) == -1) { 183 tst_brk(TBROK | TERRNO, "sigaltstack"); 184 return; 185 } 186 if (sigaction(SIGSEGV, &segv_sig, NULL) == -1 || 187 sigaction(SIGBUS, &segv_sig, NULL) == -1) { 188 tst_brk(TBROK | TERRNO, "sigaction"); 189 return; 190 } 191 192#ifdef DEBUG 193 dump_proc_self_maps(); 194#endif 195 196 exhaust_stack_into_sigsegv(); 197} 198 199void setup(void) 200{ 201 char buf[4096], *p; 202 203 page_size = sysconf(_SC_PAGESIZE); 204 page_mask = ~(page_size - 1); 205 206 buf[4095] = '\0'; 207 SAFE_FILE_SCANF("/proc/cmdline", "%4095[^\n]", buf); 208 209 if ((p = strstr(buf, "stack_guard_gap=")) != NULL) { 210 if (sscanf(p, "stack_guard_gap=%ld", &GAP_PAGES) != 1) { 211 tst_brk(TBROK | TERRNO, "sscanf"); 212 return; 213 } 214 tst_res(TINFO, "stack_guard_gap = %ld", GAP_PAGES); 215 } 216 217 THRESHOLD = (GAP_PAGES - 1) * page_size; 218 219 { 220 volatile int *a = alloca(128); 221 222 { 223 volatile int *b = alloca(128); 224 225 STACK_GROWSDOWN = a > b; 226 tst_res(TINFO, "STACK_GROWSDOWN = %d == %p > %p", STACK_GROWSDOWN, a, b); 227 } 228 } 229} 230 231void stack_clash_test(void) 232{ 233 int status; 234 pid_t pid; 235 236 pid = SAFE_FORK(); 237 if (!pid) { 238 do_child(); 239 exit(EXIT_TESTBROKE); 240 return; 241 } 242 243 SAFE_WAITPID(pid, &status, 0); 244 245 if (WIFEXITED(status)) { 246 switch (WEXITSTATUS(status)) { 247 case EXIT_FAILURE: 248 tst_res(TFAIL, "stack is too close to the mmaped area"); 249 return; 250 case EXIT_SUCCESS: 251 tst_res(TPASS, "stack is far enough from mmaped area"); 252 return; 253 default: 254 case EXIT_TESTBROKE: 255 break; 256 } 257 } 258 259 tst_brk(TBROK, "child did not exit gracefully"); 260} 261 262static struct tst_test test = { 263 .forks_child = 1, 264 .needs_root = 1, 265 .setup = setup, 266 .test_all = stack_clash_test, 267}; 268