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 | |