michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: */ michael@0: michael@0: /* Code in this file needs to be kept in sync with code in nsPresArena.cpp. michael@0: * michael@0: * We want to use a fixed address for frame poisoning so that it is readily michael@0: * identifiable in crash dumps. Whether such an address is available michael@0: * without any special setup depends on the system configuration. michael@0: * michael@0: * All current 64-bit CPUs (with the possible exception of PowerPC64) michael@0: * reserve the vast majority of the virtual address space for future michael@0: * hardware extensions; valid addresses must be below some break point michael@0: * between 2**48 and 2**54, depending on exactly which chip you have. Some michael@0: * chips (notably amd64) also allow the use of the *highest* 2**48 -- 2**54 michael@0: * addresses. Thus, if user space pointers are 64 bits wide, we can just michael@0: * use an address outside this range, and no more is required. To michael@0: * accommodate the chips that allow very high addresses to be valid, the michael@0: * value chosen is close to 2**63 (that is, in the middle of the space). michael@0: * michael@0: * In most cases, a purely 32-bit operating system must reserve some michael@0: * fraction of the address space for its own use. Contemporary 32-bit OSes michael@0: * tend to take the high gigabyte or so (0xC000_0000 on up). If we can michael@0: * prove that high addresses are reserved to the kernel, we can use an michael@0: * address in that region. Unfortunately, not all 32-bit OSes do this; michael@0: * OSX 10.4 might not, and it is unclear what mobile OSes are like michael@0: * (some 32-bit CPUs make it very easy for the kernel to exist in its own michael@0: * private address space). michael@0: * michael@0: * Furthermore, when a 32-bit user space process is running on a 64-bit michael@0: * kernel, the operating system has no need to reserve any of the space that michael@0: * the process can see, and generally does not do so. This is the scenario michael@0: * of greatest concern, since it covers all contemporary OSX iterations michael@0: * (10.5+) as well as Windows Vista and 7 on newer amd64 hardware. Linux on michael@0: * amd64 is generally run as a pure 64-bit environment, but its 32-bit michael@0: * compatibility mode also has this property. michael@0: * michael@0: * Thus, when user space pointers are 32 bits wide, we need to validate michael@0: * our chosen address, and possibly *make* it a good poison address by michael@0: * allocating a page around it and marking it inaccessible. The algorithm michael@0: * for this is: michael@0: * michael@0: * 1. Attempt to make the page surrounding the poison address a reserved, michael@0: * inaccessible memory region using OS primitives. On Windows, this is michael@0: * done with VirtualAlloc(MEM_RESERVE); on Unix, mmap(PROT_NONE). michael@0: * michael@0: * 2. If mmap/VirtualAlloc failed, there are two possible reasons: either michael@0: * the region is reserved to the kernel and no further action is michael@0: * required, or there is already usable memory in this area and we have michael@0: * to pick a different address. The tricky part is knowing which case michael@0: * we have, without attempting to access the region. On Windows, we michael@0: * rely on GetSystemInfo()'s reported upper and lower bounds of the michael@0: * application memory area. On Unix, there is nothing devoted to the michael@0: * purpose, but seeing if madvise() fails is close enough (it *might* michael@0: * disrupt someone else's use of the memory region, but not by as much michael@0: * as anything else available). michael@0: * michael@0: * Be aware of these gotchas: michael@0: * michael@0: * 1. We cannot use mmap() with MAP_FIXED. MAP_FIXED is defined to michael@0: * _replace_ any existing mapping in the region, if necessary to satisfy michael@0: * the request. Obviously, as we are blindly attempting to acquire a michael@0: * page at a constant address, we must not do this, lest we overwrite michael@0: * someone else's allocation. michael@0: * michael@0: * 2. For the same reason, we cannot blindly use mprotect() if mmap() fails. michael@0: * michael@0: * 3. madvise() may fail when applied to a 'magic' memory region provided as michael@0: * a kernel/user interface. Fortunately, the only such case I know about michael@0: * is the "vsyscall" area (not to be confused with the "vdso" area) for michael@0: * *64*-bit processes on Linux - and we don't even run this code for michael@0: * 64-bit processes. michael@0: * michael@0: * 4. VirtualQuery() does not produce any useful information if michael@0: * applied to kernel memory - in fact, it doesn't write its output michael@0: * at all. Thus, it is not used here. michael@0: */ michael@0: michael@0: #include "mozilla/IntegerPrintfMacros.h" michael@0: #include "mozilla/NullPtr.h" michael@0: michael@0: // MAP_ANON(YMOUS) is not in any standard. Add defines as necessary. michael@0: #define _GNU_SOURCE 1 michael@0: #define _DARWIN_C_SOURCE 1 michael@0: michael@0: #include michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #ifdef _WIN32 michael@0: #include michael@0: #else michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include michael@0: #ifndef MAP_ANON michael@0: #ifdef MAP_ANONYMOUS michael@0: #define MAP_ANON MAP_ANONYMOUS michael@0: #else michael@0: #error "Don't know how to get anonymous memory" michael@0: #endif michael@0: #endif michael@0: #endif michael@0: michael@0: #define SIZxPTR ((int)(sizeof(uintptr_t)*2)) michael@0: michael@0: /* This program assumes that a whole number of return instructions fit into michael@0: * 32 bits, and that 32-bit alignment is sufficient for a branch destination. michael@0: * For architectures where this is not true, fiddling with RETURN_INSTR_TYPE michael@0: * can be enough. michael@0: */ michael@0: michael@0: #if defined __i386__ || defined __x86_64__ || \ michael@0: defined __i386 || defined __x86_64 || \ michael@0: defined _M_IX86 || defined _M_AMD64 michael@0: #define RETURN_INSTR 0xC3C3C3C3 /* ret; ret; ret; ret */ michael@0: michael@0: #elif defined __arm__ || defined _M_ARM michael@0: #define RETURN_INSTR 0xE12FFF1E /* bx lr */ michael@0: michael@0: // PPC has its own style of CPU-id #defines. There is no Windows for michael@0: // PPC as far as I know, so no _M_ variant. michael@0: #elif defined _ARCH_PPC || defined _ARCH_PWR || defined _ARCH_PWR2 michael@0: #define RETURN_INSTR 0x4E800020 /* blr */ michael@0: michael@0: #elif defined __sparc || defined __sparcv9 michael@0: #define RETURN_INSTR 0x81c3e008 /* retl */ michael@0: michael@0: #elif defined __alpha michael@0: #define RETURN_INSTR 0x6bfa8001 /* ret */ michael@0: michael@0: #elif defined __hppa michael@0: #define RETURN_INSTR 0xe840c002 /* bv,n r0(rp) */ michael@0: michael@0: #elif defined __mips michael@0: #define RETURN_INSTR 0x03e00008 /* jr ra */ michael@0: michael@0: #ifdef __MIPSEL michael@0: /* On mipsel, jr ra needs to be followed by a nop. michael@0: 0x03e00008 as a 64 bits integer just does that */ michael@0: #define RETURN_INSTR_TYPE uint64_t michael@0: #endif michael@0: michael@0: #elif defined __s390__ michael@0: #define RETURN_INSTR 0x07fe0000 /* br %r14 */ michael@0: michael@0: #elif defined __aarch64__ michael@0: #define RETURN_INSTR 0xd65f03c0 /* ret */ michael@0: michael@0: #elif defined __ia64 michael@0: struct ia64_instr { uint32_t i[4]; }; michael@0: static const ia64_instr _return_instr = michael@0: {{ 0x00000011, 0x00000001, 0x80000200, 0x00840008 }}; /* br.ret.sptk.many b0 */ michael@0: michael@0: #define RETURN_INSTR _return_instr michael@0: #define RETURN_INSTR_TYPE ia64_instr michael@0: michael@0: #else michael@0: #error "Need return instruction for this architecture" michael@0: #endif michael@0: michael@0: #ifndef RETURN_INSTR_TYPE michael@0: #define RETURN_INSTR_TYPE uint32_t michael@0: #endif michael@0: michael@0: // Miscellaneous Windows/Unix portability gumph michael@0: michael@0: #ifdef _WIN32 michael@0: // Uses of this function deliberately leak the string. michael@0: static LPSTR michael@0: StrW32Error(DWORD errcode) michael@0: { michael@0: LPSTR errmsg; michael@0: FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | michael@0: FORMAT_MESSAGE_FROM_SYSTEM | michael@0: FORMAT_MESSAGE_IGNORE_INSERTS, michael@0: nullptr, errcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), michael@0: (LPSTR)&errmsg, 0, nullptr); michael@0: michael@0: // FormatMessage puts an unwanted newline at the end of the string michael@0: size_t n = strlen(errmsg)-1; michael@0: while (errmsg[n] == '\r' || errmsg[n] == '\n') n--; michael@0: errmsg[n+1] = '\0'; michael@0: return errmsg; michael@0: } michael@0: #define LastErrMsg() (StrW32Error(GetLastError())) michael@0: michael@0: // Because we use VirtualAlloc in MEM_RESERVE mode, the "page size" we want michael@0: // is the allocation granularity. michael@0: static SYSTEM_INFO _sinfo; michael@0: #undef PAGESIZE michael@0: #define PAGESIZE (_sinfo.dwAllocationGranularity) michael@0: michael@0: michael@0: static void * michael@0: ReserveRegion(uintptr_t request, bool accessible) michael@0: { michael@0: return VirtualAlloc((void *)request, PAGESIZE, michael@0: accessible ? MEM_RESERVE|MEM_COMMIT : MEM_RESERVE, michael@0: accessible ? PAGE_EXECUTE_READWRITE : PAGE_NOACCESS); michael@0: } michael@0: michael@0: static void michael@0: ReleaseRegion(void *page) michael@0: { michael@0: VirtualFree(page, PAGESIZE, MEM_RELEASE); michael@0: } michael@0: michael@0: static bool michael@0: ProbeRegion(uintptr_t page) michael@0: { michael@0: if (page >= (uintptr_t)_sinfo.lpMaximumApplicationAddress && michael@0: page + PAGESIZE >= (uintptr_t)_sinfo.lpMaximumApplicationAddress) { michael@0: return true; michael@0: } else { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: MakeRegionExecutable(void *) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: #undef MAP_FAILED michael@0: #define MAP_FAILED 0 michael@0: michael@0: #else // Unix michael@0: michael@0: #define LastErrMsg() (strerror(errno)) michael@0: michael@0: static unsigned long _pagesize; michael@0: #define PAGESIZE _pagesize michael@0: michael@0: static void * michael@0: ReserveRegion(uintptr_t request, bool accessible) michael@0: { michael@0: return mmap(reinterpret_cast(request), PAGESIZE, michael@0: accessible ? PROT_READ|PROT_WRITE : PROT_NONE, michael@0: MAP_PRIVATE|MAP_ANON, -1, 0); michael@0: } michael@0: michael@0: static void michael@0: ReleaseRegion(void *page) michael@0: { michael@0: munmap(page, PAGESIZE); michael@0: } michael@0: michael@0: static bool michael@0: ProbeRegion(uintptr_t page) michael@0: { michael@0: if (madvise(reinterpret_cast(page), PAGESIZE, MADV_NORMAL)) { michael@0: return true; michael@0: } else { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: static int michael@0: MakeRegionExecutable(void *page) michael@0: { michael@0: return mprotect((caddr_t)page, PAGESIZE, PROT_READ|PROT_WRITE|PROT_EXEC); michael@0: } michael@0: michael@0: #endif michael@0: michael@0: static uintptr_t michael@0: ReservePoisonArea() michael@0: { michael@0: if (sizeof(uintptr_t) == 8) { michael@0: // Use the hardware-inaccessible region. michael@0: // We have to avoid 64-bit constants and shifts by 32 bits, since this michael@0: // code is compiled in 32-bit mode, although it is never executed there. michael@0: uintptr_t result = (((uintptr_t(0x7FFFFFFFu) << 31) << 1 | michael@0: uintptr_t(0xF0DEAFFFu)) & michael@0: ~uintptr_t(PAGESIZE-1)); michael@0: printf("INFO | poison area assumed at 0x%.*" PRIxPTR "\n", SIZxPTR, result); michael@0: return result; michael@0: } else { michael@0: // First see if we can allocate the preferred poison address from the OS. michael@0: uintptr_t candidate = (0xF0DEAFFF & ~(PAGESIZE-1)); michael@0: void *result = ReserveRegion(candidate, false); michael@0: if (result == (void *)candidate) { michael@0: // success - inaccessible page allocated michael@0: printf("INFO | poison area allocated at 0x%.*" PRIxPTR michael@0: " (preferred addr)\n", SIZxPTR, (uintptr_t)result); michael@0: return candidate; michael@0: } michael@0: michael@0: // That didn't work, so see if the preferred address is within a range michael@0: // of permanently inacessible memory. michael@0: if (ProbeRegion(candidate)) { michael@0: // success - selected page cannot be usable memory michael@0: if (result != MAP_FAILED) michael@0: ReleaseRegion(result); michael@0: printf("INFO | poison area assumed at 0x%.*" PRIxPTR michael@0: " (preferred addr)\n", SIZxPTR, candidate); michael@0: return candidate; michael@0: } michael@0: michael@0: // The preferred address is already in use. Did the OS give us a michael@0: // consolation prize? michael@0: if (result != MAP_FAILED) { michael@0: printf("INFO | poison area allocated at 0x%.*" PRIxPTR michael@0: " (consolation prize)\n", SIZxPTR, (uintptr_t)result); michael@0: return (uintptr_t)result; michael@0: } michael@0: michael@0: // It didn't, so try to allocate again, without any constraint on michael@0: // the address. michael@0: result = ReserveRegion(0, false); michael@0: if (result != MAP_FAILED) { michael@0: printf("INFO | poison area allocated at 0x%.*" PRIxPTR michael@0: " (fallback)\n", SIZxPTR, (uintptr_t)result); michael@0: return (uintptr_t)result; michael@0: } michael@0: michael@0: printf("ERROR | no usable poison area found\n"); michael@0: return 0; michael@0: } michael@0: } michael@0: michael@0: /* The "positive control" area confirms that we can allocate a page with the michael@0: * proper characteristics. michael@0: */ michael@0: static uintptr_t michael@0: ReservePositiveControl() michael@0: { michael@0: michael@0: void *result = ReserveRegion(0, false); michael@0: if (result == MAP_FAILED) { michael@0: printf("ERROR | allocating positive control | %s\n", LastErrMsg()); michael@0: return 0; michael@0: } michael@0: printf("INFO | positive control allocated at 0x%.*" PRIxPTR "\n", michael@0: SIZxPTR, (uintptr_t)result); michael@0: return (uintptr_t)result; michael@0: } michael@0: michael@0: /* The "negative control" area confirms that our probe logic does detect a michael@0: * page that is readable, writable, or executable. michael@0: */ michael@0: static uintptr_t michael@0: ReserveNegativeControl() michael@0: { michael@0: void *result = ReserveRegion(0, true); michael@0: if (result == MAP_FAILED) { michael@0: printf("ERROR | allocating negative control | %s\n", LastErrMsg()); michael@0: return 0; michael@0: } michael@0: michael@0: // Fill the page with return instructions. michael@0: RETURN_INSTR_TYPE *p = (RETURN_INSTR_TYPE *)result; michael@0: RETURN_INSTR_TYPE *limit = (RETURN_INSTR_TYPE *)(((char *)result) + PAGESIZE); michael@0: while (p < limit) michael@0: *p++ = RETURN_INSTR; michael@0: michael@0: // Now mark it executable as well as readable and writable. michael@0: // (mmap(PROT_EXEC) may fail when applied to anonymous memory.) michael@0: michael@0: if (MakeRegionExecutable(result)) { michael@0: printf("ERROR | making negative control executable | %s\n", LastErrMsg()); michael@0: return 0; michael@0: } michael@0: michael@0: printf("INFO | negative control allocated at 0x%.*" PRIxPTR "\n", michael@0: SIZxPTR, (uintptr_t)result); michael@0: return (uintptr_t)result; michael@0: } michael@0: michael@0: static void michael@0: JumpTo(uintptr_t opaddr) michael@0: { michael@0: #ifdef __ia64 michael@0: struct func_call { michael@0: uintptr_t func; michael@0: uintptr_t gp; michael@0: } call = { opaddr, }; michael@0: ((void (*)())&call)(); michael@0: #else michael@0: ((void (*)())opaddr)(); michael@0: #endif michael@0: } michael@0: michael@0: #ifdef _WIN32 michael@0: static BOOL michael@0: IsBadExecPtr(uintptr_t ptr) michael@0: { michael@0: BOOL ret = false; michael@0: michael@0: #ifdef _MSC_VER michael@0: __try { michael@0: JumpTo(ptr); michael@0: } __except (EXCEPTION_EXECUTE_HANDLER) { michael@0: ret = true; michael@0: } michael@0: #else michael@0: printf("INFO | exec test not supported on MinGW build\n"); michael@0: // We do our best michael@0: ret = IsBadReadPtr((const void*)ptr, 1); michael@0: #endif michael@0: return ret; michael@0: } michael@0: #endif michael@0: michael@0: /* Test each page. */ michael@0: static bool michael@0: TestPage(const char *pagelabel, uintptr_t pageaddr, int should_succeed) michael@0: { michael@0: const char *oplabel; michael@0: uintptr_t opaddr; michael@0: michael@0: bool failed = false; michael@0: for (unsigned int test = 0; test < 3; test++) { michael@0: switch (test) { michael@0: // The execute test must be done before the write test, because the michael@0: // write test will clobber memory at the target address. michael@0: case 0: oplabel = "reading"; opaddr = pageaddr + PAGESIZE/2 - 1; break; michael@0: case 1: oplabel = "executing"; opaddr = pageaddr + PAGESIZE/2; break; michael@0: case 2: oplabel = "writing"; opaddr = pageaddr + PAGESIZE/2 - 1; break; michael@0: default: abort(); michael@0: } michael@0: michael@0: #ifdef _WIN32 michael@0: BOOL badptr; michael@0: michael@0: switch (test) { michael@0: case 0: badptr = IsBadReadPtr((const void*)opaddr, 1); break; michael@0: case 1: badptr = IsBadExecPtr(opaddr); break; michael@0: case 2: badptr = IsBadWritePtr((void*)opaddr, 1); break; michael@0: default: abort(); michael@0: } michael@0: michael@0: if (badptr) { michael@0: if (should_succeed) { michael@0: printf("TEST-UNEXPECTED-FAIL | %s %s\n", oplabel, pagelabel); michael@0: failed = true; michael@0: } else { michael@0: printf("TEST-PASS | %s %s\n", oplabel, pagelabel); michael@0: } michael@0: } else { michael@0: // if control reaches this point the probe succeeded michael@0: if (should_succeed) { michael@0: printf("TEST-PASS | %s %s\n", oplabel, pagelabel); michael@0: } else { michael@0: printf("TEST-UNEXPECTED-FAIL | %s %s\n", oplabel, pagelabel); michael@0: failed = true; michael@0: } michael@0: } michael@0: #else michael@0: pid_t pid = fork(); michael@0: if (pid == -1) { michael@0: printf("ERROR | %s %s | fork=%s\n", oplabel, pagelabel, michael@0: LastErrMsg()); michael@0: exit(2); michael@0: } else if (pid == 0) { michael@0: volatile unsigned char scratch; michael@0: switch (test) { michael@0: case 0: scratch = *(volatile unsigned char *)opaddr; break; michael@0: case 1: JumpTo(opaddr); break; michael@0: case 2: *(volatile unsigned char *)opaddr = 0; break; michael@0: default: abort(); michael@0: } michael@0: (void)scratch; michael@0: _exit(0); michael@0: } else { michael@0: int status; michael@0: if (waitpid(pid, &status, 0) != pid) { michael@0: printf("ERROR | %s %s | wait=%s\n", oplabel, pagelabel, michael@0: LastErrMsg()); michael@0: exit(2); michael@0: } michael@0: michael@0: if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { michael@0: if (should_succeed) { michael@0: printf("TEST-PASS | %s %s\n", oplabel, pagelabel); michael@0: } else { michael@0: printf("TEST-UNEXPECTED-FAIL | %s %s | unexpected successful exit\n", michael@0: oplabel, pagelabel); michael@0: failed = true; michael@0: } michael@0: } else if (WIFEXITED(status)) { michael@0: printf("ERROR | %s %s | unexpected exit code %d\n", michael@0: oplabel, pagelabel, WEXITSTATUS(status)); michael@0: exit(2); michael@0: } else if (WIFSIGNALED(status)) { michael@0: if (should_succeed) { michael@0: printf("TEST-UNEXPECTED-FAIL | %s %s | unexpected signal %d\n", michael@0: oplabel, pagelabel, WTERMSIG(status)); michael@0: failed = true; michael@0: } else { michael@0: printf("TEST-PASS | %s %s | signal %d (as expected)\n", michael@0: oplabel, pagelabel, WTERMSIG(status)); michael@0: } michael@0: } else { michael@0: printf("ERROR | %s %s | unexpected exit status %d\n", michael@0: oplabel, pagelabel, status); michael@0: exit(2); michael@0: } michael@0: } michael@0: #endif michael@0: } michael@0: return failed; michael@0: } michael@0: michael@0: int michael@0: main() michael@0: { michael@0: #ifdef _WIN32 michael@0: GetSystemInfo(&_sinfo); michael@0: #else michael@0: _pagesize = sysconf(_SC_PAGESIZE); michael@0: #endif michael@0: michael@0: uintptr_t ncontrol = ReserveNegativeControl(); michael@0: uintptr_t pcontrol = ReservePositiveControl(); michael@0: uintptr_t poison = ReservePoisonArea(); michael@0: michael@0: if (!ncontrol || !pcontrol || !poison) michael@0: return 2; michael@0: michael@0: bool failed = false; michael@0: failed |= TestPage("negative control", ncontrol, 1); michael@0: failed |= TestPage("positive control", pcontrol, 0); michael@0: failed |= TestPage("poison area", poison, 0); michael@0: michael@0: return failed ? 1 : 0; michael@0: }