brintos

brintos / linux-shallow public Read only

0
0
Text · 5.4 KiB · ea4a4e2 Raw
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