mfbt/tests/TestPoisonArea.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:946dc6fa152f
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 */
7
8 /* Code in this file needs to be kept in sync with code in nsPresArena.cpp.
9 *
10 * We want to use a fixed address for frame poisoning so that it is readily
11 * identifiable in crash dumps. Whether such an address is available
12 * without any special setup depends on the system configuration.
13 *
14 * All current 64-bit CPUs (with the possible exception of PowerPC64)
15 * reserve the vast majority of the virtual address space for future
16 * hardware extensions; valid addresses must be below some break point
17 * between 2**48 and 2**54, depending on exactly which chip you have. Some
18 * chips (notably amd64) also allow the use of the *highest* 2**48 -- 2**54
19 * addresses. Thus, if user space pointers are 64 bits wide, we can just
20 * use an address outside this range, and no more is required. To
21 * accommodate the chips that allow very high addresses to be valid, the
22 * value chosen is close to 2**63 (that is, in the middle of the space).
23 *
24 * In most cases, a purely 32-bit operating system must reserve some
25 * fraction of the address space for its own use. Contemporary 32-bit OSes
26 * tend to take the high gigabyte or so (0xC000_0000 on up). If we can
27 * prove that high addresses are reserved to the kernel, we can use an
28 * address in that region. Unfortunately, not all 32-bit OSes do this;
29 * OSX 10.4 might not, and it is unclear what mobile OSes are like
30 * (some 32-bit CPUs make it very easy for the kernel to exist in its own
31 * private address space).
32 *
33 * Furthermore, when a 32-bit user space process is running on a 64-bit
34 * kernel, the operating system has no need to reserve any of the space that
35 * the process can see, and generally does not do so. This is the scenario
36 * of greatest concern, since it covers all contemporary OSX iterations
37 * (10.5+) as well as Windows Vista and 7 on newer amd64 hardware. Linux on
38 * amd64 is generally run as a pure 64-bit environment, but its 32-bit
39 * compatibility mode also has this property.
40 *
41 * Thus, when user space pointers are 32 bits wide, we need to validate
42 * our chosen address, and possibly *make* it a good poison address by
43 * allocating a page around it and marking it inaccessible. The algorithm
44 * for this is:
45 *
46 * 1. Attempt to make the page surrounding the poison address a reserved,
47 * inaccessible memory region using OS primitives. On Windows, this is
48 * done with VirtualAlloc(MEM_RESERVE); on Unix, mmap(PROT_NONE).
49 *
50 * 2. If mmap/VirtualAlloc failed, there are two possible reasons: either
51 * the region is reserved to the kernel and no further action is
52 * required, or there is already usable memory in this area and we have
53 * to pick a different address. The tricky part is knowing which case
54 * we have, without attempting to access the region. On Windows, we
55 * rely on GetSystemInfo()'s reported upper and lower bounds of the
56 * application memory area. On Unix, there is nothing devoted to the
57 * purpose, but seeing if madvise() fails is close enough (it *might*
58 * disrupt someone else's use of the memory region, but not by as much
59 * as anything else available).
60 *
61 * Be aware of these gotchas:
62 *
63 * 1. We cannot use mmap() with MAP_FIXED. MAP_FIXED is defined to
64 * _replace_ any existing mapping in the region, if necessary to satisfy
65 * the request. Obviously, as we are blindly attempting to acquire a
66 * page at a constant address, we must not do this, lest we overwrite
67 * someone else's allocation.
68 *
69 * 2. For the same reason, we cannot blindly use mprotect() if mmap() fails.
70 *
71 * 3. madvise() may fail when applied to a 'magic' memory region provided as
72 * a kernel/user interface. Fortunately, the only such case I know about
73 * is the "vsyscall" area (not to be confused with the "vdso" area) for
74 * *64*-bit processes on Linux - and we don't even run this code for
75 * 64-bit processes.
76 *
77 * 4. VirtualQuery() does not produce any useful information if
78 * applied to kernel memory - in fact, it doesn't write its output
79 * at all. Thus, it is not used here.
80 */
81
82 #include "mozilla/IntegerPrintfMacros.h"
83 #include "mozilla/NullPtr.h"
84
85 // MAP_ANON(YMOUS) is not in any standard. Add defines as necessary.
86 #define _GNU_SOURCE 1
87 #define _DARWIN_C_SOURCE 1
88
89 #include <stddef.h>
90
91 #include <errno.h>
92 #include <stdio.h>
93 #include <stdlib.h>
94 #include <string.h>
95
96 #ifdef _WIN32
97 #include <windows.h>
98 #else
99 #include <sys/types.h>
100 #include <fcntl.h>
101 #include <signal.h>
102 #include <unistd.h>
103 #include <sys/stat.h>
104 #include <sys/wait.h>
105
106 #include <sys/mman.h>
107 #ifndef MAP_ANON
108 #ifdef MAP_ANONYMOUS
109 #define MAP_ANON MAP_ANONYMOUS
110 #else
111 #error "Don't know how to get anonymous memory"
112 #endif
113 #endif
114 #endif
115
116 #define SIZxPTR ((int)(sizeof(uintptr_t)*2))
117
118 /* This program assumes that a whole number of return instructions fit into
119 * 32 bits, and that 32-bit alignment is sufficient for a branch destination.
120 * For architectures where this is not true, fiddling with RETURN_INSTR_TYPE
121 * can be enough.
122 */
123
124 #if defined __i386__ || defined __x86_64__ || \
125 defined __i386 || defined __x86_64 || \
126 defined _M_IX86 || defined _M_AMD64
127 #define RETURN_INSTR 0xC3C3C3C3 /* ret; ret; ret; ret */
128
129 #elif defined __arm__ || defined _M_ARM
130 #define RETURN_INSTR 0xE12FFF1E /* bx lr */
131
132 // PPC has its own style of CPU-id #defines. There is no Windows for
133 // PPC as far as I know, so no _M_ variant.
134 #elif defined _ARCH_PPC || defined _ARCH_PWR || defined _ARCH_PWR2
135 #define RETURN_INSTR 0x4E800020 /* blr */
136
137 #elif defined __sparc || defined __sparcv9
138 #define RETURN_INSTR 0x81c3e008 /* retl */
139
140 #elif defined __alpha
141 #define RETURN_INSTR 0x6bfa8001 /* ret */
142
143 #elif defined __hppa
144 #define RETURN_INSTR 0xe840c002 /* bv,n r0(rp) */
145
146 #elif defined __mips
147 #define RETURN_INSTR 0x03e00008 /* jr ra */
148
149 #ifdef __MIPSEL
150 /* On mipsel, jr ra needs to be followed by a nop.
151 0x03e00008 as a 64 bits integer just does that */
152 #define RETURN_INSTR_TYPE uint64_t
153 #endif
154
155 #elif defined __s390__
156 #define RETURN_INSTR 0x07fe0000 /* br %r14 */
157
158 #elif defined __aarch64__
159 #define RETURN_INSTR 0xd65f03c0 /* ret */
160
161 #elif defined __ia64
162 struct ia64_instr { uint32_t i[4]; };
163 static const ia64_instr _return_instr =
164 {{ 0x00000011, 0x00000001, 0x80000200, 0x00840008 }}; /* br.ret.sptk.many b0 */
165
166 #define RETURN_INSTR _return_instr
167 #define RETURN_INSTR_TYPE ia64_instr
168
169 #else
170 #error "Need return instruction for this architecture"
171 #endif
172
173 #ifndef RETURN_INSTR_TYPE
174 #define RETURN_INSTR_TYPE uint32_t
175 #endif
176
177 // Miscellaneous Windows/Unix portability gumph
178
179 #ifdef _WIN32
180 // Uses of this function deliberately leak the string.
181 static LPSTR
182 StrW32Error(DWORD errcode)
183 {
184 LPSTR errmsg;
185 FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
186 FORMAT_MESSAGE_FROM_SYSTEM |
187 FORMAT_MESSAGE_IGNORE_INSERTS,
188 nullptr, errcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
189 (LPSTR)&errmsg, 0, nullptr);
190
191 // FormatMessage puts an unwanted newline at the end of the string
192 size_t n = strlen(errmsg)-1;
193 while (errmsg[n] == '\r' || errmsg[n] == '\n') n--;
194 errmsg[n+1] = '\0';
195 return errmsg;
196 }
197 #define LastErrMsg() (StrW32Error(GetLastError()))
198
199 // Because we use VirtualAlloc in MEM_RESERVE mode, the "page size" we want
200 // is the allocation granularity.
201 static SYSTEM_INFO _sinfo;
202 #undef PAGESIZE
203 #define PAGESIZE (_sinfo.dwAllocationGranularity)
204
205
206 static void *
207 ReserveRegion(uintptr_t request, bool accessible)
208 {
209 return VirtualAlloc((void *)request, PAGESIZE,
210 accessible ? MEM_RESERVE|MEM_COMMIT : MEM_RESERVE,
211 accessible ? PAGE_EXECUTE_READWRITE : PAGE_NOACCESS);
212 }
213
214 static void
215 ReleaseRegion(void *page)
216 {
217 VirtualFree(page, PAGESIZE, MEM_RELEASE);
218 }
219
220 static bool
221 ProbeRegion(uintptr_t page)
222 {
223 if (page >= (uintptr_t)_sinfo.lpMaximumApplicationAddress &&
224 page + PAGESIZE >= (uintptr_t)_sinfo.lpMaximumApplicationAddress) {
225 return true;
226 } else {
227 return false;
228 }
229 }
230
231 static bool
232 MakeRegionExecutable(void *)
233 {
234 return false;
235 }
236
237 #undef MAP_FAILED
238 #define MAP_FAILED 0
239
240 #else // Unix
241
242 #define LastErrMsg() (strerror(errno))
243
244 static unsigned long _pagesize;
245 #define PAGESIZE _pagesize
246
247 static void *
248 ReserveRegion(uintptr_t request, bool accessible)
249 {
250 return mmap(reinterpret_cast<void*>(request), PAGESIZE,
251 accessible ? PROT_READ|PROT_WRITE : PROT_NONE,
252 MAP_PRIVATE|MAP_ANON, -1, 0);
253 }
254
255 static void
256 ReleaseRegion(void *page)
257 {
258 munmap(page, PAGESIZE);
259 }
260
261 static bool
262 ProbeRegion(uintptr_t page)
263 {
264 if (madvise(reinterpret_cast<void*>(page), PAGESIZE, MADV_NORMAL)) {
265 return true;
266 } else {
267 return false;
268 }
269 }
270
271 static int
272 MakeRegionExecutable(void *page)
273 {
274 return mprotect((caddr_t)page, PAGESIZE, PROT_READ|PROT_WRITE|PROT_EXEC);
275 }
276
277 #endif
278
279 static uintptr_t
280 ReservePoisonArea()
281 {
282 if (sizeof(uintptr_t) == 8) {
283 // Use the hardware-inaccessible region.
284 // We have to avoid 64-bit constants and shifts by 32 bits, since this
285 // code is compiled in 32-bit mode, although it is never executed there.
286 uintptr_t result = (((uintptr_t(0x7FFFFFFFu) << 31) << 1 |
287 uintptr_t(0xF0DEAFFFu)) &
288 ~uintptr_t(PAGESIZE-1));
289 printf("INFO | poison area assumed at 0x%.*" PRIxPTR "\n", SIZxPTR, result);
290 return result;
291 } else {
292 // First see if we can allocate the preferred poison address from the OS.
293 uintptr_t candidate = (0xF0DEAFFF & ~(PAGESIZE-1));
294 void *result = ReserveRegion(candidate, false);
295 if (result == (void *)candidate) {
296 // success - inaccessible page allocated
297 printf("INFO | poison area allocated at 0x%.*" PRIxPTR
298 " (preferred addr)\n", SIZxPTR, (uintptr_t)result);
299 return candidate;
300 }
301
302 // That didn't work, so see if the preferred address is within a range
303 // of permanently inacessible memory.
304 if (ProbeRegion(candidate)) {
305 // success - selected page cannot be usable memory
306 if (result != MAP_FAILED)
307 ReleaseRegion(result);
308 printf("INFO | poison area assumed at 0x%.*" PRIxPTR
309 " (preferred addr)\n", SIZxPTR, candidate);
310 return candidate;
311 }
312
313 // The preferred address is already in use. Did the OS give us a
314 // consolation prize?
315 if (result != MAP_FAILED) {
316 printf("INFO | poison area allocated at 0x%.*" PRIxPTR
317 " (consolation prize)\n", SIZxPTR, (uintptr_t)result);
318 return (uintptr_t)result;
319 }
320
321 // It didn't, so try to allocate again, without any constraint on
322 // the address.
323 result = ReserveRegion(0, false);
324 if (result != MAP_FAILED) {
325 printf("INFO | poison area allocated at 0x%.*" PRIxPTR
326 " (fallback)\n", SIZxPTR, (uintptr_t)result);
327 return (uintptr_t)result;
328 }
329
330 printf("ERROR | no usable poison area found\n");
331 return 0;
332 }
333 }
334
335 /* The "positive control" area confirms that we can allocate a page with the
336 * proper characteristics.
337 */
338 static uintptr_t
339 ReservePositiveControl()
340 {
341
342 void *result = ReserveRegion(0, false);
343 if (result == MAP_FAILED) {
344 printf("ERROR | allocating positive control | %s\n", LastErrMsg());
345 return 0;
346 }
347 printf("INFO | positive control allocated at 0x%.*" PRIxPTR "\n",
348 SIZxPTR, (uintptr_t)result);
349 return (uintptr_t)result;
350 }
351
352 /* The "negative control" area confirms that our probe logic does detect a
353 * page that is readable, writable, or executable.
354 */
355 static uintptr_t
356 ReserveNegativeControl()
357 {
358 void *result = ReserveRegion(0, true);
359 if (result == MAP_FAILED) {
360 printf("ERROR | allocating negative control | %s\n", LastErrMsg());
361 return 0;
362 }
363
364 // Fill the page with return instructions.
365 RETURN_INSTR_TYPE *p = (RETURN_INSTR_TYPE *)result;
366 RETURN_INSTR_TYPE *limit = (RETURN_INSTR_TYPE *)(((char *)result) + PAGESIZE);
367 while (p < limit)
368 *p++ = RETURN_INSTR;
369
370 // Now mark it executable as well as readable and writable.
371 // (mmap(PROT_EXEC) may fail when applied to anonymous memory.)
372
373 if (MakeRegionExecutable(result)) {
374 printf("ERROR | making negative control executable | %s\n", LastErrMsg());
375 return 0;
376 }
377
378 printf("INFO | negative control allocated at 0x%.*" PRIxPTR "\n",
379 SIZxPTR, (uintptr_t)result);
380 return (uintptr_t)result;
381 }
382
383 static void
384 JumpTo(uintptr_t opaddr)
385 {
386 #ifdef __ia64
387 struct func_call {
388 uintptr_t func;
389 uintptr_t gp;
390 } call = { opaddr, };
391 ((void (*)())&call)();
392 #else
393 ((void (*)())opaddr)();
394 #endif
395 }
396
397 #ifdef _WIN32
398 static BOOL
399 IsBadExecPtr(uintptr_t ptr)
400 {
401 BOOL ret = false;
402
403 #ifdef _MSC_VER
404 __try {
405 JumpTo(ptr);
406 } __except (EXCEPTION_EXECUTE_HANDLER) {
407 ret = true;
408 }
409 #else
410 printf("INFO | exec test not supported on MinGW build\n");
411 // We do our best
412 ret = IsBadReadPtr((const void*)ptr, 1);
413 #endif
414 return ret;
415 }
416 #endif
417
418 /* Test each page. */
419 static bool
420 TestPage(const char *pagelabel, uintptr_t pageaddr, int should_succeed)
421 {
422 const char *oplabel;
423 uintptr_t opaddr;
424
425 bool failed = false;
426 for (unsigned int test = 0; test < 3; test++) {
427 switch (test) {
428 // The execute test must be done before the write test, because the
429 // write test will clobber memory at the target address.
430 case 0: oplabel = "reading"; opaddr = pageaddr + PAGESIZE/2 - 1; break;
431 case 1: oplabel = "executing"; opaddr = pageaddr + PAGESIZE/2; break;
432 case 2: oplabel = "writing"; opaddr = pageaddr + PAGESIZE/2 - 1; break;
433 default: abort();
434 }
435
436 #ifdef _WIN32
437 BOOL badptr;
438
439 switch (test) {
440 case 0: badptr = IsBadReadPtr((const void*)opaddr, 1); break;
441 case 1: badptr = IsBadExecPtr(opaddr); break;
442 case 2: badptr = IsBadWritePtr((void*)opaddr, 1); break;
443 default: abort();
444 }
445
446 if (badptr) {
447 if (should_succeed) {
448 printf("TEST-UNEXPECTED-FAIL | %s %s\n", oplabel, pagelabel);
449 failed = true;
450 } else {
451 printf("TEST-PASS | %s %s\n", oplabel, pagelabel);
452 }
453 } else {
454 // if control reaches this point the probe succeeded
455 if (should_succeed) {
456 printf("TEST-PASS | %s %s\n", oplabel, pagelabel);
457 } else {
458 printf("TEST-UNEXPECTED-FAIL | %s %s\n", oplabel, pagelabel);
459 failed = true;
460 }
461 }
462 #else
463 pid_t pid = fork();
464 if (pid == -1) {
465 printf("ERROR | %s %s | fork=%s\n", oplabel, pagelabel,
466 LastErrMsg());
467 exit(2);
468 } else if (pid == 0) {
469 volatile unsigned char scratch;
470 switch (test) {
471 case 0: scratch = *(volatile unsigned char *)opaddr; break;
472 case 1: JumpTo(opaddr); break;
473 case 2: *(volatile unsigned char *)opaddr = 0; break;
474 default: abort();
475 }
476 (void)scratch;
477 _exit(0);
478 } else {
479 int status;
480 if (waitpid(pid, &status, 0) != pid) {
481 printf("ERROR | %s %s | wait=%s\n", oplabel, pagelabel,
482 LastErrMsg());
483 exit(2);
484 }
485
486 if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
487 if (should_succeed) {
488 printf("TEST-PASS | %s %s\n", oplabel, pagelabel);
489 } else {
490 printf("TEST-UNEXPECTED-FAIL | %s %s | unexpected successful exit\n",
491 oplabel, pagelabel);
492 failed = true;
493 }
494 } else if (WIFEXITED(status)) {
495 printf("ERROR | %s %s | unexpected exit code %d\n",
496 oplabel, pagelabel, WEXITSTATUS(status));
497 exit(2);
498 } else if (WIFSIGNALED(status)) {
499 if (should_succeed) {
500 printf("TEST-UNEXPECTED-FAIL | %s %s | unexpected signal %d\n",
501 oplabel, pagelabel, WTERMSIG(status));
502 failed = true;
503 } else {
504 printf("TEST-PASS | %s %s | signal %d (as expected)\n",
505 oplabel, pagelabel, WTERMSIG(status));
506 }
507 } else {
508 printf("ERROR | %s %s | unexpected exit status %d\n",
509 oplabel, pagelabel, status);
510 exit(2);
511 }
512 }
513 #endif
514 }
515 return failed;
516 }
517
518 int
519 main()
520 {
521 #ifdef _WIN32
522 GetSystemInfo(&_sinfo);
523 #else
524 _pagesize = sysconf(_SC_PAGESIZE);
525 #endif
526
527 uintptr_t ncontrol = ReserveNegativeControl();
528 uintptr_t pcontrol = ReservePositiveControl();
529 uintptr_t poison = ReservePoisonArea();
530
531 if (!ncontrol || !pcontrol || !poison)
532 return 2;
533
534 bool failed = false;
535 failed |= TestPage("negative control", ncontrol, 1);
536 failed |= TestPage("positive control", pcontrol, 0);
537 failed |= TestPage("poison area", poison, 0);
538
539 return failed ? 1 : 0;
540 }

mercurial