1 /* $OpenBSD: noexec.c,v 1.23 2022/11/27 15:12:57 anton Exp $ */
2
3 /*
4 * Copyright (c) 2002,2003 Michael Shalayeff
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR OR HIS RELATIVES BE LIABLE FOR ANY DIRECT,
22 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * SERVICES; LOSS OF MIND, USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
27 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
28 * THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include <sys/mman.h>
32 #include <unistd.h>
33 #include <stdio.h>
34 #include <signal.h>
35 #include <string.h>
36 #include <stdlib.h>
37 #include <limits.h>
38 #include <errno.h>
39 #include <err.h>
40 #include <pthread.h>
41
42 struct context {
43 char **argv;
44 int argc;
45 };
46
47 volatile sig_atomic_t fail;
48 int page_size;
49 char label[64] = "non-exec ";
50
51 #define PAD 64*1024
52 #define PAGESIZE (1ULL << _MAX_PAGE_SHIFT)
53 #define MAXPAGESIZE 16384
54 #define TESTSZ 256 /* assuming the testfly() will fit */
55 static u_int64_t mutable[(PAD + TESTSZ + PAD + MAXPAGESIZE) / 8]
56 __attribute__((aligned(PAGESIZE)))
57 __attribute__((section(".openbsd.mutable")));
58
59 void testfly(void);
60
61 static void
fdcache(void * p,size_t size)62 fdcache(void *p, size_t size)
63 {
64 #ifdef __hppa__
65 __asm volatile( /* XXX this hardcodes the TESTSZ */
66 "fdc,m %1(%0)\n\t"
67 "fdc,m %1(%0)\n\t"
68 "fdc,m %1(%0)\n\t"
69 "fdc,m %1(%0)\n\t"
70 "fdc,m %1(%0)\n\t"
71 "fdc,m %1(%0)\n\t"
72 "fdc,m %1(%0)\n\t"
73 "fdc,m %1(%0)"
74 : "+r" (p) : "r" (32));
75 #endif
76 #ifdef __sparc64__
77 char *s = p;
78 int i;
79
80 for (i = 0; i < TESTSZ; i += 8)
81 __asm volatile("flush %0" : : "r" (s + i) : "memory");
82 #endif
83 }
84
85 static void
sigsegv(int sig,siginfo_t * sip,void * scp)86 sigsegv(int sig, siginfo_t *sip, void *scp)
87 {
88 _exit(fail);
89 }
90
91 static int
noexec(void * p,size_t size)92 noexec(void *p, size_t size)
93 {
94 fail = 0;
95 printf("%s: execute\n", label);
96 fflush(stdout);
97 ((void (*)(void))p)();
98
99 return (1);
100 }
101
102 static int
noexec_mprotect(void * p,size_t size)103 noexec_mprotect(void *p, size_t size)
104 {
105
106 /* here we must fail on segv since we said it gets executable */
107 fail = 1;
108 if (mprotect(p, size, PROT_READ|PROT_EXEC) < 0)
109 err(1, "mprotect 1");
110 printf("%s: execute\n", label);
111 fflush(stdout);
112 ((void (*)(void))p)();
113
114 /* here we are successful on segv and fail if it still executes */
115 fail = 0;
116 if (mprotect(p, size, PROT_READ) < 0)
117 err(1, "mprotect 2");
118 printf("%s: catch a signal\n", label);
119 fflush(stdout);
120 ((void (*)(void))p)();
121
122 return (1);
123 }
124
125 static void *
getaddr(void * a)126 getaddr(void *a)
127 {
128 void *ret;
129
130 /*
131 * Compile with -fno-inline to get reasonable result when comparing
132 * local variable address with caller's stack.
133 */
134 if ((void *)&ret < a)
135 ret = (void *)((u_long)&ret - 4 * page_size);
136 else
137 ret = (void *)((u_long)&ret + 4 * page_size);
138
139 return (void *)((u_long)ret & ~(page_size - 1));
140 }
141
142 static int
noexec_mmap(void * p,size_t size)143 noexec_mmap(void *p, size_t size)
144 {
145 memcpy(p + page_size * 1, p, page_size);
146 memcpy(p + page_size * 2, p, page_size);
147 fdcache(p + page_size * 1, TESTSZ);
148 fdcache(p + page_size * 2, TESTSZ);
149 if (mprotect(p, size + 2 * page_size, PROT_READ|PROT_EXEC) != 0)
150 err(1, "mprotect");
151
152 /* here we must fail on segv since we said it gets executable */
153 fail = 1;
154
155 printf("%s: execute #1\n", label);
156 fflush(stdout);
157 ((void (*)(void))p)();
158
159 /* unmap the first page to see that the higher page is still exec */
160 if (munmap(p, page_size) < 0)
161 err(1, "munmap");
162
163 p += page_size;
164 printf("%s: execute #2\n", label);
165 fflush(stdout);
166 ((void (*)(void))p)();
167
168 /* unmap the last page to see that the lower page is still exec */
169 if (munmap(p + page_size, page_size) < 0)
170 err(1, "munmap");
171
172 printf("%s: execute #3\n", label);
173 fflush(stdout);
174 ((void (*)(void))p)();
175
176 return (0);
177 }
178
179 static void
usage(void)180 usage(void)
181 {
182 extern char *__progname;
183 fprintf(stderr, "Usage: %s [-s <size>] -[TDBHS] [-p] [-m]\n",
184 __progname);
185 exit(2);
186 }
187
188 static void *
worker(void * arg)189 worker(void *arg)
190 {
191 struct context *ctx = arg;
192 u_int64_t stack[TESTSZ/8]; /* assuming the testfly() will fit */
193 struct sigaction sa;
194 int (*func)(void *, size_t);
195 size_t size;
196 char *ep;
197 void *p, *ptr;
198 int pflags, ch;
199
200 if ((page_size = sysconf(_SC_PAGESIZE)) < 0)
201 err(1, "sysconf");
202
203 setvbuf(stdout, NULL, _IONBF, 0);
204 setvbuf(stderr, NULL, _IONBF, 0);
205
206 p = NULL;
207 pflags = MAP_PRIVATE|MAP_ANON|MAP_FIXED;
208 func = &noexec;
209 size = TESTSZ;
210 while ((ch = getopt(ctx->argc, ctx->argv, "TDBMHSmps:")) != -1) {
211 if (p == NULL) {
212 switch (ch) {
213 case 'T': {
214 u_int64_t *text;
215 size_t textsiz = TESTSZ;
216
217 text = mmap(NULL, textsiz,
218 PROT_READ | PROT_WRITE,
219 MAP_PRIVATE | MAP_ANON, -1, 0);
220 if (text == MAP_FAILED)
221 err(1, "mmap");
222 memcpy(text, &testfly, textsiz);
223 if (mprotect(text, textsiz,
224 PROT_READ | PROT_EXEC) == -1)
225 err(1, "mprotect");
226 p = text;
227 pflags &=~ MAP_FIXED;
228 (void) strlcat(label, "text", sizeof(label));
229 continue;
230 }
231
232 case 'D': {
233 u_int64_t *data;
234 size_t datasiz = (PAD + TESTSZ + PAD +
235 MAXPAGESIZE) / 8;
236
237 data = mmap(NULL, datasiz,
238 PROT_READ | PROT_WRITE,
239 MAP_PRIVATE | MAP_ANON, -1, 0);
240 if (data == MAP_FAILED)
241 err(1, "mmap");
242 p = &data[(PAD + page_size) / 8];
243 p = (void *)((long)p & ~(page_size - 1));
244 (void) strlcat(label, "data", sizeof(label));
245 continue;
246 }
247
248 case 'B': {
249 u_int64_t *bss;
250 size_t bsssiz = (PAD + TESTSZ + PAD +
251 MAXPAGESIZE) / 8;
252
253 bss = mmap(NULL, bsssiz,
254 PROT_READ | PROT_WRITE,
255 MAP_PRIVATE | MAP_ANON, -1, 0);
256 if (bss == MAP_FAILED)
257 err(1, "mmap");
258 p = &bss[(PAD + page_size) / 8];
259 p = (void *)((long)p & ~(page_size - 1));
260 (void) strlcat(label, "bss", sizeof(label));
261 continue;
262 }
263
264 case 'M':
265 p = &mutable[(PAD + page_size) / 8];
266 p = (void *)((long)p & ~(page_size - 1));
267 (void)strlcat(label, "mutable", sizeof(label));
268 continue;
269 case 'H':
270 p = malloc(size + 2 * page_size);
271 if (p == NULL)
272 err(2, "malloc");
273 p += page_size;
274 p = (void *)((long)p & ~(page_size - 1));
275 (void) strlcat(label, "heap", sizeof(label));
276 continue;
277 case 'S':
278 p = getaddr(&stack);
279 pflags |= MAP_STACK;
280 (void) strlcat(label, "stack", sizeof(label));
281 continue;
282 case 's': /* only valid for heap and size */
283 size = strtoul(optarg, &ep, 0);
284 if (size > ULONG_MAX)
285 errno = ERANGE;
286 if (errno)
287 err(1, "invalid size: %s", optarg);
288 if (*ep)
289 errx(1, "invalid size: %s", optarg);
290 continue;
291 }
292 }
293 switch (ch) {
294 case 'm':
295 if (p) {
296 (void) strlcat(label, "-mmap", sizeof(label));
297 } else {
298 pflags = MAP_ANON;
299 func = &noexec_mmap;
300 (void) strlcat(label, "mmap", sizeof(label));
301 }
302 ptr = mmap(p, size + 2 * page_size,
303 PROT_READ|PROT_WRITE, pflags, -1, 0LL);
304 if (ptr == MAP_FAILED) {
305 err(1, "mmap: addr %p, len %zu, prot %d, "
306 "flags %d, fd %d, offset %lld",
307 p, size + 2 * page_size,
308 PROT_READ|PROT_WRITE, pflags, -1, 0LL);
309 }
310 p = ptr;
311 break;
312 case 'p':
313 func = &noexec_mprotect;
314 (void) strlcat(label, "-mprotect", sizeof(label));
315 break;
316 default:
317 usage();
318 }
319 }
320 ctx->argc -= optind;
321 ctx->argv += optind;
322
323 if (ctx->argc > 0)
324 usage();
325
326 if (p == NULL)
327 exit(2);
328
329 sa.sa_sigaction = &sigsegv;
330 sa.sa_flags = SA_SIGINFO;
331 sigemptyset(&sa.sa_mask);
332 sigaction(SIGSEGV, &sa, NULL);
333
334 if (p != &testfly) {
335 memcpy(p, &testfly, TESTSZ);
336 fdcache(p, size);
337 }
338
339 exit((*func)(p, size));
340 /* NOTREACHED */
341 return NULL;
342 }
343
344 int
main(int argc,char * argv[])345 main(int argc, char *argv[])
346 {
347 struct context ctx = {.argc = argc, .argv = argv};
348 pthread_t th;
349 int error;
350
351 if ((error = pthread_create(&th, NULL, worker, (void *)&ctx)))
352 errc(1, error, "pthread_create");
353 if ((error = pthread_join(th, NULL)))
354 errc(1, error, "pthread_join");
355 return 0;
356 }
357