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 */
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
84 // MAP_ANON(YMOUS) is not in any standard. Add defines as necessary.
85 #define _GNU_SOURCE 1
86 #define _DARWIN_C_SOURCE 1
87
88 #include <stddef.h>
89
90 #include <errno.h>
91 #include <stdio.h>
92 #include <stdlib.h>
93 #include <string.h>
94
95 #ifdef _WIN32
96 #include <windows.h>
97 #else
98 #include <sys/types.h>
99 #include <fcntl.h>
100 #include <signal.h>
101 #include <unistd.h>
102 #include <sys/stat.h>
103 #include <sys/wait.h>
104
105 #include <sys/mman.h>
106 #ifndef MAP_ANON
107 #ifdef MAP_ANONYMOUS
108 #define MAP_ANON MAP_ANONYMOUS
109 #else
110 #error "Don't know how to get anonymous memory"
111 #endif
112 #endif
113 #endif
114
115 #define SIZxPTR ((int)(sizeof(uintptr_t)*2))
116
117 /* This program assumes that a whole number of return instructions fit into
118 * 32 bits, and that 32-bit alignment is sufficient for a branch destination.
119 * For architectures where this is not true, fiddling with RETURN_INSTR_TYPE
120 * can be enough.
121 */
122
123 #if defined __i386__ || defined __x86_64__ || \
124 defined __i386 || defined __x86_64 || \
125 defined _M_IX86 || defined _M_AMD64
126 #define RETURN_INSTR 0xC3C3C3C3 /* ret; ret; ret; ret */
127
128 #elif defined __arm__ || defined _M_ARM
129 #define RETURN_INSTR 0xE12FFF1E /* bx lr */
130
131 // PPC has its own style of CPU-id #defines. There is no Windows for
132 // PPC as far as I know, so no _M_ variant.
133 #elif defined _ARCH_PPC || defined _ARCH_PWR || defined _ARCH_PWR2
134 #define RETURN_INSTR 0x4E800020 /* blr */
135
136 #elif defined __sparc || defined __sparcv9
137 #define RETURN_INSTR 0x81c3e008 /* retl */
138
139 #elif defined __alpha
140 #define RETURN_INSTR 0x6bfa8001 /* ret */
141
142 #elif defined __hppa
143 #define RETURN_INSTR 0xe840c002 /* bv,n r0(rp) */
144
145 #elif defined __mips
146 #define RETURN_INSTR 0x03e00008 /* jr ra */
147
148 #ifdef __MIPSEL
149 /* On mipsel, jr ra needs to be followed by a nop.
150 0x03e00008 as a 64 bits integer just does that */
151 #define RETURN_INSTR_TYPE uint64_t
152 #endif
153
154 #elif defined __s390__
155 #define RETURN_INSTR 0x07fe0000 /* br %r14 */
156
157 #elif defined __aarch64__
158 #define RETURN_INSTR 0xd65f03c0 /* ret */
159
160 #elif defined __ia64
161 struct ia64_instr { uint32_t mI[4]; };
162 static const ia64_instr _return_instr =
163 {{ 0x00000011, 0x00000001, 0x80000200, 0x00840008 }}; /* br.ret.sptk.many b0 */
164
165 #define RETURN_INSTR _return_instr
166 #define RETURN_INSTR_TYPE ia64_instr
167
168 #else
169 #error "Need return instruction for this architecture"
170 #endif
171
172 #ifndef RETURN_INSTR_TYPE
173 #define RETURN_INSTR_TYPE uint32_t
174 #endif
175
176 // Miscellaneous Windows/Unix portability gumph
177
178 #ifdef _WIN32
179 // Uses of this function deliberately leak the string.
180 static LPSTR
StrW32Error(DWORD aErrcode)181 StrW32Error(DWORD aErrcode)
182 {
183 LPSTR errmsg;
184 FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
185 FORMAT_MESSAGE_FROM_SYSTEM |
186 FORMAT_MESSAGE_IGNORE_INSERTS,
187 nullptr, aErrcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
188 (LPSTR)&errmsg, 0, nullptr);
189
190 // FormatMessage puts an unwanted newline at the end of the string
191 size_t n = strlen(errmsg)-1;
192 while (errmsg[n] == '\r' || errmsg[n] == '\n') {
193 n--;
194 }
195 errmsg[n+1] = '\0';
196 return errmsg;
197 }
198 #define LastErrMsg() (StrW32Error(GetLastError()))
199
200 // Because we use VirtualAlloc in MEM_RESERVE mode, the "page size" we want
201 // is the allocation granularity.
202 static SYSTEM_INFO sInfo_;
203
204 static inline uint32_t
PageSize()205 PageSize()
206 {
207 return sInfo_.dwAllocationGranularity;
208 }
209
210 static void*
ReserveRegion(uintptr_t aRequest,bool aAccessible)211 ReserveRegion(uintptr_t aRequest, bool aAccessible)
212 {
213 return VirtualAlloc((void*)aRequest, PageSize(),
214 aAccessible ? MEM_RESERVE|MEM_COMMIT : MEM_RESERVE,
215 aAccessible ? PAGE_EXECUTE_READWRITE : PAGE_NOACCESS);
216 }
217
218 static void
ReleaseRegion(void * aPage)219 ReleaseRegion(void* aPage)
220 {
221 VirtualFree(aPage, PageSize(), MEM_RELEASE);
222 }
223
224 static bool
ProbeRegion(uintptr_t aPage)225 ProbeRegion(uintptr_t aPage)
226 {
227 return aPage >= (uintptr_t)sInfo_.lpMaximumApplicationAddress &&
228 aPage + PageSize() >= (uintptr_t)sInfo_.lpMaximumApplicationAddress;
229 }
230
231 static bool
MakeRegionExecutable(void *)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 gUnixPageSize;
245
246 static inline unsigned long
PageSize()247 PageSize()
248 {
249 return gUnixPageSize;
250 }
251
252 static void*
ReserveRegion(uintptr_t aRequest,bool aAccessible)253 ReserveRegion(uintptr_t aRequest, bool aAccessible)
254 {
255 return mmap(reinterpret_cast<void*>(aRequest), PageSize(),
256 aAccessible ? PROT_READ|PROT_WRITE : PROT_NONE,
257 MAP_PRIVATE|MAP_ANON, -1, 0);
258 }
259
260 static void
ReleaseRegion(void * aPage)261 ReleaseRegion(void* aPage)
262 {
263 munmap(aPage, PageSize());
264 }
265
266 static bool
ProbeRegion(uintptr_t aPage)267 ProbeRegion(uintptr_t aPage)
268 {
269 return !!madvise(reinterpret_cast<void*>(aPage), PageSize(), MADV_NORMAL);
270 }
271
272 static int
MakeRegionExecutable(void * aPage)273 MakeRegionExecutable(void* aPage)
274 {
275 return mprotect((caddr_t)aPage, PageSize(), PROT_READ|PROT_WRITE|PROT_EXEC);
276 }
277
278 #endif
279
280 static uintptr_t
ReservePoisonArea()281 ReservePoisonArea()
282 {
283 if (sizeof(uintptr_t) == 8) {
284 // Use the hardware-inaccessible region.
285 // We have to avoid 64-bit constants and shifts by 32 bits, since this
286 // code is compiled in 32-bit mode, although it is never executed there.
287 uintptr_t result = (((uintptr_t(0x7FFFFFFFu) << 31) << 1 |
288 uintptr_t(0xF0DEAFFFu)) &
289 ~uintptr_t(PageSize()-1));
290 printf("INFO | poison area assumed at 0x%.*" PRIxPTR "\n", SIZxPTR, result);
291 return result;
292 }
293
294 // First see if we can allocate the preferred poison address from the OS.
295 uintptr_t candidate = (0xF0DEAFFF & ~(PageSize() - 1));
296 void* result = ReserveRegion(candidate, false);
297 if (result == reinterpret_cast<void*>(candidate)) {
298 // success - inaccessible page allocated
299 printf("INFO | poison area allocated at 0x%.*" PRIxPTR
300 " (preferred addr)\n", SIZxPTR, reinterpret_cast<uintptr_t>(result));
301 return candidate;
302 }
303
304 // That didn't work, so see if the preferred address is within a range
305 // of permanently inacessible memory.
306 if (ProbeRegion(candidate)) {
307 // success - selected page cannot be usable memory
308 if (result != MAP_FAILED) {
309 ReleaseRegion(result);
310 }
311 printf("INFO | poison area assumed at 0x%.*" PRIxPTR
312 " (preferred addr)\n", SIZxPTR, candidate);
313 return candidate;
314 }
315
316 // The preferred address is already in use. Did the OS give us a
317 // consolation prize?
318 if (result != MAP_FAILED) {
319 uintptr_t ures = reinterpret_cast<uintptr_t>(result);
320 printf("INFO | poison area allocated at 0x%.*" PRIxPTR
321 " (consolation prize)\n", SIZxPTR, ures);
322 return ures;
323 }
324
325 // It didn't, so try to allocate again, without any constraint on
326 // the address.
327 result = ReserveRegion(0, false);
328 if (result != MAP_FAILED) {
329 uintptr_t ures = reinterpret_cast<uintptr_t>(result);
330 printf("INFO | poison area allocated at 0x%.*" PRIxPTR
331 " (fallback)\n", SIZxPTR, ures);
332 return ures;
333 }
334
335 printf("ERROR | no usable poison area found\n");
336 return 0;
337 }
338
339 /* The "positive control" area confirms that we can allocate a page with the
340 * proper characteristics.
341 */
342 static uintptr_t
ReservePositiveControl()343 ReservePositiveControl()
344 {
345
346 void* result = ReserveRegion(0, false);
347 if (result == MAP_FAILED) {
348 printf("ERROR | allocating positive control | %s\n", LastErrMsg());
349 return 0;
350 }
351 printf("INFO | positive control allocated at 0x%.*" PRIxPTR "\n",
352 SIZxPTR, (uintptr_t)result);
353 return (uintptr_t)result;
354 }
355
356 /* The "negative control" area confirms that our probe logic does detect a
357 * page that is readable, writable, or executable.
358 */
359 static uintptr_t
ReserveNegativeControl()360 ReserveNegativeControl()
361 {
362 void* result = ReserveRegion(0, true);
363 if (result == MAP_FAILED) {
364 printf("ERROR | allocating negative control | %s\n", LastErrMsg());
365 return 0;
366 }
367
368 // Fill the page with return instructions.
369 RETURN_INSTR_TYPE* p = reinterpret_cast<RETURN_INSTR_TYPE*>(result);
370 RETURN_INSTR_TYPE* limit =
371 reinterpret_cast<RETURN_INSTR_TYPE*>(
372 reinterpret_cast<char*>(result) + PageSize());
373 while (p < limit) {
374 *p++ = RETURN_INSTR;
375 }
376
377 // Now mark it executable as well as readable and writable.
378 // (mmap(PROT_EXEC) may fail when applied to anonymous memory.)
379
380 if (MakeRegionExecutable(result)) {
381 printf("ERROR | making negative control executable | %s\n", LastErrMsg());
382 return 0;
383 }
384
385 printf("INFO | negative control allocated at 0x%.*" PRIxPTR "\n",
386 SIZxPTR, (uintptr_t)result);
387 return (uintptr_t)result;
388 }
389
390 static void
JumpTo(uintptr_t aOpaddr)391 JumpTo(uintptr_t aOpaddr)
392 {
393 #ifdef __ia64
394 struct func_call
395 {
396 uintptr_t mFunc;
397 uintptr_t mGp;
398 } call = { aOpaddr, };
399 ((void (*)())&call)();
400 #else
401 ((void (*)())aOpaddr)();
402 #endif
403 }
404
405 #ifdef _WIN32
406 static BOOL
IsBadExecPtr(uintptr_t aPtr)407 IsBadExecPtr(uintptr_t aPtr)
408 {
409 BOOL ret = false;
410
411 #ifdef _MSC_VER
412 __try {
413 JumpTo(aPtr);
414 } __except (EXCEPTION_EXECUTE_HANDLER) {
415 ret = true;
416 }
417 #else
418 printf("INFO | exec test not supported on MinGW build\n");
419 // We do our best
420 ret = IsBadReadPtr((const void*)aPtr, 1);
421 #endif
422 return ret;
423 }
424 #endif
425
426 /* Test each page. */
427 static bool
TestPage(const char * aPageLabel,uintptr_t aPageAddr,int aShouldSucceed)428 TestPage(const char* aPageLabel, uintptr_t aPageAddr, int aShouldSucceed)
429 {
430 const char* oplabel;
431 uintptr_t opaddr;
432
433 bool failed = false;
434 for (unsigned int test = 0; test < 3; test++) {
435 switch (test) {
436 // The execute test must be done before the write test, because the
437 // write test will clobber memory at the target address.
438 case 0: oplabel = "reading"; opaddr = aPageAddr + PageSize()/2 - 1; break;
439 case 1: oplabel = "executing"; opaddr = aPageAddr + PageSize()/2; break;
440 case 2: oplabel = "writing"; opaddr = aPageAddr + PageSize()/2 - 1; break;
441 default: abort();
442 }
443
444 #ifdef _WIN32
445 BOOL badptr;
446
447 switch (test) {
448 case 0: badptr = IsBadReadPtr((const void*)opaddr, 1); break;
449 case 1: badptr = IsBadExecPtr(opaddr); break;
450 case 2: badptr = IsBadWritePtr((void*)opaddr, 1); break;
451 default: abort();
452 }
453
454 if (badptr) {
455 if (aShouldSucceed) {
456 printf("TEST-UNEXPECTED-FAIL | %s %s\n", oplabel, aPageLabel);
457 failed = true;
458 } else {
459 printf("TEST-PASS | %s %s\n", oplabel, aPageLabel);
460 }
461 } else {
462 // if control reaches this point the probe succeeded
463 if (aShouldSucceed) {
464 printf("TEST-PASS | %s %s\n", oplabel, aPageLabel);
465 } else {
466 printf("TEST-UNEXPECTED-FAIL | %s %s\n", oplabel, aPageLabel);
467 failed = true;
468 }
469 }
470 #else
471 pid_t pid = fork();
472 if (pid == -1) {
473 printf("ERROR | %s %s | fork=%s\n", oplabel, aPageLabel,
474 LastErrMsg());
475 exit(2);
476 } else if (pid == 0) {
477 volatile unsigned char scratch;
478 switch (test) {
479 case 0: scratch = *(volatile unsigned char*)opaddr; break;
480 case 1: JumpTo(opaddr); break;
481 case 2: *(volatile unsigned char*)opaddr = 0; break;
482 default: abort();
483 }
484 (void)scratch;
485 _exit(0);
486 } else {
487 int status;
488 if (waitpid(pid, &status, 0) != pid) {
489 printf("ERROR | %s %s | wait=%s\n", oplabel, aPageLabel,
490 LastErrMsg());
491 exit(2);
492 }
493
494 if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
495 if (aShouldSucceed) {
496 printf("TEST-PASS | %s %s\n", oplabel, aPageLabel);
497 } else {
498 printf("TEST-UNEXPECTED-FAIL | %s %s | unexpected successful exit\n",
499 oplabel, aPageLabel);
500 failed = true;
501 }
502 } else if (WIFEXITED(status)) {
503 printf("ERROR | %s %s | unexpected exit code %d\n",
504 oplabel, aPageLabel, WEXITSTATUS(status));
505 exit(2);
506 } else if (WIFSIGNALED(status)) {
507 if (aShouldSucceed) {
508 printf("TEST-UNEXPECTED-FAIL | %s %s | unexpected signal %d\n",
509 oplabel, aPageLabel, WTERMSIG(status));
510 failed = true;
511 } else {
512 printf("TEST-PASS | %s %s | signal %d (as expected)\n",
513 oplabel, aPageLabel, WTERMSIG(status));
514 }
515 } else {
516 printf("ERROR | %s %s | unexpected exit status %d\n",
517 oplabel, aPageLabel, status);
518 exit(2);
519 }
520 }
521 #endif
522 }
523 return failed;
524 }
525
526 int
main()527 main()
528 {
529 #ifdef _WIN32
530 GetSystemInfo(&sInfo_);
531 #else
532 gUnixPageSize = sysconf(_SC_PAGESIZE);
533 #endif
534
535 uintptr_t ncontrol = ReserveNegativeControl();
536 uintptr_t pcontrol = ReservePositiveControl();
537 uintptr_t poison = ReservePoisonArea();
538
539 if (!ncontrol || !pcontrol || !poison) {
540 return 2;
541 }
542
543 bool failed = false;
544 failed |= TestPage("negative control", ncontrol, 1);
545 failed |= TestPage("positive control", pcontrol, 0);
546 failed |= TestPage("poison area", poison, 0);
547
548 return failed ? 1 : 0;
549 }
550