1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mfbt/tests/TestPoisonArea.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,540 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim:set ts=2 sw=2 sts=2 et cindent: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.9 + */ 1.10 + 1.11 +/* Code in this file needs to be kept in sync with code in nsPresArena.cpp. 1.12 + * 1.13 + * We want to use a fixed address for frame poisoning so that it is readily 1.14 + * identifiable in crash dumps. Whether such an address is available 1.15 + * without any special setup depends on the system configuration. 1.16 + * 1.17 + * All current 64-bit CPUs (with the possible exception of PowerPC64) 1.18 + * reserve the vast majority of the virtual address space for future 1.19 + * hardware extensions; valid addresses must be below some break point 1.20 + * between 2**48 and 2**54, depending on exactly which chip you have. Some 1.21 + * chips (notably amd64) also allow the use of the *highest* 2**48 -- 2**54 1.22 + * addresses. Thus, if user space pointers are 64 bits wide, we can just 1.23 + * use an address outside this range, and no more is required. To 1.24 + * accommodate the chips that allow very high addresses to be valid, the 1.25 + * value chosen is close to 2**63 (that is, in the middle of the space). 1.26 + * 1.27 + * In most cases, a purely 32-bit operating system must reserve some 1.28 + * fraction of the address space for its own use. Contemporary 32-bit OSes 1.29 + * tend to take the high gigabyte or so (0xC000_0000 on up). If we can 1.30 + * prove that high addresses are reserved to the kernel, we can use an 1.31 + * address in that region. Unfortunately, not all 32-bit OSes do this; 1.32 + * OSX 10.4 might not, and it is unclear what mobile OSes are like 1.33 + * (some 32-bit CPUs make it very easy for the kernel to exist in its own 1.34 + * private address space). 1.35 + * 1.36 + * Furthermore, when a 32-bit user space process is running on a 64-bit 1.37 + * kernel, the operating system has no need to reserve any of the space that 1.38 + * the process can see, and generally does not do so. This is the scenario 1.39 + * of greatest concern, since it covers all contemporary OSX iterations 1.40 + * (10.5+) as well as Windows Vista and 7 on newer amd64 hardware. Linux on 1.41 + * amd64 is generally run as a pure 64-bit environment, but its 32-bit 1.42 + * compatibility mode also has this property. 1.43 + * 1.44 + * Thus, when user space pointers are 32 bits wide, we need to validate 1.45 + * our chosen address, and possibly *make* it a good poison address by 1.46 + * allocating a page around it and marking it inaccessible. The algorithm 1.47 + * for this is: 1.48 + * 1.49 + * 1. Attempt to make the page surrounding the poison address a reserved, 1.50 + * inaccessible memory region using OS primitives. On Windows, this is 1.51 + * done with VirtualAlloc(MEM_RESERVE); on Unix, mmap(PROT_NONE). 1.52 + * 1.53 + * 2. If mmap/VirtualAlloc failed, there are two possible reasons: either 1.54 + * the region is reserved to the kernel and no further action is 1.55 + * required, or there is already usable memory in this area and we have 1.56 + * to pick a different address. The tricky part is knowing which case 1.57 + * we have, without attempting to access the region. On Windows, we 1.58 + * rely on GetSystemInfo()'s reported upper and lower bounds of the 1.59 + * application memory area. On Unix, there is nothing devoted to the 1.60 + * purpose, but seeing if madvise() fails is close enough (it *might* 1.61 + * disrupt someone else's use of the memory region, but not by as much 1.62 + * as anything else available). 1.63 + * 1.64 + * Be aware of these gotchas: 1.65 + * 1.66 + * 1. We cannot use mmap() with MAP_FIXED. MAP_FIXED is defined to 1.67 + * _replace_ any existing mapping in the region, if necessary to satisfy 1.68 + * the request. Obviously, as we are blindly attempting to acquire a 1.69 + * page at a constant address, we must not do this, lest we overwrite 1.70 + * someone else's allocation. 1.71 + * 1.72 + * 2. For the same reason, we cannot blindly use mprotect() if mmap() fails. 1.73 + * 1.74 + * 3. madvise() may fail when applied to a 'magic' memory region provided as 1.75 + * a kernel/user interface. Fortunately, the only such case I know about 1.76 + * is the "vsyscall" area (not to be confused with the "vdso" area) for 1.77 + * *64*-bit processes on Linux - and we don't even run this code for 1.78 + * 64-bit processes. 1.79 + * 1.80 + * 4. VirtualQuery() does not produce any useful information if 1.81 + * applied to kernel memory - in fact, it doesn't write its output 1.82 + * at all. Thus, it is not used here. 1.83 + */ 1.84 + 1.85 +#include "mozilla/IntegerPrintfMacros.h" 1.86 +#include "mozilla/NullPtr.h" 1.87 + 1.88 +// MAP_ANON(YMOUS) is not in any standard. Add defines as necessary. 1.89 +#define _GNU_SOURCE 1 1.90 +#define _DARWIN_C_SOURCE 1 1.91 + 1.92 +#include <stddef.h> 1.93 + 1.94 +#include <errno.h> 1.95 +#include <stdio.h> 1.96 +#include <stdlib.h> 1.97 +#include <string.h> 1.98 + 1.99 +#ifdef _WIN32 1.100 +#include <windows.h> 1.101 +#else 1.102 +#include <sys/types.h> 1.103 +#include <fcntl.h> 1.104 +#include <signal.h> 1.105 +#include <unistd.h> 1.106 +#include <sys/stat.h> 1.107 +#include <sys/wait.h> 1.108 + 1.109 +#include <sys/mman.h> 1.110 +#ifndef MAP_ANON 1.111 +#ifdef MAP_ANONYMOUS 1.112 +#define MAP_ANON MAP_ANONYMOUS 1.113 +#else 1.114 +#error "Don't know how to get anonymous memory" 1.115 +#endif 1.116 +#endif 1.117 +#endif 1.118 + 1.119 +#define SIZxPTR ((int)(sizeof(uintptr_t)*2)) 1.120 + 1.121 +/* This program assumes that a whole number of return instructions fit into 1.122 + * 32 bits, and that 32-bit alignment is sufficient for a branch destination. 1.123 + * For architectures where this is not true, fiddling with RETURN_INSTR_TYPE 1.124 + * can be enough. 1.125 + */ 1.126 + 1.127 +#if defined __i386__ || defined __x86_64__ || \ 1.128 + defined __i386 || defined __x86_64 || \ 1.129 + defined _M_IX86 || defined _M_AMD64 1.130 +#define RETURN_INSTR 0xC3C3C3C3 /* ret; ret; ret; ret */ 1.131 + 1.132 +#elif defined __arm__ || defined _M_ARM 1.133 +#define RETURN_INSTR 0xE12FFF1E /* bx lr */ 1.134 + 1.135 +// PPC has its own style of CPU-id #defines. There is no Windows for 1.136 +// PPC as far as I know, so no _M_ variant. 1.137 +#elif defined _ARCH_PPC || defined _ARCH_PWR || defined _ARCH_PWR2 1.138 +#define RETURN_INSTR 0x4E800020 /* blr */ 1.139 + 1.140 +#elif defined __sparc || defined __sparcv9 1.141 +#define RETURN_INSTR 0x81c3e008 /* retl */ 1.142 + 1.143 +#elif defined __alpha 1.144 +#define RETURN_INSTR 0x6bfa8001 /* ret */ 1.145 + 1.146 +#elif defined __hppa 1.147 +#define RETURN_INSTR 0xe840c002 /* bv,n r0(rp) */ 1.148 + 1.149 +#elif defined __mips 1.150 +#define RETURN_INSTR 0x03e00008 /* jr ra */ 1.151 + 1.152 +#ifdef __MIPSEL 1.153 +/* On mipsel, jr ra needs to be followed by a nop. 1.154 + 0x03e00008 as a 64 bits integer just does that */ 1.155 +#define RETURN_INSTR_TYPE uint64_t 1.156 +#endif 1.157 + 1.158 +#elif defined __s390__ 1.159 +#define RETURN_INSTR 0x07fe0000 /* br %r14 */ 1.160 + 1.161 +#elif defined __aarch64__ 1.162 +#define RETURN_INSTR 0xd65f03c0 /* ret */ 1.163 + 1.164 +#elif defined __ia64 1.165 +struct ia64_instr { uint32_t i[4]; }; 1.166 +static const ia64_instr _return_instr = 1.167 + {{ 0x00000011, 0x00000001, 0x80000200, 0x00840008 }}; /* br.ret.sptk.many b0 */ 1.168 + 1.169 +#define RETURN_INSTR _return_instr 1.170 +#define RETURN_INSTR_TYPE ia64_instr 1.171 + 1.172 +#else 1.173 +#error "Need return instruction for this architecture" 1.174 +#endif 1.175 + 1.176 +#ifndef RETURN_INSTR_TYPE 1.177 +#define RETURN_INSTR_TYPE uint32_t 1.178 +#endif 1.179 + 1.180 +// Miscellaneous Windows/Unix portability gumph 1.181 + 1.182 +#ifdef _WIN32 1.183 +// Uses of this function deliberately leak the string. 1.184 +static LPSTR 1.185 +StrW32Error(DWORD errcode) 1.186 +{ 1.187 + LPSTR errmsg; 1.188 + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | 1.189 + FORMAT_MESSAGE_FROM_SYSTEM | 1.190 + FORMAT_MESSAGE_IGNORE_INSERTS, 1.191 + nullptr, errcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 1.192 + (LPSTR)&errmsg, 0, nullptr); 1.193 + 1.194 + // FormatMessage puts an unwanted newline at the end of the string 1.195 + size_t n = strlen(errmsg)-1; 1.196 + while (errmsg[n] == '\r' || errmsg[n] == '\n') n--; 1.197 + errmsg[n+1] = '\0'; 1.198 + return errmsg; 1.199 +} 1.200 +#define LastErrMsg() (StrW32Error(GetLastError())) 1.201 + 1.202 +// Because we use VirtualAlloc in MEM_RESERVE mode, the "page size" we want 1.203 +// is the allocation granularity. 1.204 +static SYSTEM_INFO _sinfo; 1.205 +#undef PAGESIZE 1.206 +#define PAGESIZE (_sinfo.dwAllocationGranularity) 1.207 + 1.208 + 1.209 +static void * 1.210 +ReserveRegion(uintptr_t request, bool accessible) 1.211 +{ 1.212 + return VirtualAlloc((void *)request, PAGESIZE, 1.213 + accessible ? MEM_RESERVE|MEM_COMMIT : MEM_RESERVE, 1.214 + accessible ? PAGE_EXECUTE_READWRITE : PAGE_NOACCESS); 1.215 +} 1.216 + 1.217 +static void 1.218 +ReleaseRegion(void *page) 1.219 +{ 1.220 + VirtualFree(page, PAGESIZE, MEM_RELEASE); 1.221 +} 1.222 + 1.223 +static bool 1.224 +ProbeRegion(uintptr_t page) 1.225 +{ 1.226 + if (page >= (uintptr_t)_sinfo.lpMaximumApplicationAddress && 1.227 + page + PAGESIZE >= (uintptr_t)_sinfo.lpMaximumApplicationAddress) { 1.228 + return true; 1.229 + } else { 1.230 + return false; 1.231 + } 1.232 +} 1.233 + 1.234 +static bool 1.235 +MakeRegionExecutable(void *) 1.236 +{ 1.237 + return false; 1.238 +} 1.239 + 1.240 +#undef MAP_FAILED 1.241 +#define MAP_FAILED 0 1.242 + 1.243 +#else // Unix 1.244 + 1.245 +#define LastErrMsg() (strerror(errno)) 1.246 + 1.247 +static unsigned long _pagesize; 1.248 +#define PAGESIZE _pagesize 1.249 + 1.250 +static void * 1.251 +ReserveRegion(uintptr_t request, bool accessible) 1.252 +{ 1.253 + return mmap(reinterpret_cast<void*>(request), PAGESIZE, 1.254 + accessible ? PROT_READ|PROT_WRITE : PROT_NONE, 1.255 + MAP_PRIVATE|MAP_ANON, -1, 0); 1.256 +} 1.257 + 1.258 +static void 1.259 +ReleaseRegion(void *page) 1.260 +{ 1.261 + munmap(page, PAGESIZE); 1.262 +} 1.263 + 1.264 +static bool 1.265 +ProbeRegion(uintptr_t page) 1.266 +{ 1.267 + if (madvise(reinterpret_cast<void*>(page), PAGESIZE, MADV_NORMAL)) { 1.268 + return true; 1.269 + } else { 1.270 + return false; 1.271 + } 1.272 +} 1.273 + 1.274 +static int 1.275 +MakeRegionExecutable(void *page) 1.276 +{ 1.277 + return mprotect((caddr_t)page, PAGESIZE, PROT_READ|PROT_WRITE|PROT_EXEC); 1.278 +} 1.279 + 1.280 +#endif 1.281 + 1.282 +static uintptr_t 1.283 +ReservePoisonArea() 1.284 +{ 1.285 + if (sizeof(uintptr_t) == 8) { 1.286 + // Use the hardware-inaccessible region. 1.287 + // We have to avoid 64-bit constants and shifts by 32 bits, since this 1.288 + // code is compiled in 32-bit mode, although it is never executed there. 1.289 + uintptr_t result = (((uintptr_t(0x7FFFFFFFu) << 31) << 1 | 1.290 + uintptr_t(0xF0DEAFFFu)) & 1.291 + ~uintptr_t(PAGESIZE-1)); 1.292 + printf("INFO | poison area assumed at 0x%.*" PRIxPTR "\n", SIZxPTR, result); 1.293 + return result; 1.294 + } else { 1.295 + // First see if we can allocate the preferred poison address from the OS. 1.296 + uintptr_t candidate = (0xF0DEAFFF & ~(PAGESIZE-1)); 1.297 + void *result = ReserveRegion(candidate, false); 1.298 + if (result == (void *)candidate) { 1.299 + // success - inaccessible page allocated 1.300 + printf("INFO | poison area allocated at 0x%.*" PRIxPTR 1.301 + " (preferred addr)\n", SIZxPTR, (uintptr_t)result); 1.302 + return candidate; 1.303 + } 1.304 + 1.305 + // That didn't work, so see if the preferred address is within a range 1.306 + // of permanently inacessible memory. 1.307 + if (ProbeRegion(candidate)) { 1.308 + // success - selected page cannot be usable memory 1.309 + if (result != MAP_FAILED) 1.310 + ReleaseRegion(result); 1.311 + printf("INFO | poison area assumed at 0x%.*" PRIxPTR 1.312 + " (preferred addr)\n", SIZxPTR, candidate); 1.313 + return candidate; 1.314 + } 1.315 + 1.316 + // The preferred address is already in use. Did the OS give us a 1.317 + // consolation prize? 1.318 + if (result != MAP_FAILED) { 1.319 + printf("INFO | poison area allocated at 0x%.*" PRIxPTR 1.320 + " (consolation prize)\n", SIZxPTR, (uintptr_t)result); 1.321 + return (uintptr_t)result; 1.322 + } 1.323 + 1.324 + // It didn't, so try to allocate again, without any constraint on 1.325 + // the address. 1.326 + result = ReserveRegion(0, false); 1.327 + if (result != MAP_FAILED) { 1.328 + printf("INFO | poison area allocated at 0x%.*" PRIxPTR 1.329 + " (fallback)\n", SIZxPTR, (uintptr_t)result); 1.330 + return (uintptr_t)result; 1.331 + } 1.332 + 1.333 + printf("ERROR | no usable poison area found\n"); 1.334 + return 0; 1.335 + } 1.336 +} 1.337 + 1.338 +/* The "positive control" area confirms that we can allocate a page with the 1.339 + * proper characteristics. 1.340 + */ 1.341 +static uintptr_t 1.342 +ReservePositiveControl() 1.343 +{ 1.344 + 1.345 + void *result = ReserveRegion(0, false); 1.346 + if (result == MAP_FAILED) { 1.347 + printf("ERROR | allocating positive control | %s\n", LastErrMsg()); 1.348 + return 0; 1.349 + } 1.350 + printf("INFO | positive control allocated at 0x%.*" PRIxPTR "\n", 1.351 + SIZxPTR, (uintptr_t)result); 1.352 + return (uintptr_t)result; 1.353 +} 1.354 + 1.355 +/* The "negative control" area confirms that our probe logic does detect a 1.356 + * page that is readable, writable, or executable. 1.357 + */ 1.358 +static uintptr_t 1.359 +ReserveNegativeControl() 1.360 +{ 1.361 + void *result = ReserveRegion(0, true); 1.362 + if (result == MAP_FAILED) { 1.363 + printf("ERROR | allocating negative control | %s\n", LastErrMsg()); 1.364 + return 0; 1.365 + } 1.366 + 1.367 + // Fill the page with return instructions. 1.368 + RETURN_INSTR_TYPE *p = (RETURN_INSTR_TYPE *)result; 1.369 + RETURN_INSTR_TYPE *limit = (RETURN_INSTR_TYPE *)(((char *)result) + PAGESIZE); 1.370 + while (p < limit) 1.371 + *p++ = RETURN_INSTR; 1.372 + 1.373 + // Now mark it executable as well as readable and writable. 1.374 + // (mmap(PROT_EXEC) may fail when applied to anonymous memory.) 1.375 + 1.376 + if (MakeRegionExecutable(result)) { 1.377 + printf("ERROR | making negative control executable | %s\n", LastErrMsg()); 1.378 + return 0; 1.379 + } 1.380 + 1.381 + printf("INFO | negative control allocated at 0x%.*" PRIxPTR "\n", 1.382 + SIZxPTR, (uintptr_t)result); 1.383 + return (uintptr_t)result; 1.384 +} 1.385 + 1.386 +static void 1.387 +JumpTo(uintptr_t opaddr) 1.388 +{ 1.389 +#ifdef __ia64 1.390 + struct func_call { 1.391 + uintptr_t func; 1.392 + uintptr_t gp; 1.393 + } call = { opaddr, }; 1.394 + ((void (*)())&call)(); 1.395 +#else 1.396 + ((void (*)())opaddr)(); 1.397 +#endif 1.398 +} 1.399 + 1.400 +#ifdef _WIN32 1.401 +static BOOL 1.402 +IsBadExecPtr(uintptr_t ptr) 1.403 +{ 1.404 + BOOL ret = false; 1.405 + 1.406 +#ifdef _MSC_VER 1.407 + __try { 1.408 + JumpTo(ptr); 1.409 + } __except (EXCEPTION_EXECUTE_HANDLER) { 1.410 + ret = true; 1.411 + } 1.412 +#else 1.413 + printf("INFO | exec test not supported on MinGW build\n"); 1.414 + // We do our best 1.415 + ret = IsBadReadPtr((const void*)ptr, 1); 1.416 +#endif 1.417 + return ret; 1.418 +} 1.419 +#endif 1.420 + 1.421 +/* Test each page. */ 1.422 +static bool 1.423 +TestPage(const char *pagelabel, uintptr_t pageaddr, int should_succeed) 1.424 +{ 1.425 + const char *oplabel; 1.426 + uintptr_t opaddr; 1.427 + 1.428 + bool failed = false; 1.429 + for (unsigned int test = 0; test < 3; test++) { 1.430 + switch (test) { 1.431 + // The execute test must be done before the write test, because the 1.432 + // write test will clobber memory at the target address. 1.433 + case 0: oplabel = "reading"; opaddr = pageaddr + PAGESIZE/2 - 1; break; 1.434 + case 1: oplabel = "executing"; opaddr = pageaddr + PAGESIZE/2; break; 1.435 + case 2: oplabel = "writing"; opaddr = pageaddr + PAGESIZE/2 - 1; break; 1.436 + default: abort(); 1.437 + } 1.438 + 1.439 +#ifdef _WIN32 1.440 + BOOL badptr; 1.441 + 1.442 + switch (test) { 1.443 + case 0: badptr = IsBadReadPtr((const void*)opaddr, 1); break; 1.444 + case 1: badptr = IsBadExecPtr(opaddr); break; 1.445 + case 2: badptr = IsBadWritePtr((void*)opaddr, 1); break; 1.446 + default: abort(); 1.447 + } 1.448 + 1.449 + if (badptr) { 1.450 + if (should_succeed) { 1.451 + printf("TEST-UNEXPECTED-FAIL | %s %s\n", oplabel, pagelabel); 1.452 + failed = true; 1.453 + } else { 1.454 + printf("TEST-PASS | %s %s\n", oplabel, pagelabel); 1.455 + } 1.456 + } else { 1.457 + // if control reaches this point the probe succeeded 1.458 + if (should_succeed) { 1.459 + printf("TEST-PASS | %s %s\n", oplabel, pagelabel); 1.460 + } else { 1.461 + printf("TEST-UNEXPECTED-FAIL | %s %s\n", oplabel, pagelabel); 1.462 + failed = true; 1.463 + } 1.464 + } 1.465 +#else 1.466 + pid_t pid = fork(); 1.467 + if (pid == -1) { 1.468 + printf("ERROR | %s %s | fork=%s\n", oplabel, pagelabel, 1.469 + LastErrMsg()); 1.470 + exit(2); 1.471 + } else if (pid == 0) { 1.472 + volatile unsigned char scratch; 1.473 + switch (test) { 1.474 + case 0: scratch = *(volatile unsigned char *)opaddr; break; 1.475 + case 1: JumpTo(opaddr); break; 1.476 + case 2: *(volatile unsigned char *)opaddr = 0; break; 1.477 + default: abort(); 1.478 + } 1.479 + (void)scratch; 1.480 + _exit(0); 1.481 + } else { 1.482 + int status; 1.483 + if (waitpid(pid, &status, 0) != pid) { 1.484 + printf("ERROR | %s %s | wait=%s\n", oplabel, pagelabel, 1.485 + LastErrMsg()); 1.486 + exit(2); 1.487 + } 1.488 + 1.489 + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { 1.490 + if (should_succeed) { 1.491 + printf("TEST-PASS | %s %s\n", oplabel, pagelabel); 1.492 + } else { 1.493 + printf("TEST-UNEXPECTED-FAIL | %s %s | unexpected successful exit\n", 1.494 + oplabel, pagelabel); 1.495 + failed = true; 1.496 + } 1.497 + } else if (WIFEXITED(status)) { 1.498 + printf("ERROR | %s %s | unexpected exit code %d\n", 1.499 + oplabel, pagelabel, WEXITSTATUS(status)); 1.500 + exit(2); 1.501 + } else if (WIFSIGNALED(status)) { 1.502 + if (should_succeed) { 1.503 + printf("TEST-UNEXPECTED-FAIL | %s %s | unexpected signal %d\n", 1.504 + oplabel, pagelabel, WTERMSIG(status)); 1.505 + failed = true; 1.506 + } else { 1.507 + printf("TEST-PASS | %s %s | signal %d (as expected)\n", 1.508 + oplabel, pagelabel, WTERMSIG(status)); 1.509 + } 1.510 + } else { 1.511 + printf("ERROR | %s %s | unexpected exit status %d\n", 1.512 + oplabel, pagelabel, status); 1.513 + exit(2); 1.514 + } 1.515 + } 1.516 +#endif 1.517 + } 1.518 + return failed; 1.519 +} 1.520 + 1.521 +int 1.522 +main() 1.523 +{ 1.524 +#ifdef _WIN32 1.525 + GetSystemInfo(&_sinfo); 1.526 +#else 1.527 + _pagesize = sysconf(_SC_PAGESIZE); 1.528 +#endif 1.529 + 1.530 + uintptr_t ncontrol = ReserveNegativeControl(); 1.531 + uintptr_t pcontrol = ReservePositiveControl(); 1.532 + uintptr_t poison = ReservePoisonArea(); 1.533 + 1.534 + if (!ncontrol || !pcontrol || !poison) 1.535 + return 2; 1.536 + 1.537 + bool failed = false; 1.538 + failed |= TestPage("negative control", ncontrol, 1); 1.539 + failed |= TestPage("positive control", pcontrol, 0); 1.540 + failed |= TestPage("poison area", poison, 0); 1.541 + 1.542 + return failed ? 1 : 0; 1.543 +}