tor-browser

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

ProcessExecutableMemory.cpp (32356B)


      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 #include "jit/ProcessExecutableMemory.h"
      8 
      9 #include "mozilla/Array.h"
     10 #include "mozilla/Atomics.h"
     11 #include "mozilla/DebugOnly.h"
     12 #include "mozilla/Maybe.h"
     13 #include "mozilla/PodOperations.h"
     14 #include "mozilla/TaggedAnonymousMemory.h"
     15 #include "mozilla/XorShift128PlusRNG.h"
     16 
     17 #include <errno.h>
     18 
     19 #include "jsfriendapi.h"
     20 #include "jsmath.h"
     21 
     22 #include "gc/Memory.h"
     23 #include "jit/FlushICache.h"  // js::jit::FlushICache
     24 #include "jit/JitOptions.h"
     25 #include "threading/LockGuard.h"
     26 #include "threading/Mutex.h"
     27 #include "util/Memory.h"
     28 #include "util/Poison.h"
     29 #include "util/WindowsWrapper.h"
     30 #include "vm/MutexIDs.h"
     31 
     32 #ifdef XP_WIN
     33 #  include "mozilla/StackWalk_windows.h"
     34 #elif defined(__wasi__)
     35 #  if defined(JS_CODEGEN_WASM32)
     36 #    include <cstdlib>
     37 #  else
     38 // Nothing.
     39 #  endif
     40 #else
     41 #  include <sys/mman.h>
     42 #  include <unistd.h>
     43 #endif
     44 
     45 #ifdef MOZ_VALGRIND
     46 #  include <valgrind/valgrind.h>
     47 #endif
     48 
     49 using namespace js;
     50 using namespace js::jit;
     51 
     52 #ifdef XP_WIN
     53 #  if defined(HAVE_64BIT_BUILD)
     54 #    define NEED_JIT_UNWIND_HANDLING
     55 #  endif
     56 
     57 static void* ComputeRandomAllocationAddress() {
     58  /*
     59   * Inspiration is V8's OS::Allocate in platform-win32.cc.
     60   *
     61   * VirtualAlloc takes 64K chunks out of the virtual address space, so we
     62   * keep 16b alignment.
     63   *
     64   * x86: V8 comments say that keeping addresses in the [64MiB, 1GiB) range
     65   * tries to avoid system default DLL mapping space. In the end, we get 13
     66   * bits of randomness in our selection.
     67   * x64: [2GiB, 4TiB), with 25 bits of randomness.
     68   */
     69 #  ifdef HAVE_64BIT_BUILD
     70  static const uintptr_t base = 0x0000000080000000;
     71  static const uintptr_t mask = 0x000003ffffff0000;
     72 #  elif defined(_M_IX86) || defined(__i386__)
     73  static const uintptr_t base = 0x04000000;
     74  static const uintptr_t mask = 0x3fff0000;
     75 #  else
     76 #    error "Unsupported architecture"
     77 #  endif
     78 
     79  uint64_t rand = js::GenerateRandomSeed();
     80  return (void*)(base | (rand & mask));
     81 }
     82 
     83 #  ifdef NEED_JIT_UNWIND_HANDLING
     84 static js::JitExceptionHandler sJitExceptionHandler;
     85 static bool sHasInstalledFunctionTable = false;
     86 #  endif
     87 
     88 JS_PUBLIC_API void js::SetJitExceptionHandler(JitExceptionHandler handler) {
     89 #  ifdef NEED_JIT_UNWIND_HANDLING
     90  MOZ_ASSERT(!sJitExceptionHandler);
     91  sJitExceptionHandler = handler;
     92 #  else
     93  // Just do nothing if unwind handling is disabled.
     94 #  endif
     95 }
     96 
     97 #  ifdef NEED_JIT_UNWIND_HANDLING
     98 #    if defined(_M_ARM64)
     99 // See the ".xdata records" section of
    100 // https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling
    101 // These records can have various fields present or absent depending on the
    102 // bits set in the header. Our struct will use one 32-bit slot for unwind codes,
    103 // and no slots for epilog scopes.
    104 struct UnwindData {
    105  uint32_t functionLength : 18;
    106  uint32_t version : 2;
    107  uint32_t hasExceptionHandler : 1;
    108  uint32_t packedEpilog : 1;
    109  uint32_t epilogCount : 5;
    110  uint32_t codeWords : 5;
    111  uint8_t unwindCodes[4];
    112  uint32_t exceptionHandler;
    113 };
    114 
    115 static const unsigned ThunkLength = 20;
    116 #    else
    117 // From documentation for UNWIND_INFO on
    118 // https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64
    119 struct UnwindInfo {
    120  uint8_t version : 3;
    121  uint8_t flags : 5;
    122  uint8_t sizeOfPrologue;
    123  uint8_t countOfUnwindCodes;
    124  uint8_t frameRegister : 4;
    125  uint8_t frameOffset : 4;
    126 };
    127 static const unsigned ThunkLength = 12;
    128 union UnwindCode {
    129  struct {
    130    uint8_t codeOffset;
    131    uint8_t unwindOp : 4;
    132    uint8_t opInfo : 4;
    133  };
    134  uint16_t frameOffset;
    135 };
    136 
    137 static constexpr int kNumberOfUnwindCodes = 2;
    138 static constexpr int kPushRbpInstructionLength = 1;
    139 static constexpr int kMovRbpRspInstructionLength = 3;
    140 static constexpr int kRbpPrefixCodes = 2;
    141 static constexpr int kRbpPrefixLength =
    142    kPushRbpInstructionLength + kMovRbpRspInstructionLength;
    143 
    144 struct UnwindData {
    145  UnwindInfo unwindInfo;
    146  UnwindCode unwindCodes[kNumberOfUnwindCodes];
    147  uint32_t exceptionHandler;
    148 
    149  UnwindData() {
    150    static constexpr int kOpPushNonvol = 0;
    151    static constexpr int kOpSetFPReg = 3;
    152 
    153    unwindInfo.version = 1;
    154    unwindInfo.flags = UNW_FLAG_EHANDLER;
    155    unwindInfo.sizeOfPrologue = kRbpPrefixLength;
    156    unwindInfo.countOfUnwindCodes = kRbpPrefixCodes;
    157    unwindInfo.frameRegister = 5;
    158    unwindInfo.frameOffset = 0;
    159 
    160    // Offset here are specified to beginning of the -next- instruction.
    161    unwindCodes[0].codeOffset = kRbpPrefixLength;  // movq rbp, rsp
    162    unwindCodes[0].unwindOp = kOpSetFPReg;
    163    unwindCodes[0].opInfo = 0;
    164 
    165    unwindCodes[1].codeOffset = kPushRbpInstructionLength;  // push rbp
    166    unwindCodes[1].unwindOp = kOpPushNonvol;
    167    unwindCodes[1].opInfo = 5;
    168  }
    169 };
    170 #    endif
    171 
    172 struct ExceptionHandlerRecord {
    173  void* dynamicTable;
    174  UnwindData unwindData;
    175  uint8_t thunk[ThunkLength];
    176  RUNTIME_FUNCTION runtimeFunction;
    177 };
    178 
    179 // This function must match the function pointer type PEXCEPTION_HANDLER
    180 // mentioned in:
    181 //   http://msdn.microsoft.com/en-us/library/ssa62fwe.aspx.
    182 // This type is rather elusive in documentation; Wine is the best I've found:
    183 //   http://source.winehq.org/source/include/winnt.h
    184 static DWORD ExceptionHandler(PEXCEPTION_RECORD exceptionRecord,
    185                              _EXCEPTION_REGISTRATION_RECORD*, PCONTEXT context,
    186                              _EXCEPTION_REGISTRATION_RECORD**) {
    187  if (sJitExceptionHandler) {
    188    return sJitExceptionHandler(exceptionRecord, context);
    189  }
    190 
    191  return ExceptionContinueSearch;
    192 }
    193 
    194 // Required for enabling Stackwalking on windows using external tools.
    195 extern "C" NTSYSAPI DWORD NTAPI RtlAddGrowableFunctionTable(
    196    PVOID* DynamicTable, PRUNTIME_FUNCTION FunctionTable, DWORD EntryCount,
    197    DWORD MaximumEntryCount, ULONG_PTR RangeBase, ULONG_PTR RangeEnd);
    198 
    199 // For an explanation of the problem being solved here, see
    200 // SetJitExceptionFilter in jsfriendapi.h.
    201 static bool RegisterExecutableMemory(void* p, size_t bytes, size_t pageSize) {
    202  if (!VirtualAlloc(p, pageSize, MEM_COMMIT, PAGE_READWRITE)) {
    203    MOZ_CRASH();
    204  }
    205 
    206  // A page was reserved inside this structure for the record. This is because
    207  // all entries in the record are describes as an offset from the start of the
    208  // memory region. We construct the record there.
    209  ExceptionHandlerRecord* r = new (p) ExceptionHandlerRecord();
    210  void* handler = JS_FUNC_TO_DATA_PTR(void*, ExceptionHandler);
    211 
    212  // Because the .xdata format on ARM64 can only encode sizes up to 1M (much
    213  // too small for our JIT code regions), we register a function table callback
    214  // to provide RUNTIME_FUNCTIONs at runtime. Windows doesn't seem to care about
    215  // the size fields on RUNTIME_FUNCTIONs that are created in this way, so the
    216  // same RUNTIME_FUNCTION can work for any address in the region. We'll set up
    217  // a generic one now and the callback can just return a pointer to it.
    218 
    219  // All these fields are specified to be offsets from the base of the
    220  // executable code (which is 'p'), even if they have 'Address' in their
    221  // names. In particular, exceptionHandler is a ULONG offset which is a
    222  // 32-bit integer. Since 'p' can be farther than INT32_MAX away from
    223  // sJitExceptionHandler, we must generate a little thunk inside the
    224  // record. The record is put on its own page so that we can take away write
    225  // access to protect against accidental clobbering.
    226 
    227 #    if defined(_M_ARM64)
    228  if (!sJitExceptionHandler) {
    229    return false;
    230  }
    231 
    232  r->runtimeFunction.BeginAddress = pageSize;
    233  r->runtimeFunction.UnwindData = offsetof(ExceptionHandlerRecord, unwindData);
    234  static_assert(offsetof(ExceptionHandlerRecord, unwindData) % 4 == 0,
    235                "The ARM64 .pdata format requires that exception information "
    236                "RVAs be 4-byte aligned.");
    237 
    238  memset(&r->unwindData, 0, sizeof(r->unwindData));
    239  r->unwindData.hasExceptionHandler = true;
    240  r->unwindData.exceptionHandler = offsetof(ExceptionHandlerRecord, thunk);
    241 
    242  // Use a fake unwind code to make the Windows unwinder do _something_. If the
    243  // PC and SP both stay unchanged, we'll fail the unwinder's sanity checks and
    244  // it won't call our exception handler.
    245  r->unwindData.codeWords = 1;  // one 32-bit word gives us up to 4 codes
    246  r->unwindData.unwindCodes[0] =
    247      0b00000001;  // alloc_s small stack of size 1*16
    248  r->unwindData.unwindCodes[1] = 0b11100100;  // end
    249 
    250  uint32_t* thunk = (uint32_t*)r->thunk;
    251  uint16_t* addr = (uint16_t*)&handler;
    252 
    253  // xip0/r16 should be safe to clobber: Windows just used it to call our thunk.
    254  const uint8_t reg = 16;
    255 
    256  // Say `handler` is 0x4444333322221111, then:
    257  thunk[0] = 0xd2800000 | addr[0] << 5 | reg;  // mov  xip0, 1111
    258  thunk[1] = 0xf2a00000 | addr[1] << 5 | reg;  // movk xip0, 2222 lsl #0x10
    259  thunk[2] = 0xf2c00000 | addr[2] << 5 | reg;  // movk xip0, 3333 lsl #0x20
    260  thunk[3] = 0xf2e00000 | addr[3] << 5 | reg;  // movk xip0, 4444 lsl #0x30
    261  thunk[4] = 0xd61f0000 | reg << 5;            // br xip0
    262 #    else
    263  r->runtimeFunction.BeginAddress = pageSize;
    264  r->runtimeFunction.EndAddress = (DWORD)bytes;
    265  r->runtimeFunction.UnwindData = offsetof(ExceptionHandlerRecord, unwindData);
    266  r->unwindData.exceptionHandler = offsetof(ExceptionHandlerRecord, thunk);
    267 
    268  // mov imm64, rax
    269  r->thunk[0] = 0x48;
    270  r->thunk[1] = 0xb8;
    271  memcpy(&r->thunk[2], &handler, 8);
    272 
    273  // jmp rax
    274  r->thunk[10] = 0xff;
    275  r->thunk[11] = 0xe0;
    276 #    endif
    277 
    278  // RtlAddGrowableFunctionTable will write into the region. We must therefore
    279  // only write-protect is after this has been called.
    280 
    281  // XXX NB: The profiler believes this function is only called from the main
    282  // thread. If that ever becomes untrue, the profiler must be updated
    283  // immediately.
    284  {
    285    AutoSuppressStackWalking suppress;
    286    DWORD result = RtlAddGrowableFunctionTable(
    287        &r->dynamicTable, &r->runtimeFunction, 1, 1, (ULONG_PTR)p,
    288        (ULONG_PTR)p + bytes - pageSize);
    289    if (result != S_OK) {
    290      return false;
    291    }
    292  }
    293 
    294  DWORD oldProtect;
    295  if (!VirtualProtect(p, pageSize, PAGE_EXECUTE_READ, &oldProtect)) {
    296    MOZ_CRASH();
    297  }
    298 
    299  return true;
    300 }
    301 
    302 static void UnregisterExecutableMemory(void* p, size_t bytes, size_t pageSize) {
    303  // There's no such thing as RtlUninstallFunctionTableCallback, so there's
    304  // nothing to do here.
    305 }
    306 #  endif
    307 
    308 static void* ReserveProcessExecutableMemory(size_t bytes) {
    309 #  ifdef NEED_JIT_UNWIND_HANDLING
    310  size_t pageSize = gc::SystemPageSize();
    311  // Always reserve space for the unwind information.
    312  bytes += pageSize;
    313 #  endif
    314 
    315  void* p = nullptr;
    316  for (size_t i = 0; i < 10; i++) {
    317    void* randomAddr = ComputeRandomAllocationAddress();
    318    p = VirtualAlloc(randomAddr, bytes, MEM_RESERVE, PAGE_NOACCESS);
    319    if (p) {
    320      break;
    321    }
    322  }
    323 
    324  if (!p) {
    325    // Try again without randomization.
    326    p = VirtualAlloc(nullptr, bytes, MEM_RESERVE, PAGE_NOACCESS);
    327    if (!p) {
    328      return nullptr;
    329    }
    330  }
    331 
    332 #  ifdef NEED_JIT_UNWIND_HANDLING
    333  if (RegisterExecutableMemory(p, bytes, pageSize)) {
    334    sHasInstalledFunctionTable = true;
    335  } else {
    336    if (sJitExceptionHandler) {
    337      // This should have succeeded if we have an exception handler. Bail.
    338      VirtualFree(p, 0, MEM_RELEASE);
    339      return nullptr;
    340    }
    341  }
    342 
    343  // Skip the first page where we might have allocated an exception handler
    344  // record.
    345  p = (uint8_t*)p + pageSize;
    346  bytes -= pageSize;
    347 
    348  RegisterJitCodeRegion((uint8_t*)p, bytes);
    349 #  endif
    350  return p;
    351 }
    352 
    353 static void DeallocateProcessExecutableMemory(void* addr, size_t bytes) {
    354 #  ifdef NEED_JIT_UNWIND_HANDLING
    355  UnregisterJitCodeRegion((uint8_t*)addr, bytes);
    356 
    357  size_t pageSize = gc::SystemPageSize();
    358  addr = (uint8_t*)addr - pageSize;
    359 
    360  if (sHasInstalledFunctionTable) {
    361    UnregisterExecutableMemory(addr, bytes, pageSize);
    362  }
    363 #  endif
    364 
    365  VirtualFree(addr, 0, MEM_RELEASE);
    366 }
    367 
    368 static DWORD ProtectionSettingToFlags(ProtectionSetting protection) {
    369  if (!JitOptions.writeProtectCode) {
    370    return PAGE_EXECUTE_READWRITE;
    371  }
    372  switch (protection) {
    373    case ProtectionSetting::Writable:
    374      return PAGE_READWRITE;
    375    case ProtectionSetting::Executable:
    376      return PAGE_EXECUTE_READ;
    377  }
    378  MOZ_CRASH();
    379 }
    380 
    381 [[nodiscard]] static bool CommitPages(void* addr, size_t bytes,
    382                                      ProtectionSetting protection) {
    383  void* p = VirtualAlloc(addr, bytes, MEM_COMMIT,
    384                         ProtectionSettingToFlags(protection));
    385  if (!p) {
    386    return false;
    387  }
    388  MOZ_RELEASE_ASSERT(p == addr);
    389  return true;
    390 }
    391 
    392 static void DecommitPages(void* addr, size_t bytes) {
    393  if (!VirtualFree(addr, bytes, MEM_DECOMMIT)) {
    394    MOZ_CRASH("DecommitPages failed");
    395  }
    396 }
    397 #elif defined(__wasi__)
    398 #  if defined(JS_CODEGEN_WASM32)
    399 static void* ReserveProcessExecutableMemory(size_t bytes) {
    400  return malloc(bytes);
    401 }
    402 
    403 static void DeallocateProcessExecutableMemory(void* addr, size_t bytes) {
    404  free(addr);
    405 }
    406 
    407 [[nodiscard]] static bool CommitPages(void* addr, size_t bytes,
    408                                      ProtectionSetting protection) {
    409  return true;
    410 }
    411 
    412 static void DecommitPages(void* addr, size_t bytes) {}
    413 
    414 #  else
    415 static void* ReserveProcessExecutableMemory(size_t bytes) {
    416  MOZ_CRASH("NYI for WASI.");
    417  return nullptr;
    418 }
    419 static void DeallocateProcessExecutableMemory(void* addr, size_t bytes) {
    420  MOZ_CRASH("NYI for WASI.");
    421 }
    422 [[nodiscard]] static bool CommitPages(void* addr, size_t bytes,
    423                                      ProtectionSetting protection) {
    424  MOZ_CRASH("NYI for WASI.");
    425  return false;
    426 }
    427 static void DecommitPages(void* addr, size_t bytes) {
    428  MOZ_CRASH("NYI for WASI.");
    429 }
    430 #  endif
    431 #else  // !XP_WIN && !__wasi__
    432 #  ifndef MAP_NORESERVE
    433 #    define MAP_NORESERVE 0
    434 #  endif
    435 
    436 static void* ComputeRandomAllocationAddress() {
    437 #  ifdef __OpenBSD__
    438  // OpenBSD already has random mmap and the idea that all x64 cpus
    439  // have 48-bit address space is not correct. Returning nullptr
    440  // allows OpenBSD do to the right thing.
    441  return nullptr;
    442 #  else
    443  uint64_t rand = js::GenerateRandomSeed();
    444 
    445 #    ifdef HAVE_64BIT_BUILD
    446  // x64 CPUs have a 48-bit address space and on some platforms the OS will
    447  // give us access to 47 bits, so to be safe we right shift by 18 to leave
    448  // 46 bits.
    449  rand >>= 18;
    450 #    else
    451  // On 32-bit, right shift by 34 to leave 30 bits, range [0, 1GiB). Then add
    452  // 512MiB to get range [512MiB, 1.5GiB), or [0x20000000, 0x60000000). This
    453  // is based on V8 comments in platform-posix.cc saying this range is
    454  // relatively unpopulated across a variety of kernels.
    455  rand >>= 34;
    456  rand += 512 * 1024 * 1024;
    457 #    endif
    458 
    459  // Ensure page alignment.
    460  uintptr_t mask = ~uintptr_t(gc::SystemPageSize() - 1);
    461  return (void*)uintptr_t(rand & mask);
    462 #  endif
    463 }
    464 
    465 static void DecommitPages(void* addr, size_t bytes);
    466 
    467 static void* ReserveProcessExecutableMemory(size_t bytes) {
    468  // On most Unix platforms our strategy is as follows:
    469  //
    470  // * Reserve:  mmap with PROT_NONE
    471  // * Commit:   mmap with MAP_FIXED, PROT_READ | ...
    472  // * Decommit: mmap with MAP_FIXED, PROT_NONE
    473  //
    474  // On Apple Silicon this only works if we use mprotect to implement W^X. To
    475  // use RWX pages with the faster pthread_jit_write_protect_np API for
    476  // thread-local writable/executable switching, the kernel enforces the
    477  // following rules:
    478  //
    479  // * The initial mmap must be called with MAP_JIT.
    480  // * MAP_FIXED can't be used with MAP_JIT.
    481  // * Since macOS 11.2, mprotect can't be used to change permissions of RWX JIT
    482  //   pages (even PROT_NONE fails).
    483  //   See https://developer.apple.com/forums/thread/672804.
    484  //
    485  // This means we have to use the following strategy on Apple Silicon:
    486  //
    487  // * Reserve:  1) mmap with PROT_READ | PROT_WRITE | PROT_EXEC and MAP_JIT
    488  //             2) decommit
    489  // * Commit:   madvise with MADV_FREE_REUSE
    490  // * Decommit: madvise with MADV_FREE_REUSABLE
    491  //
    492  // On Intel Macs we also need to use MAP_JIT, to be compatible with the
    493  // Hardened Runtime (with com.apple.security.cs.allow-jit = true). The
    494  // pthread_jit_write_protect_np API is not available on Intel and MAP_JIT
    495  // can't be used with MAP_FIXED, so we have to use a hybrid of the above two
    496  // strategies:
    497  //
    498  // * Reserve:  1) mmap with PROT_NONE and MAP_JIT
    499  //             2) decommit
    500  // * Commit:   1) madvise with MADV_FREE_REUSE
    501  //             2) mprotect with PROT_READ | ...
    502  // * Decommit: 1) mprotect with PROT_NONE
    503  //             2) madvise with MADV_FREE_REUSABLE
    504  //
    505  // This is inspired by V8's code in OS::SetPermissions.
    506 
    507  // Note that randomAddr is just a hint: if the address is not available
    508  // mmap will pick a different address.
    509  void* randomAddr = ComputeRandomAllocationAddress();
    510  unsigned protection = PROT_NONE;
    511  unsigned flags = MAP_NORESERVE | MAP_PRIVATE | MAP_ANON;
    512 #  if defined(XP_DARWIN)
    513  flags |= MAP_JIT;
    514 #    if defined(JS_USE_APPLE_FAST_WX)
    515  protection = PROT_READ | PROT_WRITE | PROT_EXEC;
    516 #    endif
    517 #  endif
    518  void* p = MozTaggedAnonymousMmap(randomAddr, bytes, protection, flags, -1, 0,
    519                                   "js-executable-memory");
    520  if (p == MAP_FAILED) {
    521    return nullptr;
    522  }
    523 #  if defined(XP_DARWIN)
    524  DecommitPages(p, bytes);
    525 #  endif
    526  return p;
    527 }
    528 
    529 static void DeallocateProcessExecutableMemory(void* addr, size_t bytes) {
    530  mozilla::DebugOnly<int> result = munmap(addr, bytes);
    531  MOZ_ASSERT(!result || errno == ENOMEM);
    532 }
    533 
    534 static unsigned ProtectionSettingToFlags(ProtectionSetting protection) {
    535  if (!JitOptions.writeProtectCode) {
    536    return PROT_READ | PROT_WRITE | PROT_EXEC;
    537  }
    538 #  ifdef MOZ_VALGRIND
    539  // If we're configured for Valgrind and running on it, use a slacker
    540  // scheme that doesn't change execute permissions, since doing so causes
    541  // Valgrind a lot of extra overhead re-JITting code that loses and later
    542  // regains execute permission.  See bug 1338179.
    543  if (RUNNING_ON_VALGRIND) {
    544    switch (protection) {
    545      case ProtectionSetting::Writable:
    546        return PROT_READ | PROT_WRITE | PROT_EXEC;
    547      case ProtectionSetting::Executable:
    548        return PROT_READ | PROT_EXEC;
    549    }
    550    MOZ_CRASH();
    551  }
    552  // If we get here, we're configured for Valgrind but not running on
    553  // it, so use the standard scheme.
    554 #  endif
    555  switch (protection) {
    556    case ProtectionSetting::Writable:
    557      return PROT_READ | PROT_WRITE;
    558    case ProtectionSetting::Executable:
    559      return PROT_READ | PROT_EXEC;
    560  }
    561  MOZ_CRASH();
    562 }
    563 
    564 [[nodiscard]] static bool CommitPages(void* addr, size_t bytes,
    565                                      ProtectionSetting protection) {
    566  // See the comment in ReserveProcessExecutableMemory.
    567 #  if defined(XP_DARWIN)
    568  int ret;
    569  do {
    570    ret = madvise(addr, bytes, MADV_FREE_REUSE);
    571  } while (ret != 0 && errno == EAGAIN);
    572  if (ret != 0) {
    573    return false;
    574  }
    575 #    if !defined(JS_USE_APPLE_FAST_WX)
    576  unsigned flags = ProtectionSettingToFlags(protection);
    577  if (mprotect(addr, bytes, flags)) {
    578    return false;
    579  }
    580 #    endif
    581  return true;
    582 #  else
    583  unsigned flags = ProtectionSettingToFlags(protection);
    584  void* p = MozTaggedAnonymousMmap(addr, bytes, flags,
    585                                   MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0,
    586                                   "js-executable-memory");
    587  if (p == MAP_FAILED) {
    588    return false;
    589  }
    590  MOZ_RELEASE_ASSERT(p == addr);
    591  return true;
    592 #  endif
    593 }
    594 
    595 static void DecommitPages(void* addr, size_t bytes) {
    596  // See the comment in ReserveProcessExecutableMemory.
    597 #  if defined(XP_DARWIN)
    598  int ret;
    599 #    if !defined(JS_USE_APPLE_FAST_WX)
    600  ret = mprotect(addr, bytes, PROT_NONE);
    601  MOZ_RELEASE_ASSERT(ret == 0);
    602 #    endif
    603  do {
    604    ret = madvise(addr, bytes, MADV_FREE_REUSABLE);
    605  } while (ret != 0 && errno == EAGAIN);
    606  MOZ_RELEASE_ASSERT(ret == 0);
    607 #  else
    608  // Use mmap with MAP_FIXED and PROT_NONE. Inspired by jemalloc's
    609  // pages_decommit.
    610  void* p = MozTaggedAnonymousMmap(addr, bytes, PROT_NONE,
    611                                   MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0,
    612                                   "js-executable-memory");
    613  MOZ_RELEASE_ASSERT(addr == p);
    614 #  endif
    615 }
    616 #endif
    617 
    618 template <size_t NumBits>
    619 class PageBitSet {
    620  using WordType = uint32_t;
    621  static const size_t BitsPerWord = sizeof(WordType) * 8;
    622 
    623  static_assert((NumBits % BitsPerWord) == 0,
    624                "NumBits must be a multiple of BitsPerWord");
    625  static const size_t NumWords = NumBits / BitsPerWord;
    626 
    627  mozilla::Array<WordType, NumWords> words_;
    628 
    629  uint32_t indexToWord(uint32_t index) const {
    630    MOZ_ASSERT(index < NumBits);
    631    return index / BitsPerWord;
    632  }
    633  WordType indexToBit(uint32_t index) const {
    634    MOZ_ASSERT(index < NumBits);
    635    return WordType(1) << (index % BitsPerWord);
    636  }
    637 
    638 public:
    639  void init() { mozilla::PodArrayZero(words_); }
    640  bool contains(size_t index) const {
    641    uint32_t word = indexToWord(index);
    642    return words_[word] & indexToBit(index);
    643  }
    644  void insert(size_t index) {
    645    MOZ_ASSERT(!contains(index));
    646    uint32_t word = indexToWord(index);
    647    words_[word] |= indexToBit(index);
    648  }
    649  void remove(size_t index) {
    650    MOZ_ASSERT(contains(index));
    651    uint32_t word = indexToWord(index);
    652    words_[word] &= ~indexToBit(index);
    653  }
    654 
    655 #ifdef DEBUG
    656  bool empty() const {
    657    for (size_t i = 0; i < NumWords; i++) {
    658      if (words_[i] != 0) {
    659        return false;
    660      }
    661    }
    662    return true;
    663  }
    664 #endif
    665 };
    666 
    667 // Per-process executable memory allocator. It reserves a block of memory of
    668 // MaxCodeBytesPerProcess bytes, then allocates/deallocates pages from that.
    669 //
    670 // This has a number of benefits compared to raw mmap/VirtualAlloc:
    671 //
    672 // * More resillient against certain attacks.
    673 //
    674 // * Behaves more consistently across platforms: it avoids the 64K granularity
    675 //   issues on Windows, for instance.
    676 //
    677 // * On x64, near jumps can be used for jumps to other JIT pages.
    678 //
    679 // * On Win64, we have to register the exception handler only once (at process
    680 //   startup). This saves some memory and avoids RtlAddFunctionTable profiler
    681 //   deadlocks.
    682 class ProcessExecutableMemory {
    683  static_assert(
    684      (MaxCodeBytesPerProcess % ExecutableCodePageSize) == 0,
    685      "MaxCodeBytesPerProcess must be a multiple of ExecutableCodePageSize");
    686  static const size_t MaxCodePages =
    687      MaxCodeBytesPerProcess / ExecutableCodePageSize;
    688 
    689  // Start of the MaxCodeBytesPerProcess memory block or nullptr if
    690  // uninitialized. Note that this is NOT guaranteed to be aligned to
    691  // ExecutableCodePageSize.
    692  uint8_t* base_;
    693 
    694  // The fields below should only be accessed while we hold the lock.
    695  Mutex lock_ MOZ_UNANNOTATED;
    696 
    697  // pagesAllocated_ is an Atomic so that bytesAllocated does not have to
    698  // take the lock.
    699  mozilla::Atomic<size_t, mozilla::ReleaseAcquire> pagesAllocated_;
    700 
    701  // Page where we should try to allocate next.
    702  size_t cursor_;
    703 
    704  mozilla::Maybe<mozilla::non_crypto::XorShift128PlusRNG> rng_;
    705  PageBitSet<MaxCodePages> pages_;
    706 
    707 public:
    708  ProcessExecutableMemory()
    709      : base_(nullptr),
    710        lock_(mutexid::ProcessExecutableRegion),
    711        pagesAllocated_(0),
    712        cursor_(0),
    713        pages_() {}
    714 
    715  [[nodiscard]] bool init() {
    716    pages_.init();
    717 
    718    MOZ_RELEASE_ASSERT(!initialized());
    719    MOZ_RELEASE_ASSERT(HasJitBackend());
    720    MOZ_RELEASE_ASSERT(gc::SystemPageSize() <= ExecutableCodePageSize);
    721 
    722    void* p = ReserveProcessExecutableMemory(MaxCodeBytesPerProcess);
    723    if (!p) {
    724      return false;
    725    }
    726 
    727    base_ = static_cast<uint8_t*>(p);
    728 
    729    mozilla::Array<uint64_t, 2> seed;
    730    GenerateXorShift128PlusSeed(seed);
    731    rng_.emplace(seed[0], seed[1]);
    732    return true;
    733  }
    734 
    735  uint8_t* base() const { return base_; }
    736 
    737  bool initialized() const { return base_ != nullptr; }
    738 
    739  size_t bytesAllocated() const {
    740    MOZ_ASSERT(pagesAllocated_ <= MaxCodePages);
    741    return pagesAllocated_ * ExecutableCodePageSize;
    742  }
    743 
    744  void release() {
    745    MOZ_ASSERT(initialized());
    746    MOZ_ASSERT(pages_.empty());
    747    MOZ_ASSERT(pagesAllocated_ == 0);
    748    DeallocateProcessExecutableMemory(base_, MaxCodeBytesPerProcess);
    749    base_ = nullptr;
    750    rng_.reset();
    751    MOZ_ASSERT(!initialized());
    752  }
    753 
    754  void assertValidAddress(void* p, size_t bytes) const {
    755    MOZ_RELEASE_ASSERT(p >= base_ &&
    756                       uintptr_t(p) + bytes <=
    757                           uintptr_t(base_) + MaxCodeBytesPerProcess);
    758  }
    759 
    760  bool containsAddress(const void* p) const {
    761    return p >= base_ &&
    762           uintptr_t(p) < uintptr_t(base_) + MaxCodeBytesPerProcess;
    763  }
    764 
    765  void* allocate(size_t bytes, ProtectionSetting protection,
    766                 MemCheckKind checkKind);
    767  void deallocate(void* addr, size_t bytes, bool decommit);
    768 };
    769 
    770 void* ProcessExecutableMemory::allocate(size_t bytes,
    771                                        ProtectionSetting protection,
    772                                        MemCheckKind checkKind) {
    773  MOZ_ASSERT(initialized());
    774  MOZ_ASSERT(HasJitBackend());
    775  MOZ_ASSERT(bytes > 0);
    776  MOZ_ASSERT((bytes % ExecutableCodePageSize) == 0);
    777 
    778  size_t numPages = bytes / ExecutableCodePageSize;
    779 
    780  // Take the lock and try to allocate.
    781  void* p = nullptr;
    782  {
    783    LockGuard<Mutex> guard(lock_);
    784    MOZ_ASSERT(pagesAllocated_ <= MaxCodePages);
    785 
    786    // Check if we have enough pages available.
    787    if (pagesAllocated_ + numPages >= MaxCodePages) {
    788      return nullptr;
    789    }
    790 
    791    MOZ_ASSERT(bytes <= MaxCodeBytesPerProcess);
    792 
    793    // Maybe skip a page to make allocations less predictable.
    794    size_t page = cursor_ + (rng_.ref().next() % 2);
    795 
    796    for (size_t i = 0; i < MaxCodePages; i++) {
    797      // Make sure page + numPages - 1 is a valid index.
    798      if (page + numPages > MaxCodePages) {
    799        page = 0;
    800      }
    801 
    802      bool available = true;
    803      for (size_t j = 0; j < numPages; j++) {
    804        if (pages_.contains(page + j)) {
    805          available = false;
    806          break;
    807        }
    808      }
    809      if (!available) {
    810        page++;
    811        continue;
    812      }
    813 
    814      // Mark the pages as unavailable.
    815      for (size_t j = 0; j < numPages; j++) {
    816        pages_.insert(page + j);
    817      }
    818 
    819      pagesAllocated_ += numPages;
    820      MOZ_ASSERT(pagesAllocated_ <= MaxCodePages);
    821 
    822      // If we allocated a small number of pages, move cursor_ to the
    823      // next page. We don't do this for larger allocations to avoid
    824      // skipping a large number of small holes.
    825      if (numPages <= 2) {
    826        cursor_ = page + numPages;
    827      }
    828 
    829      p = base_ + page * ExecutableCodePageSize;
    830      break;
    831    }
    832    if (!p) {
    833      return nullptr;
    834    }
    835  }
    836 
    837  // Commit the pages after releasing the lock.
    838  if (!CommitPages(p, bytes, protection)) {
    839    deallocate(p, bytes, /* decommit = */ false);
    840    return nullptr;
    841  }
    842 
    843 #if !defined(__wasi__)
    844  gc::RecordMemoryAlloc(bytes);
    845 #endif
    846 
    847  SetMemCheckKind(p, bytes, checkKind);
    848 
    849  return p;
    850 }
    851 
    852 void ProcessExecutableMemory::deallocate(void* addr, size_t bytes,
    853                                         bool decommit) {
    854  MOZ_ASSERT(initialized());
    855  MOZ_ASSERT(addr);
    856  MOZ_ASSERT((uintptr_t(addr) % gc::SystemPageSize()) == 0);
    857  MOZ_ASSERT(bytes > 0);
    858  MOZ_ASSERT((bytes % ExecutableCodePageSize) == 0);
    859 
    860  assertValidAddress(addr, bytes);
    861 
    862  size_t firstPage =
    863      (static_cast<uint8_t*>(addr) - base_) / ExecutableCodePageSize;
    864  size_t numPages = bytes / ExecutableCodePageSize;
    865 
    866  // Decommit before taking the lock.
    867  MOZ_MAKE_MEM_NOACCESS(addr, bytes);
    868  if (decommit) {
    869    DecommitPages(addr, bytes);
    870 #if !defined(__wasi__)
    871    gc::RecordMemoryFree(bytes);
    872 #endif
    873  }
    874 
    875  LockGuard<Mutex> guard(lock_);
    876  MOZ_ASSERT(numPages <= pagesAllocated_);
    877  pagesAllocated_ -= numPages;
    878 
    879  for (size_t i = 0; i < numPages; i++) {
    880    pages_.remove(firstPage + i);
    881  }
    882 
    883  // Move the cursor back so we can reuse pages instead of fragmenting the
    884  // whole region.
    885  if (firstPage < cursor_) {
    886    cursor_ = firstPage;
    887  }
    888 }
    889 
    890 MOZ_RUNINIT static ProcessExecutableMemory execMemory;
    891 
    892 void* js::jit::AllocateExecutableMemory(size_t bytes,
    893                                        ProtectionSetting protection,
    894                                        MemCheckKind checkKind) {
    895  return execMemory.allocate(bytes, protection, checkKind);
    896 }
    897 
    898 void js::jit::DeallocateExecutableMemory(void* addr, size_t bytes) {
    899  execMemory.deallocate(addr, bytes, /* decommit = */ true);
    900 }
    901 
    902 bool js::jit::InitProcessExecutableMemory() { return execMemory.init(); }
    903 
    904 void js::jit::ReleaseProcessExecutableMemory() { execMemory.release(); }
    905 
    906 size_t js::jit::LikelyAvailableExecutableMemory() {
    907  // Round down available memory to the closest MB.
    908  return MaxCodeBytesPerProcess -
    909         AlignBytes(execMemory.bytesAllocated(), 0x100000U);
    910 }
    911 
    912 bool js::jit::CanLikelyAllocateMoreExecutableMemory() {
    913  // Use a 8 MB buffer.
    914  static const size_t BufferSize = 8 * 1024 * 1024;
    915 
    916  MOZ_ASSERT(execMemory.bytesAllocated() <= MaxCodeBytesPerProcess);
    917 
    918  return execMemory.bytesAllocated() + BufferSize <= MaxCodeBytesPerProcess;
    919 }
    920 
    921 bool js::jit::AddressIsInExecutableMemory(const void* p) {
    922  return execMemory.containsAddress(p);
    923 }
    924 
    925 bool js::jit::ReprotectRegion(void* start, size_t size,
    926                              ProtectionSetting protection,
    927                              MustFlushICache flushICache) {
    928 #if defined(JS_CODEGEN_WASM32)
    929  return true;
    930 #endif
    931 
    932  // Flush ICache when making code executable, before we modify |size|.
    933  if (flushICache == MustFlushICache::Yes) {
    934    MOZ_ASSERT(protection == ProtectionSetting::Executable);
    935    jit::FlushICache(start, size);
    936  }
    937 
    938  // Calculate the start of the page containing this region,
    939  // and account for this extra memory within size.
    940  size_t pageSize = gc::SystemPageSize();
    941  intptr_t startPtr = reinterpret_cast<intptr_t>(start);
    942  intptr_t pageStartPtr = startPtr & ~(pageSize - 1);
    943  void* pageStart = reinterpret_cast<void*>(pageStartPtr);
    944  size += (startPtr - pageStartPtr);
    945 
    946  // Round size up
    947  size += (pageSize - 1);
    948  size &= ~(pageSize - 1);
    949 
    950  MOZ_ASSERT((uintptr_t(pageStart) % pageSize) == 0);
    951 
    952  execMemory.assertValidAddress(pageStart, size);
    953 
    954  // On weak memory systems, make sure new code is visible on all cores before
    955  // addresses of the code are made public.  Now is the latest moment in time
    956  // when we can do that, and we're assuming that every other thread that has
    957  // written into the memory that is being reprotected here has synchronized
    958  // with this thread in such a way that the memory writes have become visible
    959  // and we therefore only need to execute the fence once here.  See bug 1529933
    960  // for a longer discussion of why this is both necessary and sufficient.
    961  //
    962  // We use the C++ fence here -- and not AtomicOperations::fenceSeqCst() --
    963  // primarily because ReprotectRegion will be called while we construct our own
    964  // jitted atomics.  But the C++ fence is sufficient and correct, too.
    965 #ifdef __wasi__
    966  MOZ_CRASH("NYI FOR WASI.");
    967 #else
    968  std::atomic_thread_fence(std::memory_order_seq_cst);
    969 
    970  if (!JitOptions.writeProtectCode) {
    971    return true;
    972  }
    973 
    974 #  ifdef JS_USE_APPLE_FAST_WX
    975  MOZ_CRASH("writeProtectCode should always be false on Apple Silicon");
    976 #  endif
    977 
    978 #  ifdef XP_WIN
    979  DWORD flags = ProtectionSettingToFlags(protection);
    980  // This is a essentially a VirtualProtect, but with lighter impact on
    981  // antivirus analysis. See bug 1823634.
    982  if (!VirtualAlloc(pageStart, size, MEM_COMMIT, flags)) {
    983    return false;
    984  }
    985 #  else
    986  unsigned flags = ProtectionSettingToFlags(protection);
    987  if (mprotect(pageStart, size, flags)) {
    988    return false;
    989  }
    990 #  endif
    991 #endif  // __wasi__
    992 
    993  execMemory.assertValidAddress(pageStart, size);
    994  return true;
    995 }
    996 
    997 #if defined(JS_USE_APPLE_FAST_WX) && !defined(XP_IOS)
    998 void js::jit::AutoMarkJitCodeWritableForThread::markExecutable(
    999    bool executable) {
   1000  if (__builtin_available(macOS 11.0, *)) {
   1001    pthread_jit_write_protect_np(executable);
   1002  } else {
   1003    MOZ_CRASH("pthread_jit_write_protect_np must be available");
   1004  }
   1005 }
   1006 #endif
   1007 
   1008 #ifdef DEBUG
   1009 static MOZ_THREAD_LOCAL(bool) sMarkingWritable;
   1010 
   1011 void js::jit::AutoMarkJitCodeWritableForThread::checkConstructor() {
   1012  if (!sMarkingWritable.initialized()) {
   1013    sMarkingWritable.infallibleInit();
   1014  }
   1015  MOZ_ASSERT(!sMarkingWritable.get(),
   1016             "AutoMarkJitCodeWritableForThread shouldn't be nested");
   1017  sMarkingWritable.set(true);
   1018 }
   1019 
   1020 void js::jit::AutoMarkJitCodeWritableForThread::checkDestructor() {
   1021  MOZ_ASSERT(sMarkingWritable.get());
   1022  sMarkingWritable.set(false);
   1023 }
   1024 #endif