xref: /netbsd-src/tests/lib/libc/sys/t_mprotect.c (revision deb6f0161a9109e7de9b519dc8dfb9478668dcdd)
1 /* $NetBSD: t_mprotect.c,v 1.7 2017/05/06 21:34:52 joerg Exp $ */
2 
3 /*-
4  * Copyright (c) 2011 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Jukka Ruohonen.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 #include <sys/cdefs.h>
32 __RCSID("$NetBSD: t_mprotect.c,v 1.7 2017/05/06 21:34:52 joerg Exp $");
33 
34 #include <sys/param.h>
35 #include <sys/mman.h>
36 #include <sys/sysctl.h>
37 #include <sys/wait.h>
38 
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <unistd.h>
44 
45 #include <atf-c.h>
46 
47 #include "../common/exec_prot.h"
48 
49 static long	page = 0;
50 static char	path[] = "mmap";
51 
52 static void	sighandler(int);
53 static bool	paxinit(void);
54 
55 static void
56 sighandler(int signo)
57 {
58 	_exit(signo);
59 }
60 
61 static bool
62 paxinit(void)
63 {
64 	size_t len = sizeof(int);
65 	int pax_flags;
66 	int rv;
67 
68 	rv = sysctlbyname("proc.curproc.paxflags",
69 	    &pax_flags, &len, NULL, 0);
70 
71 	if (rv != 0)
72 		return false;
73 
74 	return ((pax_flags & CTL_PROC_PAXFLAGS_MPROTECT) != 0);
75 }
76 
77 ATF_TC_WITH_CLEANUP(mprotect_access);
78 ATF_TC_HEAD(mprotect_access, tc)
79 {
80 	atf_tc_set_md_var(tc, "descr", "Test for EACCES from mprotect(2)");
81 }
82 
83 ATF_TC_BODY(mprotect_access, tc)
84 {
85 	int prot[2] = { PROT_NONE, PROT_READ };
86 	void *map;
87 	size_t i;
88 	int fd;
89 
90 	fd = open(path, O_RDONLY | O_CREAT);
91 	ATF_REQUIRE(fd >= 0);
92 
93 	/*
94 	 * The call should fail with EACCES if we try to mark
95 	 * a PROT_NONE or PROT_READ file/section as PROT_WRITE.
96 	 */
97 	for (i = 0; i < __arraycount(prot); i++) {
98 
99 		map = mmap(NULL, page, prot[i], MAP_SHARED, fd, 0);
100 
101 		if (map == MAP_FAILED)
102 			continue;
103 
104 		errno = 0;
105 
106 		ATF_REQUIRE(mprotect(map, page, PROT_WRITE) != 0);
107 		ATF_REQUIRE(errno == EACCES);
108 		ATF_REQUIRE(munmap(map, page) == 0);
109 	}
110 
111 	ATF_REQUIRE(close(fd) == 0);
112 }
113 
114 ATF_TC_CLEANUP(mprotect_access, tc)
115 {
116 	(void)unlink(path);
117 }
118 
119 ATF_TC(mprotect_err);
120 ATF_TC_HEAD(mprotect_err, tc)
121 {
122 	atf_tc_set_md_var(tc, "descr", "Test error conditions of mprotect(2)");
123 }
124 
125 ATF_TC_BODY(mprotect_err, tc)
126 {
127 	errno = 0;
128 
129 	ATF_REQUIRE(mprotect((char *)-1, 1, PROT_READ) != 0);
130 	ATF_REQUIRE(errno == EINVAL);
131 }
132 
133 ATF_TC(mprotect_exec);
134 ATF_TC_HEAD(mprotect_exec, tc)
135 {
136 	atf_tc_set_md_var(tc, "descr",
137 	    "Test mprotect(2) executable space protections");
138 }
139 
140 /*
141  * Trivial function -- should fit into a page
142  */
143 ATF_TC_BODY(mprotect_exec, tc)
144 {
145 	pid_t pid;
146 	void *map;
147 	int sta, xp_support;
148 
149 	xp_support = exec_prot_support();
150 
151 	switch (xp_support) {
152 	case NOTIMPL:
153 		atf_tc_skip(
154 		    "Execute protection callback check not implemented");
155 		break;
156 	case NO_XP:
157 		atf_tc_skip(
158 		    "Host does not support executable space protection");
159 		break;
160 	case PARTIAL_XP: case PERPAGE_XP: default:
161 		break;
162 	}
163 
164 	/*
165 	 * Map a page read/write and copy a trivial assembly function inside.
166 	 * We will then change the mapping rights:
167 	 * - first by setting the execution right, and check that we can
168 	 *   call the code found in the allocated page.
169 	 * - second by removing the execution right. This should generate
170 	 *   a SIGSEGV on architectures that can enforce --x permissions.
171 	 */
172 
173 	map = mmap(NULL, page, PROT_MPROTECT(PROT_EXEC)|PROT_WRITE|PROT_READ,
174 	    MAP_ANON, -1, 0);
175 	ATF_REQUIRE(map != MAP_FAILED);
176 
177 	memcpy(map, (void *)return_one,
178 	    (uintptr_t)return_one_end - (uintptr_t)return_one);
179 
180 	/* give r-x rights then call code in page */
181 	ATF_REQUIRE(mprotect(map, page, PROT_EXEC|PROT_READ) == 0);
182 	ATF_REQUIRE(((int (*)(void))map)() == 1);
183 
184 	/* remove --x right */
185 	ATF_REQUIRE(mprotect(map, page, PROT_READ) == 0);
186 
187 	pid = fork();
188 	ATF_REQUIRE(pid >= 0);
189 
190 	if (pid == 0) {
191 		ATF_REQUIRE(signal(SIGSEGV, sighandler) != SIG_ERR);
192 		ATF_CHECK(((int (*)(void))map)() == 1);
193 		_exit(0);
194 	}
195 
196 	(void)wait(&sta);
197 
198 	ATF_REQUIRE(munmap(map, page) == 0);
199 
200 	ATF_REQUIRE(WIFEXITED(sta) != 0);
201 
202 	switch (xp_support) {
203 	case PARTIAL_XP:
204 		/* Partial protection might fail; skip the test when it does */
205 		if (WEXITSTATUS(sta) != SIGSEGV) {
206 			atf_tc_skip("Host only supports "
207 			    "partial executable space protection");
208 		}
209 		break;
210 	case PERPAGE_XP: default:
211 		/* Per-page --x protection should not fail */
212 		ATF_REQUIRE(WEXITSTATUS(sta) == SIGSEGV);
213 		break;
214 	}
215 }
216 
217 ATF_TC(mprotect_pax);
218 ATF_TC_HEAD(mprotect_pax, tc)
219 {
220 	atf_tc_set_md_var(tc, "descr", "PaX restrictions and mprotect(2)");
221 	atf_tc_set_md_var(tc, "require.user", "root");
222 }
223 
224 ATF_TC_BODY(mprotect_pax, tc)
225 {
226 	const int prot[4] = { PROT_NONE, PROT_READ, PROT_WRITE };
227 	const char *str = NULL;
228 	void *map;
229 	size_t i;
230 	int rv;
231 
232 	if (!paxinit())
233 		atf_tc_skip("PaX MPROTECT restrictions not enabled");
234 
235 	/*
236 	 * As noted in the original PaX documentation [1],
237 	 * the following restrictions should apply:
238 	 *
239 	 *   (1) creating executable anonymous mappings
240 	 *
241 	 *   (2) creating executable/writable file mappings
242 	 *
243 	 *   (3) making a non-executable mapping executable
244 	 *
245 	 *   (4) making an executable/read-only file mapping
246 	 *       writable except for performing relocations
247 	 *       on an ET_DYN ELF file (non-PIC shared library)
248 	 *
249 	 *  The following will test only the case (3).
250 	 *
251 	 * [1] http://pax.grsecurity.net/docs/mprotect.txt
252 	 *
253 	 *     (Sun Apr 3 11:06:53 EEST 2011.)
254 	 */
255 	for (i = 0; i < __arraycount(prot); i++) {
256 
257 		map = mmap(NULL, page, prot[i], MAP_ANON, -1, 0);
258 
259 		if (map == MAP_FAILED)
260 			continue;
261 
262 		rv = mprotect(map, 1, prot[i] | PROT_EXEC);
263 
264 		(void)munmap(map, page);
265 
266 		if (rv == 0) {
267 			str = "non-executable mapping made executable";
268 			goto out;
269 		}
270 	}
271 
272 out:
273 	if (str != NULL)
274 		atf_tc_fail("%s", str);
275 }
276 
277 ATF_TC(mprotect_write);
278 ATF_TC_HEAD(mprotect_write, tc)
279 {
280 	atf_tc_set_md_var(tc, "descr", "Test mprotect(2) write protections");
281 }
282 
283 ATF_TC_BODY(mprotect_write, tc)
284 {
285 	pid_t pid;
286 	void *map;
287 	int sta;
288 
289 	/*
290 	 * Map a page read/write, change the protection
291 	 * to read-only with mprotect(2), and try to write
292 	 * to the page. This should generate a SIGSEGV.
293 	 */
294 	map = mmap(NULL, page, PROT_WRITE|PROT_READ, MAP_ANON, -1, 0);
295 	ATF_REQUIRE(map != MAP_FAILED);
296 
297 	ATF_REQUIRE(strlcpy(map, "XXX", 3) == 3);
298 	ATF_REQUIRE(mprotect(map, page, PROT_READ) == 0);
299 
300 	pid = fork();
301 	ATF_REQUIRE(pid >= 0);
302 
303 	if (pid == 0) {
304 		ATF_REQUIRE(signal(SIGSEGV, sighandler) != SIG_ERR);
305 		ATF_REQUIRE(strlcpy(map, "XXX", 3) == 0);
306 	}
307 
308 	(void)wait(&sta);
309 
310 	ATF_REQUIRE(WIFEXITED(sta) != 0);
311 	ATF_REQUIRE(WEXITSTATUS(sta) == SIGSEGV);
312 	ATF_REQUIRE(munmap(map, page) == 0);
313 }
314 
315 ATF_TC(mprotect_mremap_exec);
316 ATF_TC_HEAD(mprotect_mremap_exec, tc)
317 {
318 	atf_tc_set_md_var(tc, "descr",
319 	    "Test mremap(2)+mprotect(2) executable space protections");
320 }
321 
322 /*
323  * Trivial function -- should fit into a page
324  */
325 ATF_TC_BODY(mprotect_mremap_exec, tc)
326 {
327 	void *map, *map2;
328 	pid_t pid;
329 	int sta;
330 
331 	/*
332 	 * Map a page read/write/exec and duplicate it.
333 	 * Map the copy executable.
334 	 * Copy a trivial assembly function to the writeable mapping.
335 	 * Try to execute it. This should never create a SIGSEGV.
336 	 */
337 
338 	map = mmap(NULL, page, PROT_MPROTECT(PROT_EXEC|PROT_WRITE|PROT_READ),
339 	    MAP_ANON, -1, 0);
340 	ATF_REQUIRE(map != MAP_FAILED);
341 	map2 = mremap(map, page, NULL, page, MAP_REMAPDUP);
342 	ATF_REQUIRE(map2 != MAP_FAILED);
343 	ATF_REQUIRE(mprotect(map, page, PROT_WRITE|PROT_READ) == 0);
344 	ATF_REQUIRE(mprotect(map2, page, PROT_EXEC|PROT_READ) == 0);
345 
346 	memcpy(map, (void *)return_one,
347 	    (uintptr_t)return_one_end - (uintptr_t)return_one);
348 	__builtin___clear_cache(map, (void *)((uintptr_t)map + page));
349 
350 	ATF_REQUIRE(((int (*)(void))map2)() == 1);
351 
352 	/* Double check that the executable mapping is not writeable. */
353 	pid = fork();
354 	ATF_REQUIRE(pid >= 0);
355 
356 	if (pid == 0) {
357 		ATF_REQUIRE(signal(SIGSEGV, sighandler) != SIG_ERR);
358 		ATF_REQUIRE(strlcpy(map2, "XXX", 3) == 0);
359 	}
360 
361 	(void)wait(&sta);
362 
363 	ATF_REQUIRE(WIFEXITED(sta) != 0);
364 	ATF_REQUIRE(WEXITSTATUS(sta) == SIGSEGV);
365 
366 	if (exec_prot_support() == PERPAGE_XP) {
367 		/* Double check that the writeable mapping is not executable. */
368 		pid = fork();
369 		ATF_REQUIRE(pid >= 0);
370 
371 		if (pid == 0) {
372 			ATF_REQUIRE(signal(SIGSEGV, sighandler) != SIG_ERR);
373 			ATF_REQUIRE(((int (*)(void))map)() == 1);
374 		}
375 
376 		(void)wait(&sta);
377 
378 		ATF_REQUIRE(WIFEXITED(sta) != 0);
379 		ATF_REQUIRE(WEXITSTATUS(sta) == SIGSEGV);
380 	}
381 
382 	ATF_REQUIRE(munmap(map, page) == 0);
383 	ATF_REQUIRE(munmap(map2, page) == 0);
384 }
385 
386 ATF_TP_ADD_TCS(tp)
387 {
388 	page = sysconf(_SC_PAGESIZE);
389 	ATF_REQUIRE(page >= 0);
390 
391 	ATF_TP_ADD_TC(tp, mprotect_access);
392 	ATF_TP_ADD_TC(tp, mprotect_err);
393 	ATF_TP_ADD_TC(tp, mprotect_exec);
394 	ATF_TP_ADD_TC(tp, mprotect_pax);
395 	ATF_TP_ADD_TC(tp, mprotect_write);
396 	ATF_TP_ADD_TC(tp, mprotect_mremap_exec);
397 
398 	return atf_no_error();
399 }
400