mfbt/tests/TestPoisonArea.cpp

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

     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  */
     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  */
    82 #include "mozilla/IntegerPrintfMacros.h"
    83 #include "mozilla/NullPtr.h"
    85 // MAP_ANON(YMOUS) is not in any standard.  Add defines as necessary.
    86 #define _GNU_SOURCE 1
    87 #define _DARWIN_C_SOURCE 1
    89 #include <stddef.h>
    91 #include <errno.h>
    92 #include <stdio.h>
    93 #include <stdlib.h>
    94 #include <string.h>
    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>
   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
   116 #define SIZxPTR ((int)(sizeof(uintptr_t)*2))
   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  */
   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 */
   129 #elif defined __arm__ || defined _M_ARM
   130 #define RETURN_INSTR 0xE12FFF1E /* bx lr */
   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 */
   137 #elif defined __sparc || defined __sparcv9
   138 #define RETURN_INSTR 0x81c3e008 /* retl */
   140 #elif defined __alpha
   141 #define RETURN_INSTR 0x6bfa8001 /* ret */
   143 #elif defined __hppa
   144 #define RETURN_INSTR 0xe840c002 /* bv,n r0(rp) */
   146 #elif defined __mips
   147 #define RETURN_INSTR 0x03e00008 /* jr ra */
   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
   155 #elif defined __s390__
   156 #define RETURN_INSTR 0x07fe0000 /* br %r14 */
   158 #elif defined __aarch64__
   159 #define RETURN_INSTR 0xd65f03c0 /* ret */
   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 */
   166 #define RETURN_INSTR _return_instr
   167 #define RETURN_INSTR_TYPE ia64_instr
   169 #else
   170 #error "Need return instruction for this architecture"
   171 #endif
   173 #ifndef RETURN_INSTR_TYPE
   174 #define RETURN_INSTR_TYPE uint32_t
   175 #endif
   177 // Miscellaneous Windows/Unix portability gumph
   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);
   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()))
   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)
   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 }
   214 static void
   215 ReleaseRegion(void *page)
   216 {
   217   VirtualFree(page, PAGESIZE, MEM_RELEASE);
   218 }
   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 }
   231 static bool
   232 MakeRegionExecutable(void *)
   233 {
   234   return false;
   235 }
   237 #undef MAP_FAILED
   238 #define MAP_FAILED 0
   240 #else // Unix
   242 #define LastErrMsg() (strerror(errno))
   244 static unsigned long _pagesize;
   245 #define PAGESIZE _pagesize
   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 }
   255 static void
   256 ReleaseRegion(void *page)
   257 {
   258   munmap(page, PAGESIZE);
   259 }
   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 }
   271 static int
   272 MakeRegionExecutable(void *page)
   273 {
   274   return mprotect((caddr_t)page, PAGESIZE, PROT_READ|PROT_WRITE|PROT_EXEC);
   275 }
   277 #endif
   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     }
   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     }
   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     }
   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     }
   330     printf("ERROR | no usable poison area found\n");
   331     return 0;
   332   }
   333 }
   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 {
   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 }
   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   }
   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;
   370   // Now mark it executable as well as readable and writable.
   371   // (mmap(PROT_EXEC) may fail when applied to anonymous memory.)
   373   if (MakeRegionExecutable(result)) {
   374     printf("ERROR | making negative control executable | %s\n", LastErrMsg());
   375     return 0;
   376   }
   378   printf("INFO | negative control allocated at 0x%.*" PRIxPTR "\n",
   379          SIZxPTR, (uintptr_t)result);
   380   return (uintptr_t)result;
   381 }
   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 }
   397 #ifdef _WIN32
   398 static BOOL
   399 IsBadExecPtr(uintptr_t ptr)
   400 {
   401   BOOL ret = false;
   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
   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;
   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     }
   436 #ifdef _WIN32
   437     BOOL badptr;
   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     }
   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       }
   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 }
   518 int
   519 main()
   520 {
   521 #ifdef _WIN32
   522   GetSystemInfo(&_sinfo);
   523 #else
   524   _pagesize = sysconf(_SC_PAGESIZE);
   525 #endif
   527   uintptr_t ncontrol = ReserveNegativeControl();
   528   uintptr_t pcontrol = ReservePositiveControl();
   529   uintptr_t poison = ReservePoisonArea();
   531   if (!ncontrol || !pcontrol || !poison)
   532     return 2;
   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);
   539   return failed ? 1 : 0;
   540 }

mercurial