tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }