tor-browser

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

DllBlocklist.cpp (23175B)


      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 https://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/Attributes.h"
      8 #include "mozilla/NativeNt.h"
      9 #include "mozilla/Types.h"
     10 #include "mozilla/WindowsDllBlocklist.h"
     11 
     12 #include "DllBlocklist.h"
     13 #include "LoaderPrivateAPI.h"
     14 #include "ModuleLoadFrame.h"
     15 #include "SharedSection.h"
     16 
     17 using mozilla::DllBlockInfoFlags;
     18 
     19 #define DLL_BLOCKLIST_ENTRY(name, ...) \
     20  {MOZ_LITERAL_UNICODE_STRING(L##name), __VA_ARGS__},
     21 #define DLL_BLOCKLIST_STRING_TYPE UNICODE_STRING
     22 
     23 #if defined(MOZ_LAUNCHER_PROCESS) || defined(NIGHTLY_BUILD)
     24 #  include "mozilla/WindowsDllBlocklistLauncherDefs.h"
     25 #else
     26 #  include "mozilla/WindowsDllBlocklistCommon.h"
     27 DLL_BLOCKLIST_DEFINITIONS_BEGIN
     28 DLL_BLOCKLIST_DEFINITIONS_END
     29 #endif
     30 
     31 using WritableBuffer = mozilla::glue::detail::WritableBuffer<1024>;
     32 
     33 class MOZ_STATIC_CLASS MOZ_TRIVIAL_CTOR_DTOR NativeNtBlockSet final {
     34  struct NativeNtBlockSetEntry {
     35    NativeNtBlockSetEntry() = default;
     36    ~NativeNtBlockSetEntry() = default;
     37    NativeNtBlockSetEntry(const UNICODE_STRING& aName, uint64_t aVersion,
     38                          NativeNtBlockSetEntry* aNext)
     39        : mName(aName), mVersion(aVersion), mNext(aNext) {}
     40    UNICODE_STRING mName;
     41    uint64_t mVersion;
     42    NativeNtBlockSetEntry* mNext;
     43  };
     44 
     45 public:
     46  // Constructor and destructor MUST be trivial
     47  constexpr NativeNtBlockSet() : mFirstEntry(nullptr) {}
     48  ~NativeNtBlockSet() = default;
     49 
     50  void Add(const UNICODE_STRING& aName, uint64_t aVersion);
     51  void Write(WritableBuffer& buffer);
     52 
     53 private:
     54  static NativeNtBlockSetEntry* NewEntry(const UNICODE_STRING& aName,
     55                                         uint64_t aVersion,
     56                                         NativeNtBlockSetEntry* aNextEntry);
     57 
     58 private:
     59  NativeNtBlockSetEntry* mFirstEntry;
     60  mozilla::nt::SRWLock mLock;
     61 };
     62 
     63 NativeNtBlockSet::NativeNtBlockSetEntry* NativeNtBlockSet::NewEntry(
     64    const UNICODE_STRING& aName, uint64_t aVersion,
     65    NativeNtBlockSet::NativeNtBlockSetEntry* aNextEntry) {
     66  return mozilla::freestanding::RtlNew<NativeNtBlockSetEntry>(aName, aVersion,
     67                                                              aNextEntry);
     68 }
     69 
     70 void NativeNtBlockSet::Add(const UNICODE_STRING& aName, uint64_t aVersion) {
     71  mozilla::nt::AutoExclusiveLock lock(mLock);
     72 
     73  for (NativeNtBlockSetEntry* entry = mFirstEntry; entry;
     74       entry = entry->mNext) {
     75    if (::RtlEqualUnicodeString(&entry->mName, &aName, TRUE) &&
     76        aVersion == entry->mVersion) {
     77      return;
     78    }
     79  }
     80 
     81  // Not present, add it
     82  NativeNtBlockSetEntry* newEntry = NewEntry(aName, aVersion, mFirstEntry);
     83  if (newEntry) {
     84    mFirstEntry = newEntry;
     85  }
     86 }
     87 
     88 void NativeNtBlockSet::Write(WritableBuffer& aBuffer) {
     89  // NB: If this function is called, it is long after kernel32 is initialized,
     90  // so it is safe to use Win32 calls here.
     91  char buf[MAX_PATH];
     92 
     93  // It would be nicer to use RAII here. However, its destructor
     94  // might not run if an exception occurs, in which case we would never release
     95  // the lock (MSVC warns about this possibility). So we acquire and release
     96  // manually.
     97  ::AcquireSRWLockExclusive(&mLock);
     98 
     99  MOZ_SEH_TRY {
    100    for (auto entry = mFirstEntry; entry; entry = entry->mNext) {
    101      int convOk = ::WideCharToMultiByte(CP_UTF8, 0, entry->mName.Buffer,
    102                                         entry->mName.Length / sizeof(wchar_t),
    103                                         buf, sizeof(buf), nullptr, nullptr);
    104      if (!convOk) {
    105        continue;
    106      }
    107 
    108      // write name[,v.v.v.v];
    109      aBuffer.Write(buf, convOk);
    110 
    111      if (entry->mVersion != DllBlockInfo::ALL_VERSIONS) {
    112        aBuffer.Write(",", 1);
    113        uint16_t parts[4];
    114        parts[0] = entry->mVersion >> 48;
    115        parts[1] = (entry->mVersion >> 32) & 0xFFFF;
    116        parts[2] = (entry->mVersion >> 16) & 0xFFFF;
    117        parts[3] = entry->mVersion & 0xFFFF;
    118        for (size_t p = 0; p < std::size(parts); ++p) {
    119          _ltoa_s(parts[p], buf, sizeof(buf), 10);
    120          aBuffer.Write(buf, strlen(buf));
    121          if (p != std::size(parts) - 1) {
    122            aBuffer.Write(".", 1);
    123          }
    124        }
    125      }
    126      aBuffer.Write(";", 1);
    127    }
    128  }
    129  MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {}
    130 
    131  ::ReleaseSRWLockExclusive(&mLock);
    132 }
    133 
    134 static NativeNtBlockSet gBlockSet;
    135 
    136 extern "C" void MOZ_EXPORT NativeNtBlockSet_Write(WritableBuffer& aBuffer) {
    137  gBlockSet.Write(aBuffer);
    138 }
    139 
    140 enum class BlockAction {
    141  // Allow the DLL to be loaded.
    142  Allow,
    143  // Substitute in a different DLL to be loaded instead of this one?
    144  // This is intended to be used for Layered Service Providers, which
    145  // cannot be blocked in the normal way. Note that this doesn't seem
    146  // to be actually implemented right now, and no entries in the blocklist
    147  // use it.
    148  SubstituteLSP,
    149  // There was an error in determining whether we should block this DLL.
    150  // It will be blocked.
    151  Error,
    152  // Block the DLL from loading.
    153  Deny,
    154  // Effectively block the DLL from loading by redirecting its DllMain
    155  // to a stub version. This is needed for DLLs that add themselves to
    156  // the executable's Import Table, since failing to load would mean the
    157  // executable would fail to launch.
    158  NoOpEntryPoint,
    159 };
    160 
    161 static BlockAction CheckBlockInfo(const DllBlockInfo* aInfo,
    162                                  const mozilla::nt::PEHeaders& aHeaders,
    163                                  uint64_t& aVersion) {
    164  aVersion = DllBlockInfo::ALL_VERSIONS;
    165 
    166  if ((aInfo->mFlags & DllBlockInfoFlags::CHILD_PROCESSES_ONLY) &&
    167      !(gBlocklistInitFlags & eDllBlocklistInitFlagIsChildProcess)) {
    168    return BlockAction::Allow;
    169  }
    170 
    171  if ((aInfo->mFlags & DllBlockInfoFlags::UTILITY_PROCESSES_ONLY) &&
    172      !(gBlocklistInitFlags & eDllBlocklistInitFlagIsUtilityProcess)) {
    173    return BlockAction::Allow;
    174  }
    175 
    176  if ((aInfo->mFlags & DllBlockInfoFlags::SOCKET_PROCESSES_ONLY) &&
    177      !(gBlocklistInitFlags & eDllBlocklistInitFlagIsSocketProcess)) {
    178    return BlockAction::Allow;
    179  }
    180 
    181  if ((aInfo->mFlags & DllBlockInfoFlags::GPU_PROCESSES_ONLY) &&
    182      !(gBlocklistInitFlags & eDllBlocklistInitFlagIsGPUProcess)) {
    183    return BlockAction::Allow;
    184  }
    185 
    186  if ((aInfo->mFlags & DllBlockInfoFlags::BROWSER_PROCESS_ONLY) &&
    187      (gBlocklistInitFlags & eDllBlocklistInitFlagIsChildProcess)) {
    188    return BlockAction::Allow;
    189  }
    190 
    191  if ((aInfo->mFlags & DllBlockInfoFlags::GMPLUGIN_PROCESSES_ONLY) &&
    192      !(gBlocklistInitFlags & eDllBlocklistInitFlagIsGMPluginProcess)) {
    193    return BlockAction::Allow;
    194  }
    195 
    196  if ((aInfo->mFlags & DllBlockInfoFlags::RDD_PROCESSES_ONLY) &&
    197      !(gBlocklistInitFlags & eDllBlocklistInitFlagIsRDDProcess)) {
    198    return BlockAction::Allow;
    199  }
    200 
    201  if (aInfo->mMaxVersion == DllBlockInfo::ALL_VERSIONS) {
    202    return BlockAction::Deny;
    203  }
    204 
    205  if (!aHeaders) {
    206    return BlockAction::Error;
    207  }
    208 
    209  if (aInfo->mFlags & DllBlockInfoFlags::USE_TIMESTAMP) {
    210    DWORD timestamp;
    211    if (!aHeaders.GetTimeStamp(timestamp)) {
    212      return BlockAction::Error;
    213    }
    214 
    215    if (timestamp > aInfo->mMaxVersion) {
    216      return BlockAction::Allow;
    217    }
    218 
    219    return BlockAction::Deny;
    220  }
    221 
    222  // Else we try to get the file version information. Note that we don't have
    223  // access to GetFileVersionInfo* APIs.
    224  if (!aHeaders.GetVersionInfo(aVersion)) {
    225    return BlockAction::Error;
    226  }
    227 
    228  if (aInfo->IsVersionBlocked(aVersion)) {
    229    return BlockAction::Deny;
    230  }
    231 
    232  return BlockAction::Allow;
    233 }
    234 
    235 static BOOL WINAPI NoOp_DllMain(HINSTANCE, DWORD, LPVOID) { return TRUE; }
    236 
    237 // This helper function checks whether a given module is included
    238 // in the executable's Import Table.  Because an injected module's
    239 // DllMain may revert the Import Table to the original state, we parse
    240 // the Import Table every time a module is loaded without creating a cache.
    241 static bool IsInjectedDependentModule(
    242    const UNICODE_STRING& aModuleLeafName,
    243    mozilla::freestanding::Kernel32ExportsSolver& aK32Exports) {
    244  mozilla::nt::PEHeaders exeHeaders(aK32Exports.mGetModuleHandleW(nullptr));
    245  if (!exeHeaders || !exeHeaders.IsImportDirectoryTampered()) {
    246    // If no tampering is detected, no need to enumerate the Import Table.
    247    return false;
    248  }
    249 
    250  bool isDependent = false;
    251  exeHeaders.EnumImportChunks(
    252      [&isDependent, &aModuleLeafName, &exeHeaders](const char* aDepModule) {
    253        // If |aDepModule| is within the PE image, it's not an injected module
    254        // but a legitimate dependent module.
    255        if (isDependent || exeHeaders.IsWithinImage(aDepModule)) {
    256          return;
    257        }
    258 
    259        UNICODE_STRING depModuleLeafName;
    260        mozilla::nt::AllocatedUnicodeString depModuleName(aDepModule);
    261        mozilla::nt::GetLeafName(&depModuleLeafName, depModuleName);
    262        isDependent = (::RtlCompareUnicodeString(
    263                           &aModuleLeafName, &depModuleLeafName, TRUE) == 0);
    264      });
    265  return isDependent;
    266 }
    267 
    268 // Allowing a module to be loaded but detour the entrypoint to NoOp_DllMain
    269 // so that the module has no chance to interact with our code.  We need this
    270 // technique to safely block a module injected by IAT tampering because
    271 // blocking such a module makes a process fail to launch.
    272 static bool RedirectToNoOpEntryPoint(
    273    const mozilla::nt::PEHeaders& aModule,
    274    mozilla::freestanding::Kernel32ExportsSolver& aK32Exports) {
    275  mozilla::interceptor::WindowsDllEntryPointInterceptor interceptor(
    276      aK32Exports);
    277  if (!interceptor.Set(aModule, NoOp_DllMain)) {
    278    return false;
    279  }
    280 
    281  return true;
    282 }
    283 
    284 static BlockAction DetermineBlockAction(
    285    const UNICODE_STRING& aLeafName, void* aBaseAddress,
    286    mozilla::freestanding::Kernel32ExportsSolver* aK32Exports) {
    287  if (mozilla::nt::Contains12DigitHexString(aLeafName) ||
    288      mozilla::nt::IsFileNameAtLeast16HexDigits(aLeafName)) {
    289    return BlockAction::Deny;
    290  }
    291 
    292  mozilla::nt::PEHeaders headers(aBaseAddress);
    293  DWORD checksum = 0;
    294  DWORD timestamp = 0;
    295  DWORD imageSize = 0;
    296  uint64_t version = 0;
    297 
    298  // Block some malicious DLLs known for crashing our process (bug 1841751),
    299  // based on matching the combination of version number + timestamp + image
    300  // size. We further reduce the chances of collision with legit DLLs by
    301  // checking for a checksum of 0 and the absence of debug information, both of
    302  // which are unusual for production-ready DLLs.
    303  if (headers.GetCheckSum(checksum) && checksum == 0 && !headers.GetPdbInfo() &&
    304      headers.GetTimeStamp(timestamp)) {
    305    struct KnownMaliciousCombination {
    306      uint64_t mVersion;
    307      uint32_t mTimestamp;
    308      uint32_t mImageSize;
    309    };
    310    const KnownMaliciousCombination instances[]{
    311        // 1.0.0.26638
    312        {0x000100000000680e, 0x570B8A90, 0x62000},
    313        // 1.0.0.26793
    314        {0x00010000000068a9, 0x572B4CE4, 0x62000},
    315        // 1.0.0.27567
    316        {0x0001000000006baf, 0x57A725AC, 0x61000},
    317        // 1.0.0.29915
    318        {0x00010000000074db, 0x5A115D81, 0x5D000},
    319        // 1.0.0.31122
    320        {0x0001000000007992, 0x5CFF88B8, 0x5D000}};
    321 
    322    // We iterate over timestamps, because they are unique and it is a quick
    323    // field to fetch
    324    for (const auto& instance : instances) {
    325      if (instance.mTimestamp == timestamp) {
    326        // Only fetch other fields in case we have a match. Then, we can exit
    327        // the loop.
    328        if (headers.GetImageSize(imageSize) &&
    329            instance.mImageSize == imageSize &&
    330            headers.GetVersionInfo(version) && instance.mVersion == version) {
    331          return BlockAction::Deny;
    332        }
    333        break;
    334      }
    335    }
    336  }
    337 
    338  DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY(info);
    339  DECLARE_DLL_BLOCKLIST_NUM_ENTRIES(infoNumEntries);
    340 
    341  mozilla::freestanding::DllBlockInfoComparator comp(aLeafName);
    342 
    343  size_t match = LowerBound(info, 0, infoNumEntries, comp);
    344  bool builtinListHasLowerBound = match != infoNumEntries;
    345  const DllBlockInfo* entry = nullptr;
    346  BlockAction checkResult = BlockAction::Allow;
    347  if (builtinListHasLowerBound) {
    348    // There may be multiple entries on the list. Since LowerBound() returns
    349    // the first entry that matches (if there are any matches),
    350    // search forward from there.
    351    while (match < infoNumEntries && (comp(info[match]) == 0)) {
    352      entry = &info[match];
    353      checkResult = CheckBlockInfo(entry, headers, version);
    354      if (checkResult != BlockAction::Allow) {
    355        break;
    356      }
    357      ++match;
    358    }
    359  }
    360  mozilla::DebugOnly<bool> blockedByDynamicBlocklist = false;
    361  // Make sure we handle a case that older versions are blocked by the static
    362  // list, but the dynamic list blocks all versions.
    363  if (checkResult == BlockAction::Allow) {
    364    if (!mozilla::freestanding::gSharedSection.IsDisabled()) {
    365      entry = mozilla::freestanding::gSharedSection.SearchBlocklist(aLeafName);
    366      if (entry) {
    367        checkResult = CheckBlockInfo(entry, headers, version);
    368        blockedByDynamicBlocklist = checkResult != BlockAction::Allow;
    369      }
    370    }
    371  }
    372  if (checkResult == BlockAction::Allow) {
    373    return BlockAction::Allow;
    374  }
    375 
    376  gBlockSet.Add(entry->mName, version);
    377 
    378  if ((entry->mFlags & DllBlockInfoFlags::REDIRECT_TO_NOOP_ENTRYPOINT) &&
    379      aK32Exports && RedirectToNoOpEntryPoint(headers, *aK32Exports)) {
    380    MOZ_ASSERT(!blockedByDynamicBlocklist, "dynamic blocklist has redirect?");
    381    return BlockAction::NoOpEntryPoint;
    382  }
    383 
    384  return checkResult;
    385 }
    386 
    387 namespace mozilla {
    388 namespace freestanding {
    389 
    390 CrossProcessDllInterceptor::FuncHookType<LdrLoadDllPtr> stub_LdrLoadDll;
    391 
    392 NTSTATUS NTAPI patched_LdrLoadDll(PWCHAR aDllPath, PULONG aFlags,
    393                                  PUNICODE_STRING aDllName,
    394                                  PHANDLE aOutHandle) {
    395  ModuleLoadFrame frame(aDllName);
    396 
    397  NTSTATUS ntStatus = stub_LdrLoadDll(aDllPath, aFlags, aDllName, aOutHandle);
    398 
    399  return frame.SetLoadStatus(ntStatus, aOutHandle);
    400 }
    401 
    402 CrossProcessDllInterceptor::FuncHookType<NtMapViewOfSectionPtr>
    403    stub_NtMapViewOfSection;
    404 
    405 // All the code for patched_NtMapViewOfSection that relies on checked stack
    406 // buffers (e.g. mbi, sectionFileName) should be put in this helper function
    407 // (see bug 1733532).
    408 MOZ_NEVER_INLINE NTSTATUS AfterMapViewOfExecutableSection(
    409    HANDLE aProcess, PVOID* aBaseAddress, NTSTATUS aStubStatus) {
    410  // We don't care about mappings that aren't MEM_IMAGE.
    411  MEMORY_BASIC_INFORMATION mbi;
    412  NTSTATUS ntStatus =
    413      ::NtQueryVirtualMemory(aProcess, *aBaseAddress, MemoryBasicInformation,
    414                             &mbi, sizeof(mbi), nullptr);
    415  if (!NT_SUCCESS(ntStatus)) {
    416    ::NtUnmapViewOfSection(aProcess, *aBaseAddress);
    417    return STATUS_ACCESS_DENIED;
    418  }
    419  if (!(mbi.Type & MEM_IMAGE)) {
    420    return aStubStatus;
    421  }
    422 
    423  // Get the section name
    424  nt::MemorySectionNameBuf sectionFileName(
    425      gLoaderPrivateAPI.GetSectionNameBuffer(*aBaseAddress));
    426  if (sectionFileName.IsEmpty()) {
    427    ::NtUnmapViewOfSection(aProcess, *aBaseAddress);
    428    return STATUS_ACCESS_DENIED;
    429  }
    430 
    431  // Find the leaf name
    432  UNICODE_STRING leafOnStack;
    433  nt::GetLeafName(&leafOnStack, sectionFileName);
    434 
    435  bool isInjectedDependent = false;
    436  const UNICODE_STRING k32Name = MOZ_LITERAL_UNICODE_STRING(L"kernel32.dll");
    437  Kernel32ExportsSolver* k32Exports = nullptr;
    438  BlockAction blockAction;
    439  // Trying to get the Kernel32Exports while loading kernel32.dll causes Firefox
    440  // to crash. (but only during a profile-guided optimization run, oddly) We
    441  // know we're never going to block kernel32.dll, so skip all this
    442  if (::RtlCompareUnicodeString(&k32Name, &leafOnStack, TRUE) == 0) {
    443    blockAction = BlockAction::Allow;
    444  } else {
    445    auto noSharedSectionReset{SharedSection::AutoNoReset()};
    446    k32Exports = gSharedSection.GetKernel32Exports();
    447    // Small optimization: Since loading a dependent module does not involve
    448    // LdrLoadDll, we know isInjectedDependent is false if we hold a top frame.
    449    if (k32Exports && !ModuleLoadFrame::ExistsTopFrame()) {
    450      // Note that if a module is dependent but not injected, this means that
    451      // the executable built against it, and it should be signed by Mozilla
    452      // or Microsoft, so we don't need to worry about adding it to the list
    453      // for CIG. (and users won't be able to block it) So the only special
    454      // case here is a dependent module that has been injected.
    455      isInjectedDependent = IsInjectedDependentModule(leafOnStack, *k32Exports);
    456    }
    457 
    458    if (isInjectedDependent) {
    459      // Add an NT dv\path to the shared section so that a sandbox process can
    460      // use it to bypass CIG.  In a sandbox process, this addition fails
    461      // because we cannot map the section to a writable region, but it's
    462      // ignorable because the paths have been added by the browser process.
    463      (void)SharedSection::AddDependentModule(sectionFileName);
    464 
    465      bool attemptToBlockViaRedirect;
    466 #if defined(NIGHTLY_BUILD)
    467      // We enable automatic DLL blocking only in Nightly for now
    468      // because it caused a compat issue (bug 1682304 and 1704373).
    469      attemptToBlockViaRedirect = true;
    470      // We will set blockAction below in the if (attemptToBlockViaRedirect)
    471      // block, but I guess the compiler isn't smart enough to figure
    472      // that out and complains about an uninitialized variable :-(
    473      blockAction = BlockAction::NoOpEntryPoint;
    474 #else
    475      // Check blocklist
    476      blockAction =
    477          DetermineBlockAction(leafOnStack, *aBaseAddress, k32Exports);
    478      // If we were going to block this dependent module, try redirection
    479      // instead of blocking it, since blocking it would cause the .exe not to
    480      // launch.
    481      // Note tht Deny and Error both end up blocking the module in a
    482      // straightforward way, so those are the cases in which we need
    483      // to redirect instead.
    484      attemptToBlockViaRedirect =
    485          blockAction == BlockAction::Deny || blockAction == BlockAction::Error;
    486 #endif
    487      if (attemptToBlockViaRedirect) {
    488        // For a dependent module, try redirection instead of blocking it.
    489        // If we fail, we reluctantly allow the module for free.
    490        mozilla::nt::PEHeaders headers(*aBaseAddress);
    491        blockAction = RedirectToNoOpEntryPoint(headers, *k32Exports)
    492                          ? BlockAction::NoOpEntryPoint
    493                          : BlockAction::Allow;
    494      }
    495    } else {
    496      // Check blocklist
    497      blockAction =
    498          DetermineBlockAction(leafOnStack, *aBaseAddress, k32Exports);
    499    }
    500  }
    501 
    502  ModuleLoadInfo::Status loadStatus = ModuleLoadInfo::Status::Blocked;
    503 
    504  switch (blockAction) {
    505    case BlockAction::Allow:
    506      loadStatus = ModuleLoadInfo::Status::Loaded;
    507      break;
    508 
    509    case BlockAction::NoOpEntryPoint:
    510      loadStatus = ModuleLoadInfo::Status::Redirected;
    511      break;
    512 
    513    case BlockAction::SubstituteLSP:
    514      // The process heap needs to be available here because
    515      // NotifyLSPSubstitutionRequired below copies a given string into
    516      // the heap. We use a soft assert here, assuming LSP load always
    517      // occurs after the heap is initialized.
    518      MOZ_ASSERT(nt::RtlGetProcessHeap());
    519 
    520      // Notify patched_LdrLoadDll that it will be necessary to perform
    521      // a substitution before returning.
    522      ModuleLoadFrame::NotifyLSPSubstitutionRequired(&leafOnStack);
    523      break;
    524 
    525    default:
    526      break;
    527  }
    528 
    529  if (nt::RtlGetProcessHeap()) {
    530    ModuleLoadFrame::NotifySectionMap(
    531        nt::AllocatedUnicodeString(sectionFileName), *aBaseAddress, aStubStatus,
    532        loadStatus, isInjectedDependent);
    533  }
    534 
    535  if (loadStatus == ModuleLoadInfo::Status::Loaded ||
    536      loadStatus == ModuleLoadInfo::Status::Redirected) {
    537    return aStubStatus;
    538  }
    539 
    540  ::NtUnmapViewOfSection(aProcess, *aBaseAddress);
    541  return STATUS_ACCESS_DENIED;
    542 }
    543 
    544 // To preserve compatibility with third-parties, calling into this function
    545 // must not use checked stack buffers when reached through Thread32Next (see
    546 // bug 1733532). Hence this function is declared as MOZ_NO_STACK_PROTECTOR.
    547 // Ideally, all code relying on stack buffers should be put in the dedicated
    548 // helper function AfterMapViewOfExecutableImageSection, which does not have
    549 // the MOZ_NO_STACK_PROTECTOR attribute. The obi variable below is an
    550 // exception to this rule, as it is required to collect the information that
    551 // lets us decide whether we really need to go through the helper function.
    552 NTSTATUS NTAPI patched_NtMapViewOfSection(
    553    HANDLE aSection, HANDLE aProcess, PVOID* aBaseAddress, ULONG_PTR aZeroBits,
    554    SIZE_T aCommitSize, PLARGE_INTEGER aSectionOffset, PSIZE_T aViewSize,
    555    SECTION_INHERIT aInheritDisposition, ULONG aAllocationType,
    556    ULONG aProtectionFlags) {
    557  // Save off the values currently in the out-pointers for later restoration if
    558  // we decide not to permit this mapping.
    559  auto const rollback =
    560      [
    561          // Avoid taking a reference to the stack frame, mostly out of
    562          // paranoia. (The values of `aBaseAddress` et al. may have been
    563          // crafted to point to our return address anyway...)
    564          =,
    565          // `NtMapViewOfSection` itself is mildly robust to invalid pointers;
    566          // we can't easily do that, but we can at least check for `nullptr`.
    567          baseAddress = aBaseAddress ? *aBaseAddress : nullptr,
    568          sectionOffset = aSectionOffset ? *aSectionOffset : LARGE_INTEGER{},
    569          viewSize = aViewSize ? *aViewSize : 0]() {
    570        if (aBaseAddress) *aBaseAddress = baseAddress;
    571        if (aSectionOffset) *aSectionOffset = sectionOffset;
    572        if (aViewSize) *aViewSize = viewSize;
    573      };
    574 
    575  // We always map first, then we check for additional info after.
    576  NTSTATUS stubStatus = stub_NtMapViewOfSection(
    577      aSection, aProcess, aBaseAddress, aZeroBits, aCommitSize, aSectionOffset,
    578      aViewSize, aInheritDisposition, aAllocationType, aProtectionFlags);
    579  if (!NT_SUCCESS(stubStatus)) {
    580    return stubStatus;
    581  }
    582 
    583  if (aProcess != nt::kCurrentProcess) {
    584    // We're only interested in mapping for the current process.
    585    return stubStatus;
    586  }
    587 
    588  PUBLIC_OBJECT_BASIC_INFORMATION obi;
    589  NTSTATUS ntStatus = ::NtQueryObject(aSection, ObjectBasicInformation, &obi,
    590                                      sizeof(obi), nullptr);
    591  if (!NT_SUCCESS(ntStatus)) {
    592    ::NtUnmapViewOfSection(aProcess, *aBaseAddress);
    593    rollback();
    594    return STATUS_ACCESS_DENIED;
    595  }
    596 
    597  // We don't care about sections for which the permission to map executable
    598  // views was not asked at creation time. This early exit path is notably
    599  // taken for:
    600  //  - calls to LoadLibraryExW using LOAD_LIBRARY_AS_DATAFILE or
    601  //    LOAD_LIBRARY_AS_IMAGE_RESOURCE (bug 1842088), thus allowing us to load
    602  //    blocked DLLs for analysis without executing them;
    603  //  - calls to Thread32Next (bug 1733532), thus avoiding the helper function
    604  //    with stack cookie checks.
    605  if (!(obi.GrantedAccess & SECTION_MAP_EXECUTE)) {
    606    return stubStatus;
    607  }
    608 
    609  NTSTATUS rv =
    610      AfterMapViewOfExecutableSection(aProcess, aBaseAddress, stubStatus);
    611  if (FAILED(rv)) {
    612    rollback();
    613  }
    614  return rv;
    615 }
    616 
    617 }  // namespace freestanding
    618 }  // namespace mozilla