/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

#include "mozilla/ArrayUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/BinarySearch.h"
#include "mozilla/NativeNt.h"
#include "mozilla/Types.h"
#include "mozilla/WindowsDllBlocklist.h"

#include "CrashAnnotations.h"
#include "DllBlocklist.h"
#include "LoaderPrivateAPI.h"
#include "ModuleLoadFrame.h"
#include "SharedSection.h"

using mozilla::DllBlockInfoFlags;

#define DLL_BLOCKLIST_ENTRY(name, ...) \
  {MOZ_LITERAL_UNICODE_STRING(L##name), __VA_ARGS__},
#define DLL_BLOCKLIST_STRING_TYPE UNICODE_STRING

#if defined(MOZ_LAUNCHER_PROCESS) || defined(NIGHTLY_BUILD)
#  include "mozilla/WindowsDllBlocklistLauncherDefs.h"
#else
#  include "mozilla/WindowsDllBlocklistCommon.h"
DLL_BLOCKLIST_DEFINITIONS_BEGIN
DLL_BLOCKLIST_DEFINITIONS_END
#endif

using WritableBuffer = mozilla::glue::detail::WritableBuffer<1024>;

class MOZ_STATIC_CLASS MOZ_TRIVIAL_CTOR_DTOR NativeNtBlockSet final {
  struct NativeNtBlockSetEntry {
    NativeNtBlockSetEntry() = default;
    ~NativeNtBlockSetEntry() = default;
    NativeNtBlockSetEntry(const UNICODE_STRING& aName, uint64_t aVersion,
                          NativeNtBlockSetEntry* aNext)
        : mName(aName), mVersion(aVersion), mNext(aNext) {}
    UNICODE_STRING mName;
    uint64_t mVersion;
    NativeNtBlockSetEntry* mNext;
  };

 public:
  // Constructor and destructor MUST be trivial
  constexpr NativeNtBlockSet() : mFirstEntry(nullptr) {}
  ~NativeNtBlockSet() = default;

  void Add(const UNICODE_STRING& aName, uint64_t aVersion);
  void Write(WritableBuffer& buffer);

 private:
  static NativeNtBlockSetEntry* NewEntry(const UNICODE_STRING& aName,
                                         uint64_t aVersion,
                                         NativeNtBlockSetEntry* aNextEntry);

 private:
  NativeNtBlockSetEntry* mFirstEntry;
  mozilla::nt::SRWLock mLock;
};

NativeNtBlockSet::NativeNtBlockSetEntry* NativeNtBlockSet::NewEntry(
    const UNICODE_STRING& aName, uint64_t aVersion,
    NativeNtBlockSet::NativeNtBlockSetEntry* aNextEntry) {
  return mozilla::freestanding::RtlNew<NativeNtBlockSetEntry>(aName, aVersion,
                                                              aNextEntry);
}

void NativeNtBlockSet::Add(const UNICODE_STRING& aName, uint64_t aVersion) {
  mozilla::nt::AutoExclusiveLock lock(mLock);

  for (NativeNtBlockSetEntry* entry = mFirstEntry; entry;
       entry = entry->mNext) {
    if (::RtlEqualUnicodeString(&entry->mName, &aName, TRUE) &&
        aVersion == entry->mVersion) {
      return;
    }
  }

  // Not present, add it
  NativeNtBlockSetEntry* newEntry = NewEntry(aName, aVersion, mFirstEntry);
  if (newEntry) {
    mFirstEntry = newEntry;
  }
}

void NativeNtBlockSet::Write(WritableBuffer& aBuffer) {
  // NB: If this function is called, it is long after kernel32 is initialized,
  // so it is safe to use Win32 calls here.
  char buf[MAX_PATH];

  // It would be nicer to use RAII here. However, its destructor
  // might not run if an exception occurs, in which case we would never release
  // the lock (MSVC warns about this possibility). So we acquire and release
  // manually.
  ::AcquireSRWLockExclusive(&mLock);

  MOZ_SEH_TRY {
    for (auto entry = mFirstEntry; entry; entry = entry->mNext) {
      int convOk = ::WideCharToMultiByte(CP_UTF8, 0, entry->mName.Buffer,
                                         entry->mName.Length / sizeof(wchar_t),
                                         buf, sizeof(buf), nullptr, nullptr);
      if (!convOk) {
        continue;
      }

      // write name[,v.v.v.v];
      aBuffer.Write(buf, convOk);

      if (entry->mVersion != DllBlockInfo::ALL_VERSIONS) {
        aBuffer.Write(",", 1);
        uint16_t parts[4];
        parts[0] = entry->mVersion >> 48;
        parts[1] = (entry->mVersion >> 32) & 0xFFFF;
        parts[2] = (entry->mVersion >> 16) & 0xFFFF;
        parts[3] = entry->mVersion & 0xFFFF;
        for (size_t p = 0; p < mozilla::ArrayLength(parts); ++p) {
          _ltoa_s(parts[p], buf, sizeof(buf), 10);
          aBuffer.Write(buf, strlen(buf));
          if (p != mozilla::ArrayLength(parts) - 1) {
            aBuffer.Write(".", 1);
          }
        }
      }
      aBuffer.Write(";", 1);
    }
  }
  MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {}

  ::ReleaseSRWLockExclusive(&mLock);
}

static NativeNtBlockSet gBlockSet;

extern "C" void MOZ_EXPORT
NativeNtBlockSet_Write(CrashReporter::AnnotationWriter& aWriter) {
  WritableBuffer buffer;
  gBlockSet.Write(buffer);
  aWriter.Write(CrashReporter::Annotation::BlockedDllList, buffer.Data(),
                buffer.Length());
}

enum class BlockAction {
  // Allow the DLL to be loaded.
  Allow,
  // Substitute in a different DLL to be loaded instead of this one?
  // This is intended to be used for Layered Service Providers, which
  // cannot be blocked in the normal way. Note that this doesn't seem
  // to be actually implemented right now, and no entries in the blocklist
  // use it.
  SubstituteLSP,
  // There was an error in determining whether we should block this DLL.
  // It will be blocked.
  Error,
  // Block the DLL from loading.
  Deny,
  // Effectively block the DLL from loading by redirecting its DllMain
  // to a stub version. This is needed for DLLs that add themselves to
  // the executable's Import Table, since failing to load would mean the
  // executable would fail to launch.
  NoOpEntryPoint,
};

static BlockAction CheckBlockInfo(const DllBlockInfo* aInfo,
                                  const mozilla::nt::PEHeaders& aHeaders,
                                  uint64_t& aVersion) {
  aVersion = DllBlockInfo::ALL_VERSIONS;

  if (aInfo->mFlags & (DllBlockInfoFlags::BLOCK_WIN8_AND_OLDER |
                       DllBlockInfoFlags::BLOCK_WIN7_AND_OLDER)) {
    RTL_OSVERSIONINFOW osv = {sizeof(osv)};
    NTSTATUS ntStatus = ::RtlGetVersion(&osv);
    if (!NT_SUCCESS(ntStatus)) {
      return BlockAction::Error;
    }

    if ((aInfo->mFlags & DllBlockInfoFlags::BLOCK_WIN8_AND_OLDER) &&
        (osv.dwMajorVersion > 6 ||
         (osv.dwMajorVersion == 6 && osv.dwMinorVersion > 2))) {
      return BlockAction::Allow;
    }

    if ((aInfo->mFlags & DllBlockInfoFlags::BLOCK_WIN7_AND_OLDER) &&
        (osv.dwMajorVersion > 6 ||
         (osv.dwMajorVersion == 6 && osv.dwMinorVersion > 1))) {
      return BlockAction::Allow;
    }
  }

  if ((aInfo->mFlags & DllBlockInfoFlags::CHILD_PROCESSES_ONLY) &&
      !(gBlocklistInitFlags & eDllBlocklistInitFlagIsChildProcess)) {
    return BlockAction::Allow;
  }

  if ((aInfo->mFlags & DllBlockInfoFlags::UTILITY_PROCESSES_ONLY) &&
      !(gBlocklistInitFlags & eDllBlocklistInitFlagIsUtilityProcess)) {
    return BlockAction::Allow;
  }

  if ((aInfo->mFlags & DllBlockInfoFlags::SOCKET_PROCESSES_ONLY) &&
      !(gBlocklistInitFlags & eDllBlocklistInitFlagIsSocketProcess)) {
    return BlockAction::Allow;
  }

  if ((aInfo->mFlags & DllBlockInfoFlags::GPU_PROCESSES_ONLY) &&
      !(gBlocklistInitFlags & eDllBlocklistInitFlagIsGPUProcess)) {
    return BlockAction::Allow;
  }

  if ((aInfo->mFlags & DllBlockInfoFlags::BROWSER_PROCESS_ONLY) &&
      (gBlocklistInitFlags & eDllBlocklistInitFlagIsChildProcess)) {
    return BlockAction::Allow;
  }

  if ((aInfo->mFlags & DllBlockInfoFlags::GMPLUGIN_PROCESSES_ONLY) &&
      !(gBlocklistInitFlags & eDllBlocklistInitFlagIsGMPluginProcess)) {
    return BlockAction::Allow;
  }

  if (aInfo->mMaxVersion == DllBlockInfo::ALL_VERSIONS) {
    return BlockAction::Deny;
  }

  if (!aHeaders) {
    return BlockAction::Error;
  }

  if (aInfo->mFlags & DllBlockInfoFlags::USE_TIMESTAMP) {
    DWORD timestamp;
    if (!aHeaders.GetTimeStamp(timestamp)) {
      return BlockAction::Error;
    }

    if (timestamp > aInfo->mMaxVersion) {
      return BlockAction::Allow;
    }

    return BlockAction::Deny;
  }

  // Else we try to get the file version information. Note that we don't have
  // access to GetFileVersionInfo* APIs.
  if (!aHeaders.GetVersionInfo(aVersion)) {
    return BlockAction::Error;
  }

  if (aInfo->IsVersionBlocked(aVersion)) {
    return BlockAction::Deny;
  }

  return BlockAction::Allow;
}

static BOOL WINAPI NoOp_DllMain(HINSTANCE, DWORD, LPVOID) { return TRUE; }

// This helper function checks whether a given module is included
// in the executable's Import Table.  Because an injected module's
// DllMain may revert the Import Table to the original state, we parse
// the Import Table every time a module is loaded without creating a cache.
static bool IsInjectedDependentModule(
    const UNICODE_STRING& aModuleLeafName,
    mozilla::freestanding::Kernel32ExportsSolver& aK32Exports) {
  mozilla::nt::PEHeaders exeHeaders(aK32Exports.mGetModuleHandleW(nullptr));
  if (!exeHeaders || !exeHeaders.IsImportDirectoryTampered()) {
    // If no tampering is detected, no need to enumerate the Import Table.
    return false;
  }

  bool isDependent = false;
  exeHeaders.EnumImportChunks(
      [&isDependent, &aModuleLeafName, &exeHeaders](const char* aDepModule) {
        // If |aDepModule| is within the PE image, it's not an injected module
        // but a legitimate dependent module.
        if (isDependent || exeHeaders.IsWithinImage(aDepModule)) {
          return;
        }

        UNICODE_STRING depModuleLeafName;
        mozilla::nt::AllocatedUnicodeString depModuleName(aDepModule);
        mozilla::nt::GetLeafName(&depModuleLeafName, depModuleName);
        isDependent = (::RtlCompareUnicodeString(
                           &aModuleLeafName, &depModuleLeafName, TRUE) == 0);
      });
  return isDependent;
}

// Allowing a module to be loaded but detour the entrypoint to NoOp_DllMain
// so that the module has no chance to interact with our code.  We need this
// technique to safely block a module injected by IAT tampering because
// blocking such a module makes a process fail to launch.
static bool RedirectToNoOpEntryPoint(
    const mozilla::nt::PEHeaders& aModule,
    mozilla::freestanding::Kernel32ExportsSolver& aK32Exports) {
  mozilla::interceptor::WindowsDllEntryPointInterceptor interceptor(
      aK32Exports);
  if (!interceptor.Set(aModule, NoOp_DllMain)) {
    return false;
  }

  return true;
}

static BlockAction DetermineBlockAction(
    const UNICODE_STRING& aLeafName, void* aBaseAddress,
    mozilla::freestanding::Kernel32ExportsSolver* aK32Exports) {
  if (mozilla::nt::Contains12DigitHexString(aLeafName) ||
      mozilla::nt::IsFileNameAtLeast16HexDigits(aLeafName)) {
    return BlockAction::Deny;
  }

  mozilla::nt::PEHeaders headers(aBaseAddress);
  DWORD checksum = 0;
  DWORD timestamp = 0;
  DWORD imageSize = 0;
  uint64_t version = 0;

  // Block some malicious DLLs known for crashing our process (bug 1841751),
  // based on matching the combination of version number + timestamp + image
  // size. We further reduce the chances of collision with legit DLLs by
  // checking for a checksum of 0 and the absence of debug information, both of
  // which are unusual for production-ready DLLs.
  if (headers.GetCheckSum(checksum) && checksum == 0 && !headers.GetPdbInfo() &&
      headers.GetTimeStamp(timestamp)) {
    struct KnownMaliciousCombination {
      uint64_t mVersion;
      uint32_t mTimestamp;
      uint32_t mImageSize;
    };
    const KnownMaliciousCombination instances[]{
        // 1.0.0.26638
        {0x000100000000680e, 0x570B8A90, 0x62000},
        // 1.0.0.26793
        {0x00010000000068a9, 0x572B4CE4, 0x62000},
        // 1.0.0.27567
        {0x0001000000006baf, 0x57A725AC, 0x61000},
        // 1.0.0.29915
        {0x00010000000074db, 0x5A115D81, 0x5D000},
        // 1.0.0.31122
        {0x0001000000007992, 0x5CFF88B8, 0x5D000}};

    // We iterate over timestamps, because they are unique and it is a quick
    // field to fetch
    for (const auto& instance : instances) {
      if (instance.mTimestamp == timestamp) {
        // Only fetch other fields in case we have a match. Then, we can exit
        // the loop.
        if (headers.GetImageSize(imageSize) &&
            instance.mImageSize == imageSize &&
            headers.GetVersionInfo(version) && instance.mVersion == version) {
          return BlockAction::Deny;
        }
        break;
      }
    }
  }

  DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY(info);
  DECLARE_DLL_BLOCKLIST_NUM_ENTRIES(infoNumEntries);

  mozilla::freestanding::DllBlockInfoComparator comp(aLeafName);

  size_t match = LowerBound(info, 0, infoNumEntries, comp);
  bool builtinListHasLowerBound = match != infoNumEntries;
  const DllBlockInfo* entry = nullptr;
  BlockAction checkResult = BlockAction::Allow;
  if (builtinListHasLowerBound) {
    // There may be multiple entries on the list. Since LowerBound() returns
    // the first entry that matches (if there are any matches),
    // search forward from there.
    while (match < infoNumEntries && (comp(info[match]) == 0)) {
      entry = &info[match];
      checkResult = CheckBlockInfo(entry, headers, version);
      if (checkResult != BlockAction::Allow) {
        break;
      }
      ++match;
    }
  }
  mozilla::DebugOnly<bool> blockedByDynamicBlocklist = false;
  // Make sure we handle a case that older versions are blocked by the static
  // list, but the dynamic list blocks all versions.
  if (checkResult == BlockAction::Allow) {
    if (!mozilla::freestanding::gSharedSection.IsDisabled()) {
      entry = mozilla::freestanding::gSharedSection.SearchBlocklist(aLeafName);
      if (entry) {
        checkResult = CheckBlockInfo(entry, headers, version);
        blockedByDynamicBlocklist = checkResult != BlockAction::Allow;
      }
    }
  }
  if (checkResult == BlockAction::Allow) {
    return BlockAction::Allow;
  }

  gBlockSet.Add(entry->mName, version);

  if ((entry->mFlags & DllBlockInfoFlags::REDIRECT_TO_NOOP_ENTRYPOINT) &&
      aK32Exports && RedirectToNoOpEntryPoint(headers, *aK32Exports)) {
    MOZ_ASSERT(!blockedByDynamicBlocklist, "dynamic blocklist has redirect?");
    return BlockAction::NoOpEntryPoint;
  }

  return checkResult;
}

namespace mozilla {
namespace freestanding {

CrossProcessDllInterceptor::FuncHookType<LdrLoadDllPtr> stub_LdrLoadDll;

NTSTATUS NTAPI patched_LdrLoadDll(PWCHAR aDllPath, PULONG aFlags,
                                  PUNICODE_STRING aDllName,
                                  PHANDLE aOutHandle) {
  ModuleLoadFrame frame(aDllName);

  NTSTATUS ntStatus = stub_LdrLoadDll(aDllPath, aFlags, aDllName, aOutHandle);

  return frame.SetLoadStatus(ntStatus, aOutHandle);
}

CrossProcessDllInterceptor::FuncHookType<NtMapViewOfSectionPtr>
    stub_NtMapViewOfSection;

// All the code for patched_NtMapViewOfSection that relies on checked stack
// buffers (e.g. mbi, sectionFileName) should be put in this helper function
// (see bug 1733532).
MOZ_NEVER_INLINE NTSTATUS AfterMapViewOfExecutableSection(
    HANDLE aProcess, PVOID* aBaseAddress, NTSTATUS aStubStatus) {
  // We don't care about mappings that aren't MEM_IMAGE.
  MEMORY_BASIC_INFORMATION mbi;
  NTSTATUS ntStatus =
      ::NtQueryVirtualMemory(aProcess, *aBaseAddress, MemoryBasicInformation,
                             &mbi, sizeof(mbi), nullptr);
  if (!NT_SUCCESS(ntStatus)) {
    ::NtUnmapViewOfSection(aProcess, *aBaseAddress);
    return STATUS_ACCESS_DENIED;
  }
  if (!(mbi.Type & MEM_IMAGE)) {
    return aStubStatus;
  }

  // Get the section name
  nt::MemorySectionNameBuf sectionFileName(
      gLoaderPrivateAPI.GetSectionNameBuffer(*aBaseAddress));
  if (sectionFileName.IsEmpty()) {
    ::NtUnmapViewOfSection(aProcess, *aBaseAddress);
    return STATUS_ACCESS_DENIED;
  }

  // Find the leaf name
  UNICODE_STRING leafOnStack;
  nt::GetLeafName(&leafOnStack, sectionFileName);

  bool isInjectedDependent = false;
  const UNICODE_STRING k32Name = MOZ_LITERAL_UNICODE_STRING(L"kernel32.dll");
  Kernel32ExportsSolver* k32Exports = nullptr;
  BlockAction blockAction;
  // Trying to get the Kernel32Exports while loading kernel32.dll causes Firefox
  // to crash. (but only during a profile-guided optimization run, oddly) We
  // know we're never going to block kernel32.dll, so skip all this
  if (::RtlCompareUnicodeString(&k32Name, &leafOnStack, TRUE) == 0) {
    blockAction = BlockAction::Allow;
  } else {
    auto noSharedSectionReset{SharedSection::AutoNoReset()};
    k32Exports = gSharedSection.GetKernel32Exports();
    // Small optimization: Since loading a dependent module does not involve
    // LdrLoadDll, we know isInjectedDependent is false if we hold a top frame.
    if (k32Exports && !ModuleLoadFrame::ExistsTopFrame()) {
      // Note that if a module is dependent but not injected, this means that
      // the executable built against it, and it should be signed by Mozilla
      // or Microsoft, so we don't need to worry about adding it to the list
      // for CIG. (and users won't be able to block it) So the only special
      // case here is a dependent module that has been injected.
      isInjectedDependent = IsInjectedDependentModule(leafOnStack, *k32Exports);
    }

    if (isInjectedDependent) {
      // Add an NT dv\path to the shared section so that a sandbox process can
      // use it to bypass CIG.  In a sandbox process, this addition fails
      // because we cannot map the section to a writable region, but it's
      // ignorable because the paths have been added by the browser process.
      Unused << SharedSection::AddDependentModule(sectionFileName);

      bool attemptToBlockViaRedirect;
#if defined(NIGHTLY_BUILD)
      // We enable automatic DLL blocking only in Nightly for now
      // because it caused a compat issue (bug 1682304 and 1704373).
      attemptToBlockViaRedirect = true;
      // We will set blockAction below in the if (attemptToBlockViaRedirect)
      // block, but I guess the compiler isn't smart enough to figure
      // that out and complains about an uninitialized variable :-(
      blockAction = BlockAction::NoOpEntryPoint;
#else
      // Check blocklist
      blockAction =
          DetermineBlockAction(leafOnStack, *aBaseAddress, k32Exports);
      // If we were going to block this dependent module, try redirection
      // instead of blocking it, since blocking it would cause the .exe not to
      // launch.
      // Note tht Deny and Error both end up blocking the module in a
      // straightforward way, so those are the cases in which we need
      // to redirect instead.
      attemptToBlockViaRedirect =
          blockAction == BlockAction::Deny || blockAction == BlockAction::Error;
#endif
      if (attemptToBlockViaRedirect) {
        // For a dependent module, try redirection instead of blocking it.
        // If we fail, we reluctantly allow the module for free.
        mozilla::nt::PEHeaders headers(*aBaseAddress);
        blockAction = RedirectToNoOpEntryPoint(headers, *k32Exports)
                          ? BlockAction::NoOpEntryPoint
                          : BlockAction::Allow;
      }
    } else {
      // Check blocklist
      blockAction =
          DetermineBlockAction(leafOnStack, *aBaseAddress, k32Exports);
    }
  }

  ModuleLoadInfo::Status loadStatus = ModuleLoadInfo::Status::Blocked;

  switch (blockAction) {
    case BlockAction::Allow:
      loadStatus = ModuleLoadInfo::Status::Loaded;
      break;

    case BlockAction::NoOpEntryPoint:
      loadStatus = ModuleLoadInfo::Status::Redirected;
      break;

    case BlockAction::SubstituteLSP:
      // The process heap needs to be available here because
      // NotifyLSPSubstitutionRequired below copies a given string into
      // the heap. We use a soft assert here, assuming LSP load always
      // occurs after the heap is initialized.
      MOZ_ASSERT(nt::RtlGetProcessHeap());

      // Notify patched_LdrLoadDll that it will be necessary to perform
      // a substitution before returning.
      ModuleLoadFrame::NotifyLSPSubstitutionRequired(&leafOnStack);
      break;

    default:
      break;
  }

  if (nt::RtlGetProcessHeap()) {
    ModuleLoadFrame::NotifySectionMap(
        nt::AllocatedUnicodeString(sectionFileName), *aBaseAddress, aStubStatus,
        loadStatus, isInjectedDependent);
  }

  if (loadStatus == ModuleLoadInfo::Status::Loaded ||
      loadStatus == ModuleLoadInfo::Status::Redirected) {
    return aStubStatus;
  }

  ::NtUnmapViewOfSection(aProcess, *aBaseAddress);
  return STATUS_ACCESS_DENIED;
}

// To preserve compatibility with third-parties, calling into this function
// must not use checked stack buffers when reached through Thread32Next (see
// bug 1733532). Hence this function is declared as MOZ_NO_STACK_PROTECTOR.
// Ideally, all code relying on stack buffers should be put in the dedicated
// helper function AfterMapViewOfExecutableImageSection, which does not have
// the MOZ_NO_STACK_PROTECTOR attribute. The obi variable below is an
// exception to this rule, as it is required to collect the information that
// lets us decide whether we really need to go through the helper function.
NTSTATUS NTAPI patched_NtMapViewOfSection(
    HANDLE aSection, HANDLE aProcess, PVOID* aBaseAddress, ULONG_PTR aZeroBits,
    SIZE_T aCommitSize, PLARGE_INTEGER aSectionOffset, PSIZE_T aViewSize,
    SECTION_INHERIT aInheritDisposition, ULONG aAllocationType,
    ULONG aProtectionFlags) {
  // Save off the values currently in the out-pointers for later restoration if
  // we decide not to permit this mapping.
  auto const rollback =
      [
          // Avoid taking a reference to the stack frame, mostly out of
          // paranoia. (The values of `aBaseAddress` et al. may have been
          // crafted to point to our return address anyway...)
          =,
          // `NtMapViewOfSection` itself is mildly robust to invalid pointers;
          // we can't easily do that, but we can at least check for `nullptr`.
          baseAddress = aBaseAddress ? *aBaseAddress : nullptr,
          sectionOffset = aSectionOffset ? *aSectionOffset : LARGE_INTEGER{},
          viewSize = aViewSize ? *aViewSize : 0]() {
        if (aBaseAddress) *aBaseAddress = baseAddress;
        if (aSectionOffset) *aSectionOffset = sectionOffset;
        if (aViewSize) *aViewSize = viewSize;
      };

  // We always map first, then we check for additional info after.
  NTSTATUS stubStatus = stub_NtMapViewOfSection(
      aSection, aProcess, aBaseAddress, aZeroBits, aCommitSize, aSectionOffset,
      aViewSize, aInheritDisposition, aAllocationType, aProtectionFlags);
  if (!NT_SUCCESS(stubStatus)) {
    return stubStatus;
  }

  if (aProcess != nt::kCurrentProcess) {
    // We're only interested in mapping for the current process.
    return stubStatus;
  }

  PUBLIC_OBJECT_BASIC_INFORMATION obi;
  NTSTATUS ntStatus = ::NtQueryObject(aSection, ObjectBasicInformation, &obi,
                                      sizeof(obi), nullptr);
  if (!NT_SUCCESS(ntStatus)) {
    ::NtUnmapViewOfSection(aProcess, *aBaseAddress);
    rollback();
    return STATUS_ACCESS_DENIED;
  }

  // We don't care about sections for which the permission to map executable
  // views was not asked at creation time. This early exit path is notably
  // taken for:
  //  - calls to LoadLibraryExW using LOAD_LIBRARY_AS_DATAFILE or
  //    LOAD_LIBRARY_AS_IMAGE_RESOURCE (bug 1842088), thus allowing us to load
  //    blocked DLLs for analysis without executing them;
  //  - calls to Thread32Next (bug 1733532), thus avoiding the helper function
  //    with stack cookie checks.
  if (!(obi.GrantedAccess & SECTION_MAP_EXECUTE)) {
    return stubStatus;
  }

  NTSTATUS rv =
      AfterMapViewOfExecutableSection(aProcess, aBaseAddress, stubStatus);
  if (FAILED(rv)) {
    rollback();
  }
  return rv;
}

}  // namespace freestanding
}  // namespace mozilla
