1 /* 2 * Copyright (c) 2023 Alexey Dobriyan <adobriyan@gmail.com> 3 * 4 * Permission to use, copy, modify, and distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16 /* 17 * Test that userspace stack is NX. Requires linking with -Wl,-z,noexecstack 18 * because I don't want to bother with PT_GNU_STACK detection. 19 * 20 * Fill the stack with INT3's and then try to execute some of them: 21 * SIGSEGV -- good, SIGTRAP -- bad. 22 * 23 * Regular stack is completely overwritten before testing. 24 * Test doesn't exit SIGSEGV handler after first fault at INT3. 25 */ 26 #undef _GNU_SOURCE 27 #define _GNU_SOURCE 28 #undef NDEBUG 29 #include <assert.h> 30 #include <signal.h> 31 #include <stdint.h> 32 #include <stdio.h> 33 #include <stdlib.h> 34 #include <sys/mman.h> 35 #include <sys/resource.h> 36 #include <unistd.h> 37 38 #define PAGE_SIZE 4096 39 40 /* 41 * This is memset(rsp, 0xcc, -1); but down. 42 * It will SIGSEGV when bottom of the stack is reached. 43 * Byte-size access is important! (see rdi tweak in the signal handler). 44 */ 45 void make_stack1(void); 46 asm( 47 ".pushsection .text\n" 48 ".globl make_stack1\n" 49 ".align 16\n" 50 "make_stack1:\n" 51 "mov $0xcc, %al\n" 52 #if defined __amd64__ 53 "mov %rsp, %rdi\n" 54 "mov $-1, %rcx\n" 55 #elif defined __i386__ 56 "mov %esp, %edi\n" 57 "mov $-1, %ecx\n" 58 #else 59 #error 60 #endif 61 "std\n" 62 "rep stosb\n" 63 /* unreachable */ 64 "hlt\n" 65 ".type make_stack1,@function\n" 66 ".size make_stack1,.-make_stack1\n" 67 ".popsection\n" 68 ); 69 70 /* 71 * memset(p, 0xcc, -1); 72 * It will SIGSEGV when top of the stack is reached. 73 */ 74 void make_stack2(uint64_t p); 75 asm( 76 ".pushsection .text\n" 77 ".globl make_stack2\n" 78 ".align 16\n" 79 "make_stack2:\n" 80 "mov $0xcc, %al\n" 81 #if defined __amd64__ 82 "mov $-1, %rcx\n" 83 #elif defined __i386__ 84 "mov $-1, %ecx\n" 85 #else 86 #error 87 #endif 88 "cld\n" 89 "rep stosb\n" 90 /* unreachable */ 91 "hlt\n" 92 ".type make_stack2,@function\n" 93 ".size make_stack2,.-make_stack2\n" 94 ".popsection\n" 95 ); 96 97 static volatile int test_state = 0; 98 static volatile unsigned long stack_min_addr; 99 100 #if defined __amd64__ 101 #define RDI REG_RDI 102 #define RIP REG_RIP 103 #define RIP_STRING "rip" 104 #elif defined __i386__ 105 #define RDI REG_EDI 106 #define RIP REG_EIP 107 #define RIP_STRING "eip" 108 #else 109 #error 110 #endif 111 112 static void sigsegv(int _, siginfo_t *__, void *uc_) 113 { 114 /* 115 * Some Linux versions didn't clear DF before entering signal 116 * handler. make_stack1() doesn't have a chance to clear DF 117 * either so we clear it by hand here. 118 */ 119 asm volatile ("cld" ::: "memory"); 120 121 ucontext_t *uc = uc_; 122 123 if (test_state == 0) { 124 /* Stack is faulted and cleared from RSP to the lowest address. */ 125 stack_min_addr = ++uc->uc_mcontext.gregs[RDI]; 126 if (1) { 127 printf("stack min %lx\n", stack_min_addr); 128 } 129 uc->uc_mcontext.gregs[RIP] = (uintptr_t)&make_stack2; 130 test_state = 1; 131 } else if (test_state == 1) { 132 /* Stack has been cleared from top to bottom. */ 133 unsigned long stack_max_addr = uc->uc_mcontext.gregs[RDI]; 134 if (1) { 135 printf("stack max %lx\n", stack_max_addr); 136 } 137 /* Start faulting pages on stack and see what happens. */ 138 uc->uc_mcontext.gregs[RIP] = stack_max_addr - PAGE_SIZE; 139 test_state = 2; 140 } else if (test_state == 2) { 141 /* Stack page is NX -- good, test next page. */ 142 uc->uc_mcontext.gregs[RIP] -= PAGE_SIZE; 143 if (uc->uc_mcontext.gregs[RIP] == stack_min_addr) { 144 /* One more SIGSEGV and test ends. */ 145 test_state = 3; 146 } 147 } else { 148 printf("PASS\tAll stack pages are NX\n"); 149 _exit(EXIT_SUCCESS); 150 } 151 } 152 153 static void sigtrap(int _, siginfo_t *__, void *uc_) 154 { 155 const ucontext_t *uc = uc_; 156 unsigned long rip = uc->uc_mcontext.gregs[RIP]; 157 printf("FAIL\texecutable page on the stack: " RIP_STRING " %lx\n", rip); 158 _exit(EXIT_FAILURE); 159 } 160 161 int main(void) 162 { 163 { 164 struct sigaction act = {}; 165 sigemptyset(&act.sa_mask); 166 act.sa_flags = SA_SIGINFO; 167 act.sa_sigaction = &sigsegv; 168 int rv = sigaction(SIGSEGV, &act, NULL); 169 assert(rv == 0); 170 } 171 { 172 struct sigaction act = {}; 173 sigemptyset(&act.sa_mask); 174 act.sa_flags = SA_SIGINFO; 175 act.sa_sigaction = &sigtrap; 176 int rv = sigaction(SIGTRAP, &act, NULL); 177 assert(rv == 0); 178 } 179 { 180 struct rlimit rlim; 181 int rv = getrlimit(RLIMIT_STACK, &rlim); 182 assert(rv == 0); 183 /* Cap stack at time-honored 8 MiB value. */ 184 rlim.rlim_max = rlim.rlim_cur; 185 if (rlim.rlim_max > 8 * 1024 * 1024) { 186 rlim.rlim_max = 8 * 1024 * 1024; 187 } 188 rv = setrlimit(RLIMIT_STACK, &rlim); 189 assert(rv == 0); 190 } 191 { 192 /* 193 * We don't know now much stack SIGSEGV handler uses. 194 * Bump this by 1 page every time someone complains, 195 * or rewrite it in assembly. 196 */ 197 const size_t len = SIGSTKSZ; 198 void *p = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); 199 assert(p != MAP_FAILED); 200 stack_t ss = {}; 201 ss.ss_sp = p; 202 ss.ss_size = len; 203 int rv = sigaltstack(&ss, NULL); 204 assert(rv == 0); 205 } 206 make_stack1(); 207 /* 208 * Unreachable, but if _this_ INT3 is ever reached, it's a bug somewhere. 209 * Fold it into main SIGTRAP pathway. 210 */ 211 __builtin_trap(); 212 } 213