Bug 1845355 - Add some asserts to detect causes of a crash r=glandium
[gecko.git] / mfbt / tests / TestPoisonArea.cpp
blob9df3929834f9e0702ee23559d93db1b953581932
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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 */
8 /* Code in this file needs to be kept in sync with code in nsPresArena.cpp.
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.
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).
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).
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.
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:
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).
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).
61 * Be aware of these gotchas:
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.
69 * 2. For the same reason, we cannot blindly use mprotect() if mmap() fails.
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.
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.
82 // MAP_ANON(YMOUS) is not in any standard. Add defines as necessary.
83 #define _GNU_SOURCE 1
84 #define _DARWIN_C_SOURCE 1
86 #include <errno.h>
87 #include <inttypes.h>
88 #include <stdio.h>
89 #include <stdlib.h>
90 #include <string.h>
92 #ifdef _WIN32
93 # include <windows.h>
94 #else
95 # include <sys/types.h>
96 # include <unistd.h>
97 # include <sys/wait.h>
99 # include <sys/mman.h>
100 # ifndef MAP_ANON
101 # ifdef MAP_ANONYMOUS
102 # define MAP_ANON MAP_ANONYMOUS
103 # else
104 # error "Don't know how to get anonymous memory"
105 # endif
106 # endif
107 #endif
109 #define SIZxPTR ((int)(sizeof(uintptr_t) * 2))
111 /* This program assumes that a whole number of return instructions fit into
112 * 32 bits, and that 32-bit alignment is sufficient for a branch destination.
113 * For architectures where this is not true, fiddling with RETURN_INSTR_TYPE
114 * can be enough.
117 #if defined __i386__ || defined __x86_64__ || defined __i386 || \
118 defined __x86_64 || defined _M_IX86 || defined _M_AMD64
119 # define RETURN_INSTR 0xC3C3C3C3 /* ret; ret; ret; ret */
121 #elif defined __arm__ || defined _M_ARM
122 # define RETURN_INSTR 0xE12FFF1E /* bx lr */
124 // PPC has its own style of CPU-id #defines. There is no Windows for
125 // PPC as far as I know, so no _M_ variant.
126 #elif defined _ARCH_PPC || defined _ARCH_PWR || defined _ARCH_PWR2
127 # define RETURN_INSTR 0x4E800020 /* blr */
129 #elif defined __m68k__
130 # define RETURN_INSTR 0x4E754E75 /* rts; rts */
132 #elif defined __riscv
133 # define RETURN_INSTR 0x80828082 /* ret; ret */
135 #elif defined __sparc || defined __sparcv9
136 # define RETURN_INSTR 0x81c3e008 /* retl */
138 #elif defined __alpha
139 # define RETURN_INSTR 0x6bfa8001 /* ret */
141 #elif defined __hppa
142 # define RETURN_INSTR 0xe840c002 /* bv,n r0(rp) */
144 #elif defined __mips
145 # define RETURN_INSTR 0x03e00008 /* jr ra */
147 # ifdef __MIPSEL
148 /* On mipsel, jr ra needs to be followed by a nop.
149 0x03e00008 as a 64 bits integer just does that */
150 # define RETURN_INSTR_TYPE uint64_t
151 # endif
153 #elif defined __s390__
154 # define RETURN_INSTR 0x07fe0000 /* br %r14 */
156 #elif defined __sh__
157 # define RETURN_INSTR 0x0b000b00 /* rts; rts */
159 #elif defined __aarch64__ || defined _M_ARM64
160 # define RETURN_INSTR 0xd65f03c0 /* ret */
162 #elif defined __loongarch64
163 # define RETURN_INSTR 0x4c000020 /* jirl zero, ra, 0 */
165 #elif defined __ia64
166 struct ia64_instr {
167 uint32_t mI[4];
169 static const ia64_instr _return_instr = {
170 {0x00000011, 0x00000001, 0x80000200, 0x00840008}}; /* br.ret.sptk.many b0 */
172 # define RETURN_INSTR _return_instr
173 # define RETURN_INSTR_TYPE ia64_instr
175 #else
176 # error "Need return instruction for this architecture"
177 #endif
179 #ifndef RETURN_INSTR_TYPE
180 # define RETURN_INSTR_TYPE uint32_t
181 #endif
183 // Miscellaneous Windows/Unix portability gumph
185 #ifdef _WIN32
186 // Uses of this function deliberately leak the string.
187 static LPSTR StrW32Error(DWORD aErrcode) {
188 LPSTR errmsg;
189 FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
190 FORMAT_MESSAGE_IGNORE_INSERTS,
191 nullptr, aErrcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
192 (LPSTR)&errmsg, 0, nullptr);
194 // FormatMessage puts an unwanted newline at the end of the string
195 size_t n = strlen(errmsg) - 1;
196 while (errmsg[n] == '\r' || errmsg[n] == '\n') {
197 n--;
199 errmsg[n + 1] = '\0';
200 return errmsg;
202 # define LastErrMsg() (StrW32Error(GetLastError()))
204 // Because we use VirtualAlloc in MEM_RESERVE mode, the "page size" we want
205 // is the allocation granularity.
206 static SYSTEM_INFO sInfo_;
208 static inline uint32_t PageSize() { return sInfo_.dwAllocationGranularity; }
210 static void* ReserveRegion(uintptr_t aRequest, bool aAccessible) {
211 return VirtualAlloc((void*)aRequest, PageSize(),
212 aAccessible ? MEM_RESERVE | MEM_COMMIT : MEM_RESERVE,
213 aAccessible ? PAGE_EXECUTE_READWRITE : PAGE_NOACCESS);
216 static void ReleaseRegion(void* aPage) {
217 VirtualFree(aPage, PageSize(), MEM_RELEASE);
220 static bool ProbeRegion(uintptr_t aPage) {
221 return aPage >= (uintptr_t)sInfo_.lpMaximumApplicationAddress &&
222 aPage + PageSize() >= (uintptr_t)sInfo_.lpMaximumApplicationAddress;
225 static bool MakeRegionExecutable(void*) { return false; }
227 # undef MAP_FAILED
228 # define MAP_FAILED 0
230 #else // Unix
232 # define LastErrMsg() (strerror(errno))
234 static unsigned long gUnixPageSize;
236 static inline unsigned long PageSize() { return gUnixPageSize; }
238 static void* ReserveRegion(uintptr_t aRequest, bool aAccessible) {
239 return mmap(reinterpret_cast<void*>(aRequest), PageSize(),
240 aAccessible ? PROT_READ | PROT_WRITE : PROT_NONE,
241 MAP_PRIVATE | MAP_ANON, -1, 0);
244 static void ReleaseRegion(void* aPage) { munmap(aPage, PageSize()); }
246 static bool ProbeRegion(uintptr_t aPage) {
247 # ifdef XP_SOLARIS
248 return !!posix_madvise(reinterpret_cast<void*>(aPage), PageSize(),
249 POSIX_MADV_NORMAL);
250 # else
251 return !!madvise(reinterpret_cast<void*>(aPage), PageSize(), MADV_NORMAL);
252 # endif
255 static int MakeRegionExecutable(void* aPage) {
256 return mprotect((caddr_t)aPage, PageSize(),
257 PROT_READ | PROT_WRITE | PROT_EXEC);
260 #endif
262 static uintptr_t ReservePoisonArea() {
263 if (sizeof(uintptr_t) == 8) {
264 // Use the hardware-inaccessible region.
265 // We have to avoid 64-bit constants and shifts by 32 bits, since this
266 // code is compiled in 32-bit mode, although it is never executed there.
267 uintptr_t result =
268 (((uintptr_t(0x7FFFFFFFu) << 31) << 1 | uintptr_t(0xF0DEAFFFu)) &
269 ~uintptr_t(PageSize() - 1));
270 printf("INFO | poison area assumed at 0x%.*" PRIxPTR "\n", SIZxPTR, result);
271 return result;
274 // First see if we can allocate the preferred poison address from the OS.
275 uintptr_t candidate = (0xF0DEAFFF & ~(PageSize() - 1));
276 void* result = ReserveRegion(candidate, false);
277 if (result == reinterpret_cast<void*>(candidate)) {
278 // success - inaccessible page allocated
279 printf("INFO | poison area allocated at 0x%.*" PRIxPTR
280 " (preferred addr)\n",
281 SIZxPTR, reinterpret_cast<uintptr_t>(result));
282 return candidate;
285 // That didn't work, so see if the preferred address is within a range
286 // of permanently inacessible memory.
287 if (ProbeRegion(candidate)) {
288 // success - selected page cannot be usable memory
289 if (result != MAP_FAILED) {
290 ReleaseRegion(result);
292 printf("INFO | poison area assumed at 0x%.*" PRIxPTR " (preferred addr)\n",
293 SIZxPTR, candidate);
294 return candidate;
297 // The preferred address is already in use. Did the OS give us a
298 // consolation prize?
299 if (result != MAP_FAILED) {
300 uintptr_t ures = reinterpret_cast<uintptr_t>(result);
301 printf("INFO | poison area allocated at 0x%.*" PRIxPTR
302 " (consolation prize)\n",
303 SIZxPTR, ures);
304 return ures;
307 // It didn't, so try to allocate again, without any constraint on
308 // the address.
309 result = ReserveRegion(0, false);
310 if (result != MAP_FAILED) {
311 uintptr_t ures = reinterpret_cast<uintptr_t>(result);
312 printf("INFO | poison area allocated at 0x%.*" PRIxPTR " (fallback)\n",
313 SIZxPTR, ures);
314 return ures;
317 printf("ERROR | no usable poison area found\n");
318 return 0;
321 /* The "positive control" area confirms that we can allocate a page with the
322 * proper characteristics.
324 static uintptr_t ReservePositiveControl() {
325 void* result = ReserveRegion(0, false);
326 if (result == MAP_FAILED) {
327 printf("ERROR | allocating positive control | %s\n", LastErrMsg());
328 return 0;
330 printf("INFO | positive control allocated at 0x%.*" PRIxPTR "\n", SIZxPTR,
331 (uintptr_t)result);
332 return (uintptr_t)result;
335 /* The "negative control" area confirms that our probe logic does detect a
336 * page that is readable, writable, or executable.
338 static uintptr_t ReserveNegativeControl() {
339 void* result = ReserveRegion(0, true);
340 if (result == MAP_FAILED) {
341 printf("ERROR | allocating negative control | %s\n", LastErrMsg());
342 return 0;
345 // Fill the page with return instructions.
346 RETURN_INSTR_TYPE* p = reinterpret_cast<RETURN_INSTR_TYPE*>(result);
347 RETURN_INSTR_TYPE* limit = reinterpret_cast<RETURN_INSTR_TYPE*>(
348 reinterpret_cast<char*>(result) + PageSize());
349 while (p < limit) {
350 *p++ = RETURN_INSTR;
353 // Now mark it executable as well as readable and writable.
354 // (mmap(PROT_EXEC) may fail when applied to anonymous memory.)
356 if (MakeRegionExecutable(result)) {
357 printf("ERROR | making negative control executable | %s\n", LastErrMsg());
358 return 0;
361 printf("INFO | negative control allocated at 0x%.*" PRIxPTR "\n", SIZxPTR,
362 (uintptr_t)result);
363 return (uintptr_t)result;
366 #ifndef _WIN32
367 static void JumpTo(uintptr_t aOpaddr) {
368 # ifdef __ia64
369 struct func_call {
370 uintptr_t mFunc;
371 uintptr_t mGp;
372 } call = {
373 aOpaddr,
375 ((void (*)()) & call)();
376 # else
377 ((void (*)())aOpaddr)();
378 # endif
380 #endif
382 /* Test each page. */
383 static bool TestPage(const char* aPageLabel, uintptr_t aPageAddr,
384 int aShouldSucceed) {
385 const char* oplabel;
386 uintptr_t opaddr;
388 bool failed = false;
389 for (unsigned int test = 0; test < 3; test++) {
390 switch (test) {
391 // The execute test must be done before the write test, because the
392 // write test will clobber memory at the target address.
393 case 0:
394 oplabel = "reading";
395 opaddr = aPageAddr + PageSize() / 2 - 1;
396 break;
397 case 1:
398 oplabel = "executing";
399 opaddr = aPageAddr + PageSize() / 2;
400 break;
401 case 2:
402 oplabel = "writing";
403 opaddr = aPageAddr + PageSize() / 2 - 1;
404 break;
405 default:
406 abort();
409 #ifdef _WIN32
410 bool badptr = true;
411 MEMORY_BASIC_INFORMATION mbi = {};
413 if (VirtualQuery((LPCVOID)opaddr, &mbi, sizeof(mbi)) &&
414 mbi.State == MEM_COMMIT) {
415 switch (test) {
416 case 0: // read
417 badptr = !(mbi.Protect & (PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE |
418 PAGE_READONLY | PAGE_READWRITE));
419 break;
420 case 1: // execute
421 badptr =
422 !(mbi.Protect & (PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE));
423 break;
424 case 2: // write
425 badptr = !(mbi.Protect & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE));
426 break;
427 default:
428 abort();
432 if (badptr) {
433 if (aShouldSucceed) {
434 printf("TEST-UNEXPECTED-FAIL | %s %s\n", oplabel, aPageLabel);
435 failed = true;
436 } else {
437 printf("TEST-PASS | %s %s\n", oplabel, aPageLabel);
439 } else {
440 // if control reaches this point the probe succeeded
441 if (aShouldSucceed) {
442 printf("TEST-PASS | %s %s\n", oplabel, aPageLabel);
443 } else {
444 printf("TEST-UNEXPECTED-FAIL | %s %s\n", oplabel, aPageLabel);
445 failed = true;
448 #else
449 pid_t pid = fork();
450 if (pid == -1) {
451 printf("ERROR | %s %s | fork=%s\n", oplabel, aPageLabel, LastErrMsg());
452 exit(2);
453 } else if (pid == 0) {
454 volatile unsigned char scratch;
455 switch (test) {
456 case 0:
457 scratch = *(volatile unsigned char*)opaddr;
458 break;
459 case 1:
460 JumpTo(opaddr);
461 break;
462 case 2:
463 *(volatile unsigned char*)opaddr = 0;
464 break;
465 default:
466 abort();
468 (void)scratch;
469 _exit(0);
470 } else {
471 int status;
472 if (waitpid(pid, &status, 0) != pid) {
473 printf("ERROR | %s %s | wait=%s\n", oplabel, aPageLabel, LastErrMsg());
474 exit(2);
477 if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
478 if (aShouldSucceed) {
479 printf("TEST-PASS | %s %s\n", oplabel, aPageLabel);
480 } else {
481 printf("TEST-UNEXPECTED-FAIL | %s %s | unexpected successful exit\n",
482 oplabel, aPageLabel);
483 failed = true;
485 } else if (WIFEXITED(status)) {
486 printf("ERROR | %s %s | unexpected exit code %d\n", oplabel, aPageLabel,
487 WEXITSTATUS(status));
488 exit(2);
489 } else if (WIFSIGNALED(status)) {
490 if (aShouldSucceed) {
491 printf("TEST-UNEXPECTED-FAIL | %s %s | unexpected signal %d\n",
492 oplabel, aPageLabel, WTERMSIG(status));
493 failed = true;
494 } else {
495 printf("TEST-PASS | %s %s | signal %d (as expected)\n", oplabel,
496 aPageLabel, WTERMSIG(status));
498 } else {
499 printf("ERROR | %s %s | unexpected exit status %d\n", oplabel,
500 aPageLabel, status);
501 exit(2);
504 #endif
506 return failed;
509 int main() {
510 #ifdef _WIN32
511 GetSystemInfo(&sInfo_);
512 #else
513 gUnixPageSize = sysconf(_SC_PAGESIZE);
514 #endif
516 uintptr_t ncontrol = ReserveNegativeControl();
517 uintptr_t pcontrol = ReservePositiveControl();
518 uintptr_t poison = ReservePoisonArea();
520 if (!ncontrol || !pcontrol || !poison) {
521 return 2;
524 bool failed = false;
525 failed |= TestPage("negative control", ncontrol, 1);
526 failed |= TestPage("positive control", pcontrol, 0);
527 failed |= TestPage("poison area", poison, 0);
529 return failed ? 1 : 0;