213 lines · c
1/*2 * Copyright (c) 2023 Alexey Dobriyan <adobriyan@gmail.com>3 *4 * Permission to use, copy, modify, and distribute this software for any5 * purpose with or without fee is hereby granted, provided that the above6 * copyright notice and this permission notice appear in all copies.7 *8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF14 * 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,noexecstack18 * 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_SOURCE27#define _GNU_SOURCE28#undef NDEBUG29#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 409639 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#else59#error60#endif61 "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#else86#error87#endif88 "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_RDI102#define RIP REG_RIP103#define RIP_STRING "rip"104#elif defined __i386__105#define RDI REG_EDI106#define RIP REG_EIP107#define RIP_STRING "eip"108#else109#error110#endif111 112static void sigsegv(int _, siginfo_t *__, void *uc_)113{114 /*115 * Some Linux versions didn't clear DF before entering signal116 * handler. make_stack1() doesn't have a chance to clear DF117 * 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