summaryrefslogtreecommitdiffstats
path: root/browser/app/winlauncher/freestanding/DllBlocklist.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'browser/app/winlauncher/freestanding/DllBlocklist.cpp')
-rw-r--r--browser/app/winlauncher/freestanding/DllBlocklist.cpp641
1 files changed, 641 insertions, 0 deletions
diff --git a/browser/app/winlauncher/freestanding/DllBlocklist.cpp b/browser/app/winlauncher/freestanding/DllBlocklist.cpp
new file mode 100644
index 0000000000..105ab6d918
--- /dev/null
+++ b/browser/app/winlauncher/freestanding/DllBlocklist.cpp
@@ -0,0 +1,641 @@
+/* -*- 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