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 */ 45void make_stack1(void); 46asm( 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 */ 74void make_stack2(uint64_t p); 75asm( 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 97static volatile int test_state = 0; 98static 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 112static 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 153static 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 161int 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