|
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 } |