summaryrefslogtreecommitdiffstats
path: root/toolkit/xre/dllservices/tests/TestDllBlocklistAssumptions.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/xre/dllservices/tests/TestDllBlocklistAssumptions.cpp')
-rw-r--r--toolkit/xre/dllservices/tests/TestDllBlocklistAssumptions.cpp344
1 files changed, 344 insertions, 0 deletions
diff --git a/toolkit/xre/dllservices/tests/TestDllBlocklistAssumptions.cpp b/toolkit/xre/dllservices/tests/TestDllBlocklistAssumptions.cpp
new file mode 100644
index 0000000000..17c68f26dc
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/TestDllBlocklistAssumptions.cpp
@@ -0,0 +1,344 @@
+/* -*- 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 http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+#include <tlhelp32.h>
+
+#include <stdio.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/Vector.h"
+
+#include "nsWindowsDllInterceptor.h"
+
+NTSTATUS NTAPI 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);
+
+using namespace mozilla;
+
+static WindowsDllInterceptor NtdllIntercept;
+
+static WindowsDllInterceptor::FuncHookType<decltype(&::NtMapViewOfSection)>
+ stub_NtMapViewOfSection;
+
+static constexpr auto kDllUsedForLoadLibraryTests = L"dbghelp.dll";
+
+class MappedViewsInfoCollector {
+ public:
+ struct MappedViewInfo {
+ PVOID BaseAddress;
+ HANDLE aSection;
+ PUBLIC_OBJECT_BASIC_INFORMATION obiSection;
+ MEMORY_BASIC_INFORMATION mbiVirtualMemory;
+ };
+
+ MappedViewsInfoCollector() = default;
+ ~MappedViewsInfoCollector() = default;
+
+ const MappedViewInfo* begin() const { return mMappedViewsInfo.begin(); }
+ const MappedViewInfo* end() const { return mMappedViewsInfo.end(); }
+
+ const MappedViewInfo* GetInfo(PVOID aBaseAddress) const {
+ for (const auto& mappedView : mMappedViewsInfo) {
+ if (mappedView.BaseAddress == aBaseAddress) {
+ return &mappedView;
+ }
+ }
+ return nullptr;
+ }
+
+ void Add(PVOID aBaseAddress, HANDLE aSection) {
+ auto existingMappedViewInfo = GetInfo(aBaseAddress);
+ if (existingMappedViewInfo) {
+ MOZ_RELEASE_ASSERT(existingMappedViewInfo->BaseAddress == aBaseAddress);
+ MOZ_RELEASE_ASSERT(existingMappedViewInfo->aSection == aSection);
+ return;
+ }
+
+ MappedViewInfo mappedViewInfo{aBaseAddress, aSection};
+ MOZ_RELEASE_ASSERT(NT_SUCCESS(::NtQueryObject(
+ aSection, ObjectBasicInformation, &mappedViewInfo.obiSection,
+ sizeof(mappedViewInfo.obiSection), nullptr)));
+ MOZ_RELEASE_ASSERT(NT_SUCCESS(::NtQueryVirtualMemory(
+ ::GetCurrentProcess(), aBaseAddress, MemoryBasicInformation,
+ &mappedViewInfo.mbiVirtualMemory,
+ sizeof(mappedViewInfo.mbiVirtualMemory), nullptr)));
+ MOZ_RELEASE_ASSERT(mMappedViewsInfo.append(std::move(mappedViewInfo)));
+ }
+
+ void Reset() { MOZ_RELEASE_ASSERT(mMappedViewsInfo.resize(0)); }
+
+ private:
+ Vector<MappedViewInfo> mMappedViewsInfo;
+};
+
+static bool sIsTestRunning = false;
+static MappedViewsInfoCollector sMappedViewsInfoCollector;
+
+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) {
+ NTSTATUS result = stub_NtMapViewOfSection(
+ aSection, aProcess, aBaseAddress, aZeroBits, aCommitSize, aSectionOffset,
+ aViewSize, aInheritDisposition, aAllocationType, aProtectionFlags);
+ if (sIsTestRunning && NT_SUCCESS(result)) {
+ MOZ_RELEASE_ASSERT(aBaseAddress);
+ sMappedViewsInfoCollector.Add(*aBaseAddress, aSection);
+ }
+ return result;
+}
+
+bool InitializeTests() {
+ NtdllIntercept.Init(L"ntdll.dll");
+
+ bool success = stub_NtMapViewOfSection.Set(
+ NtdllIntercept, "NtMapViewOfSection", patched_NtMapViewOfSection);
+ if (!success) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | TestDllBlocklistAssumptions | "
+ "Failed to hook NtMapViewOfSection.\n");
+ fflush(stdout);
+ return false;
+ }
+
+ printf(
+ "TEST-PASS | TestDllBlocklistAssumptions | "
+ "Successfully hooked NtMapViewOfSection.\n");
+ fflush(stdout);
+ return true;
+}
+
+bool CheckMappedViewAssumptions(const char* aTestDescription, PVOID baseAddress,
+ bool aExpectExecutableSection,
+ bool aExpectImageTypeMemory) {
+ auto mappedViewInfo = sMappedViewsInfoCollector.GetInfo(baseAddress);
+ if (!mappedViewInfo) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | TestDllBlocklistAssumptions | "
+ "Failed to find mapped view information while testing %s.\n",
+ aTestDescription);
+ fflush(stdout);
+ return false;
+ }
+
+ MOZ_RELEASE_ASSERT(mappedViewInfo->BaseAddress == baseAddress);
+
+ if (aExpectExecutableSection !=
+ bool(mappedViewInfo->obiSection.GrantedAccess & SECTION_MAP_EXECUTE)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | TestDllBlocklistAssumptions | "
+ "Mismatch in assumptions regarding section executablity while testing "
+ "%s.\n",
+ aTestDescription);
+ fflush(stdout);
+ return false;
+ }
+
+ if (aExpectImageTypeMemory !=
+ bool(mappedViewInfo->mbiVirtualMemory.Type & MEM_IMAGE)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | TestDllBlocklistAssumptions | "
+ "Mismatch in assumptions regarding virtual memory type while testing "
+ "%s.\n",
+ aTestDescription);
+ fflush(stdout);
+ return false;
+ }
+ return true;
+}
+
+// Assumptions used to block normal DLL loads:
+// - section handle was granted SECTION_MAP_EXECUTE access;
+// - virtual memory is tagged as MEM_IMAGE type.
+bool TestDllLoad() {
+ sMappedViewsInfoCollector.Reset();
+
+ auto module = ::LoadLibraryW(kDllUsedForLoadLibraryTests);
+ if (!module) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | TestDllBlocklistAssumptions | "
+ "Call to LoadLibraryW failed with error %lu.\n",
+ ::GetLastError());
+ fflush(stdout);
+ return false;
+ }
+
+ auto baseAddress = nt::PEHeaders::HModuleToBaseAddr<PVOID>(module);
+ bool result =
+ CheckMappedViewAssumptions("LoadLibraryW", baseAddress, true, true);
+ FreeLibrary(module);
+
+ if (result) {
+ printf(
+ "TEST-PASS | TestDllBlocklistAssumptions | "
+ "DLL loading works as expected.\n");
+ fflush(stdout);
+ }
+ return result;
+}
+
+// Assumptions used to avoid blocking DLL loads when loading as data file:
+// - section handle was *not* granted SECTION_MAP_EXECUTE access;
+// - virtual memory is *not* tagged as MEM_IMAGE type.
+bool TestDllLoadAsDataFile() {
+ sMappedViewsInfoCollector.Reset();
+
+ auto module = ::LoadLibraryExW(kDllUsedForLoadLibraryTests, nullptr,
+ LOAD_LIBRARY_AS_DATAFILE);
+ if (!module) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | TestDllBlocklistAssumptions | "
+ "Call to LoadLibraryExW failed with error %lu.\n",
+ ::GetLastError());
+ fflush(stdout);
+ return false;
+ }
+
+ auto baseAddress = nt::PEHeaders::HModuleToBaseAddr<PVOID>(module);
+ bool result = CheckMappedViewAssumptions(
+ "LoadLibraryExW(LOAD_LIBRARY_AS_DATAFILE)", baseAddress, false, false);
+ FreeLibrary(module);
+
+ if (result) {
+ printf(
+ "TEST-PASS | TestDllBlocklistAssumptions | "
+ "DLL loading as data file works as expected.\n");
+ fflush(stdout);
+ }
+ return result;
+}
+
+// Assumptions used to avoid blocking DLL loads when loading as image resource:
+// - section handle was *not* granted SECTION_MAP_EXECUTE access;
+// - virtual memory is tagged as MEM_IMAGE type, however.
+bool TestDllLoadAsImageResource() {
+ sMappedViewsInfoCollector.Reset();
+
+ auto module = ::LoadLibraryExW(kDllUsedForLoadLibraryTests, nullptr,
+ LOAD_LIBRARY_AS_IMAGE_RESOURCE);
+ if (!module) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | TestDllBlocklistAssumptions | "
+ "Call to LoadLibraryExW failed with error %lu.\n",
+ ::GetLastError());
+ fflush(stdout);
+ return false;
+ }
+
+ auto baseAddress = nt::PEHeaders::HModuleToBaseAddr<PVOID>(module);
+ bool result = CheckMappedViewAssumptions(
+ "LoadLibraryExW(LOAD_LIBRARY_AS_IMAGE_RESOURCE)", baseAddress, false,
+ true);
+ FreeLibrary(module);
+
+ if (result) {
+ printf(
+ "TEST-PASS | TestDllBlocklistAssumptions | "
+ "DLL loading as image resource works as expected.\n");
+ fflush(stdout);
+ }
+ return result;
+}
+
+// Assumptions used to avoid crashing when using Thread32Next (bug 1733532):
+// - section handle was *not* granted SECTION_MAP_EXECUTE access;
+// - virtual memory is *not* tagged as MEM_IMAGE type.
+bool TestThreadIteration() {
+ sMappedViewsInfoCollector.Reset();
+
+ HANDLE snapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
+ if (snapshot == INVALID_HANDLE_VALUE) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | TestDllBlocklistAssumptions | "
+ "Call toCreateToolhelp32Snapshot failed with error %lu.\n",
+ ::GetLastError());
+ fflush(stdout);
+ return false;
+ }
+
+ THREADENTRY32 entry{};
+ entry.dwSize = sizeof(entry);
+ if (!::Thread32First(snapshot, &entry)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | TestDllBlocklistAssumptions | "
+ "Call to Thread32First failed with error %lu.\n",
+ ::GetLastError());
+ fflush(stdout);
+ return false;
+ }
+
+ while (::Thread32Next(snapshot, &entry)) {
+ }
+ auto error = GetLastError();
+ if (error != ERROR_NO_MORE_FILES) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | TestDllBlocklistAssumptions | "
+ "Call to Thread32Next failed with unexpected error %lu.\n",
+ error);
+ fflush(stdout);
+ return false;
+ }
+
+ uint32_t count = 0;
+ for (const auto& mappedViewInfo : sMappedViewsInfoCollector) {
+ if (!CheckMappedViewAssumptions("Thread32Next", mappedViewInfo.BaseAddress,
+ false, false)) {
+ return false;
+ }
+ ++count;
+ }
+
+ if (!count) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | TestDllBlocklistAssumptions | "
+ "Unexpectedly found no mappings after iterating threads with "
+ "Thread32Next.\n");
+ fflush(stdout);
+ return false;
+ }
+
+ printf(
+ "TEST-PASS | TestDllBlocklistAssumptions | "
+ "Iterating threads with Thread32Next works as expected.\n");
+ fflush(stdout);
+ return true;
+}
+
+extern "C" int wmain(int argc, wchar_t* argv[]) {
+ LARGE_INTEGER start;
+ QueryPerformanceCounter(&start);
+
+ sIsTestRunning = true;
+ if (InitializeTests() && TestDllLoad() && TestDllLoadAsDataFile() &&
+ TestDllLoadAsImageResource() && TestThreadIteration()) {
+ sIsTestRunning = false;
+
+ printf("TEST-PASS | TestDllBlocklistAssumptions | all checks passed\n");
+
+ LARGE_INTEGER end, freq;
+ QueryPerformanceCounter(&end);
+
+ QueryPerformanceFrequency(&freq);
+
+ LARGE_INTEGER result;
+ result.QuadPart = end.QuadPart - start.QuadPart;
+ result.QuadPart *= 1000000;
+ result.QuadPart /= freq.QuadPart;
+
+ printf("Elapsed time: %lld microseconds\n", result.QuadPart);
+
+ return 0;
+ }
+
+ sIsTestRunning = false;
+ return 1;
+}