TestPoisonArea.cpp (18061B)
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 // MAP_ANON(YMOUS) is not in any standard. Add defines as necessary. 83 #define _GNU_SOURCE 1 84 #define _DARWIN_C_SOURCE 1 85 86 #include <errno.h> 87 #include <inttypes.h> 88 #include <stdio.h> 89 #include <stdlib.h> 90 #include <string.h> 91 92 #ifdef _WIN32 93 # include <windows.h> 94 #else 95 # include <sys/types.h> 96 # include <unistd.h> 97 # include <sys/wait.h> 98 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 108 109 #define SIZxPTR ((int)(sizeof(uintptr_t) * 2)) 110 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. 115 */ 116 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 */ 120 121 #elif defined __arm__ || defined _M_ARM 122 # define RETURN_INSTR 0xE12FFF1E /* bx lr */ 123 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 */ 128 129 #elif defined __m68k__ 130 # define RETURN_INSTR 0x4E754E75 /* rts; rts */ 131 132 #elif defined __riscv 133 # define RETURN_INSTR 0x80828082 /* ret; ret */ 134 135 #elif defined __sparc || defined __sparcv9 136 # define RETURN_INSTR 0x81c3e008 /* retl */ 137 138 #elif defined __alpha 139 # define RETURN_INSTR 0x6bfa8001 /* ret */ 140 141 #elif defined __hppa 142 # define RETURN_INSTR 0xe840c002 /* bv,n r0(rp) */ 143 144 #elif defined __mips 145 # define RETURN_INSTR 0x03e00008 /* jr ra */ 146 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 152 153 #elif defined __s390__ 154 # define RETURN_INSTR 0x07fe0000 /* br %r14 */ 155 156 #elif defined __sh__ 157 # define RETURN_INSTR 0x0b000b00 /* rts; rts */ 158 159 #elif defined __aarch64__ || defined _M_ARM64 160 # define RETURN_INSTR 0xd65f03c0 /* ret */ 161 162 #elif defined __loongarch64 163 # define RETURN_INSTR 0x4c000020 /* jirl zero, ra, 0 */ 164 165 #elif defined __ia64 166 struct ia64_instr { 167 uint32_t mI[4]; 168 }; 169 static const ia64_instr _return_instr = { 170 {0x00000011, 0x00000001, 0x80000200, 0x00840008}}; /* br.ret.sptk.many b0 */ 171 172 # define RETURN_INSTR _return_instr 173 # define RETURN_INSTR_TYPE ia64_instr 174 175 #else 176 # error "Need return instruction for this architecture" 177 #endif 178 179 #ifndef RETURN_INSTR_TYPE 180 # define RETURN_INSTR_TYPE uint32_t 181 #endif 182 183 // Miscellaneous Windows/Unix portability gumph 184 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); 193 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--; 198 } 199 errmsg[n + 1] = '\0'; 200 return errmsg; 201 } 202 # define LastErrMsg() (StrW32Error(GetLastError())) 203 204 // Because we use VirtualAlloc in MEM_RESERVE mode, the "page size" we want 205 // is the allocation granularity. 206 static SYSTEM_INFO sInfo_; 207 208 static inline uint32_t PageSize() { return sInfo_.dwAllocationGranularity; } 209 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); 214 } 215 216 static void ReleaseRegion(void* aPage) { 217 VirtualFree(aPage, PageSize(), MEM_RELEASE); 218 } 219 220 static bool ProbeRegion(uintptr_t aPage) { 221 return aPage >= (uintptr_t)sInfo_.lpMaximumApplicationAddress && 222 aPage + PageSize() >= (uintptr_t)sInfo_.lpMaximumApplicationAddress; 223 } 224 225 static bool MakeRegionExecutable(void*) { return false; } 226 227 # undef MAP_FAILED 228 # define MAP_FAILED 0 229 230 #else // Unix 231 232 # define LastErrMsg() (strerror(errno)) 233 234 static unsigned long gUnixPageSize; 235 236 static inline unsigned long PageSize() { return gUnixPageSize; } 237 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); 242 } 243 244 static void ReleaseRegion(void* aPage) { munmap(aPage, PageSize()); } 245 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 253 } 254 255 static int MakeRegionExecutable(void* aPage) { 256 return mprotect((caddr_t)aPage, PageSize(), 257 PROT_READ | PROT_WRITE | PROT_EXEC); 258 } 259 260 #endif 261 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; 272 } 273 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; 283 } 284 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); 291 } 292 printf("INFO | poison area assumed at 0x%.*" PRIxPTR " (preferred addr)\n", 293 SIZxPTR, candidate); 294 return candidate; 295 } 296 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; 305 } 306 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; 315 } 316 317 printf("ERROR | no usable poison area found\n"); 318 return 0; 319 } 320 321 /* The "positive control" area confirms that we can allocate a page with the 322 * proper characteristics. 323 */ 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; 329 } 330 printf("INFO | positive control allocated at 0x%.*" PRIxPTR "\n", SIZxPTR, 331 (uintptr_t)result); 332 return (uintptr_t)result; 333 } 334 335 /* The "negative control" area confirms that our probe logic does detect a 336 * page that is readable, writable, or executable. 337 */ 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; 343 } 344 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; 351 } 352 353 // Now mark it executable as well as readable and writable. 354 // (mmap(PROT_EXEC) may fail when applied to anonymous memory.) 355 356 if (MakeRegionExecutable(result)) { 357 ReleaseRegion(result); 358 return 0; 359 } 360 361 printf("INFO | negative control allocated at 0x%.*" PRIxPTR "\n", SIZxPTR, 362 (uintptr_t)result); 363 return (uintptr_t)result; 364 } 365 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, 374 }; 375 ((void (*)())&call)(); 376 # else 377 ((void (*)())aOpaddr)(); 378 # endif 379 } 380 #endif 381 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; 387 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(); 407 } 408 409 #ifdef _WIN32 410 bool badptr = true; 411 MEMORY_BASIC_INFORMATION mbi = {}; 412 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(); 429 } 430 } 431 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); 438 } 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; 446 } 447 } 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(); 467 } 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); 475 } 476 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; 484 } 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)); 497 } 498 } else { 499 printf("ERROR | %s %s | unexpected exit status %d\n", oplabel, 500 aPageLabel, status); 501 exit(2); 502 } 503 } 504 #endif 505 } 506 return failed; 507 } 508 509 int main() { 510 #ifdef _WIN32 511 GetSystemInfo(&sInfo_); 512 #else 513 gUnixPageSize = sysconf(_SC_PAGESIZE); 514 #endif 515 516 uintptr_t ncontrol = ReserveNegativeControl(); 517 if (!ncontrol) { 518 #if (defined __aarch64__ || defined _M_ARM64) && defined(XP_DARWIN) 519 // Apple silicon doesn't support W+X pages, so if we didn't manage to setup 520 // the negative page on Apple Silicon then skip that part of the test. 521 printf("TEST-SKIP | making negative control executable | %s\n", 522 LastErrMsg()); 523 #else 524 printf("ERROR | making negative control executable | %s\n", LastErrMsg()); 525 return 2; 526 #endif 527 } 528 529 uintptr_t pcontrol = ReservePositiveControl(); 530 uintptr_t poison = ReservePoisonArea(); 531 532 if (!pcontrol || !poison) { 533 return 2; 534 } 535 536 bool failed = false; 537 if (ncontrol) { 538 failed |= TestPage("negative control", ncontrol, 1); 539 } 540 failed |= TestPage("positive control", pcontrol, 0); 541 failed |= TestPage("poison area", poison, 0); 542 543 return failed ? 1 : 0; 544 }