xref: /openbsd-src/regress/sys/kern/noexec/noexec.c (revision 511bcdc49140787e0039904e43c14b7b929509c3)
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