summaryrefslogtreecommitdiffstats
path: root/toolkit/xre/dllservices/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /toolkit/xre/dllservices/tests
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/xre/dllservices/tests')
-rw-r--r--toolkit/xre/dllservices/tests/AssemblyPayloads.h238
-rw-r--r--toolkit/xre/dllservices/tests/TestDllBlocklistAssumptions.cpp344
-rw-r--r--toolkit/xre/dllservices/tests/TestDllInterceptor.cpp1475
-rw-r--r--toolkit/xre/dllservices/tests/TestDllInterceptor.exe.manifest17
-rw-r--r--toolkit/xre/dllservices/tests/TestDllInterceptorCrossProcess.cpp159
-rw-r--r--toolkit/xre/dllservices/tests/TestIATPatcher.cpp121
-rw-r--r--toolkit/xre/dllservices/tests/TestMMPolicy.cpp204
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDLLBlocklist.cpp475
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.rc42
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/moz.build17
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_GMPluginProcessOnly/TestDllBlocklist_GMPluginProcessOnly.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_GMPluginProcessOnly/moz.build15
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_GPUProcessOnly/TestDllBlocklist_GPUProcessOnly.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_GPUProcessOnly/moz.build15
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/TestDllBlocklist_MatchByName.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/moz.build15
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.rc42
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/moz.build17
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_DifferentProcesses/TestDllBlocklist_MultipleEntries_DifferentProcesses.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_DifferentProcesses/TestDllBlocklist_MultipleEntries_DifferentProcesses.rc42
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_DifferentProcesses/moz.build19
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessBackward/TestDllBlocklist_MultipleEntries_SameProcessBackward.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessBackward/TestDllBlocklist_MultipleEntries_SameProcessBackward.rc42
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessBackward/moz.build19
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessForward/TestDllBlocklist_MultipleEntries_SameProcessForward.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessForward/TestDllBlocklist_MultipleEntries_SameProcessForward.rc42
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessForward/moz.build19
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.cpp12
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.rc42
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/moz.build21
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification1/TestDllBlocklist_SingleNotification1.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification1/TestDllBlocklist_SingleNotification1.rc42
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification1/moz.build17
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification2/TestDllBlocklist_SingleNotification2.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification2/TestDllBlocklist_SingleNotification2.rc42
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification2/moz.build17
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/TestDllBlocklist_SocketProcessOnly.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/moz.build15
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/TestDllBlocklist_UserBlocked.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/moz.build15
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/TestDllBlocklist_UtilityProcessOnly.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/moz.build15
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestUntrustedModules.cpp462
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.rc38
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/moz.build17
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll2/TestUntrustedModules_Dll2.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll2/moz.build15
-rw-r--r--toolkit/xre/dllservices/tests/gtest/moz.build39
-rw-r--r--toolkit/xre/dllservices/tests/gtest/rust/Cargo.toml12
-rw-r--r--toolkit/xre/dllservices/tests/gtest/rust/TestBCryptFallback.cpp113
-rw-r--r--toolkit/xre/dllservices/tests/gtest/rust/moz.build11
-rw-r--r--toolkit/xre/dllservices/tests/gtest/rust/test.rs19
-rw-r--r--toolkit/xre/dllservices/tests/moz.build47
56 files changed, 4495 insertions, 0 deletions
diff --git a/toolkit/xre/dllservices/tests/AssemblyPayloads.h b/toolkit/xre/dllservices/tests/AssemblyPayloads.h
new file mode 100644
index 0000000000..4dcbcc5fbf
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/AssemblyPayloads.h
@@ -0,0 +1,238 @@
+/* -*- 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/. */
+
+/* These assembly functions represent patterns that were already hooked by
+ * another application before our detour.
+ */
+
+#ifndef mozilla_AssemblyPayloads_h
+#define mozilla_AssemblyPayloads_h
+
+#include <cstdint>
+
+#define PADDING_256_NOP \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;"
+
+extern "C" {
+
+#if defined(__clang__)
+# if defined(_M_X64)
+constexpr uintptr_t JumpDestination = 0x7fff00000000;
+
+__declspec(dllexport) __attribute__((naked)) void MovPushRet() {
+ asm volatile(
+ "mov %0, %%rax;"
+ "push %%rax;"
+ "ret;"
+ :
+ : "i"(JumpDestination));
+}
+
+__declspec(dllexport) __attribute__((naked)) void MovRaxJump() {
+ asm volatile(
+ "mov %0, %%rax;"
+ "jmpq *%%rax;"
+ :
+ : "i"(JumpDestination));
+}
+
+__declspec(dllexport) __attribute__((naked)) void DoubleJump() {
+ asm volatile(
+ "jmp label1;"
+
+ "label2:"
+ "mov %0, %%rax;"
+ "jmpq *%%rax;"
+
+ // 0x100 bytes padding to generate jmp rel32 instead of jmp rel8
+ PADDING_256_NOP
+
+ "label1:"
+ "jmp label2;"
+ :
+ : "i"(JumpDestination));
+}
+
+__declspec(dllexport) __attribute__((naked)) void NearJump() {
+ asm volatile(
+ "jae label3;"
+ "je label3;"
+ "jne label3;"
+
+ "label4:"
+ "mov %0, %%rax;"
+ "jmpq *%%rax;"
+
+ // 0x100 bytes padding to generate jae rel32 instead of jae rel8
+ PADDING_256_NOP
+
+ "label3:"
+ "jmp label4;"
+ :
+ : "i"(JumpDestination));
+}
+
+__declspec(dllexport) __attribute__((naked)) void OpcodeFF() {
+ // Skip PUSH (FF /6) because clang prefers Opcode 50+rd
+ // to translate PUSH r64 rather than Opcode FF.
+ asm volatile(
+ "incl %eax;"
+ "decl %ebx;"
+ "call *%rcx;"
+ "jmp *(%rip);" // Indirect jump to 0xcccccccc`cccccccc
+ "int $3;int $3;int $3;int $3;"
+ "int $3;int $3;int $3;int $3;");
+}
+
+__declspec(dllexport) __attribute__((naked)) void IndirectCall() {
+ asm volatile(
+ "call *(%rip);" // Indirect call to 0x90909090`90909090
+ "nop;nop;nop;nop;nop;nop;nop;nop;"
+ "ret;");
+}
+
+__declspec(dllexport) __attribute__((naked)) void MovImm64() {
+ asm volatile(
+ "mov $0x1234567812345678, %r10;"
+ "nop;nop;nop");
+}
+
+# if !defined(MOZ_CODE_COVERAGE)
+// This code reproduces bug 1798787: it uses the same prologue, the same unwind
+// info, and it has a call instruction that starts within the 13 first bytes.
+__attribute__((naked)) void DetouredCallCode(uintptr_t aCallee) {
+ asm volatile(
+ "subq $0x28, %rsp;"
+ "testq %rcx, %rcx;"
+ "jz exit;"
+ "callq *%rcx;"
+ "exit:"
+ "addq $0x28, %rsp;"
+ "retq;");
+}
+constexpr uint8_t gDetouredCallCodeSize = 16; // size of function in bytes
+alignas(uint32_t) uint8_t gDetouredCallUnwindInfo[] = {
+ 0x01, // Version (1), Flags (0)
+ 0x04, // SizeOfProlog (4)
+ 0x01, // CountOfUnwindCodes (1)
+ 0x00, // FrameRegister (0), FrameOffset (0)
+ // UnwindCodes[0]
+ 0x04, // .OffsetInProlog (4)
+ 0x42, // .UnwindOpCode(UWOP_ALLOC_SMALL=2), .UnwindInfo (4)
+};
+
+// This points to the same code as DetouredCallCode, but dynamically generated
+// so that it can have custom unwinding info. See TestDllInterceptor.cpp.
+extern decltype(&DetouredCallCode) gDetouredCall;
+
+// This is just a jumper: our hooking code will thus detour the jump target
+// -- it will not detour DetouredCallJumper. We need to do this to point our
+// hooking code to the dynamic code, because our hooking API works with an
+// exported function name.
+__attribute__((naked)) __declspec(dllexport noinline) void DetouredCallJumper(
+ uintptr_t aCallee) {
+ // Ideally we would want this to be:
+ // jmp qword ptr [rip + offset gDetouredCall]
+ // Unfortunately, it is unclear how to do that with inline assembly, so we
+ // use a zero offset and patch it before the test.
+ asm volatile("jmpq *0(%rip)");
+}
+# endif // !defined(MOZ_CODE_COVERAGE)
+
+# elif defined(_M_IX86)
+constexpr uintptr_t JumpDestination = 0x7fff0000;
+
+__declspec(dllexport) __attribute__((naked)) void PushRet() {
+ asm volatile(
+ "push %0;"
+ "ret;"
+ :
+ : "i"(JumpDestination));
+}
+
+__declspec(dllexport) __attribute__((naked)) void MovEaxJump() {
+ asm volatile(
+ "mov %0, %%eax;"
+ "jmp *%%eax;"
+ :
+ : "i"(JumpDestination));
+}
+
+__declspec(dllexport) __attribute__((naked)) void Opcode83() {
+ asm volatile(
+ "xor $0x42, %eax;"
+ "cmpl $1, 0xc(%ebp);");
+}
+
+__declspec(dllexport) __attribute__((naked)) void LockPrefix() {
+ // Test an instruction with a LOCK prefix (0xf0) at a non-zero offset
+ asm volatile(
+ "push $0x7c;"
+ "lock push $0x7c;");
+}
+
+__declspec(dllexport) __attribute__((naked)) void LooksLikeLockPrefix() {
+ // This is for a regression scenario of bug 1625452, where we double-counted
+ // the offset in CountPrefixBytes. When we count prefix bytes in front of
+ // the 2nd PUSH located at offset 2, we mistakenly started counting from
+ // the byte 0xf0 at offset 4, which is considered as LOCK, thus we try to
+ // detour the next byte 0xcc and it fails.
+ //
+ // 0: 6a7c push 7Ch
+ // 2: 68ccf00000 push 0F0CCh
+ //
+ asm volatile(
+ "push $0x7c;"
+ "push $0x0000f0cc;");
+}
+
+__declspec(dllexport) __attribute__((naked)) void DoubleJump() {
+ asm volatile(
+ "jmp label1;"
+
+ "label2:"
+ "mov %0, %%eax;"
+ "jmp *%%eax;"
+
+ // 0x100 bytes padding to generate jmp rel32 instead of jmp rel8
+ PADDING_256_NOP
+
+ "label1:"
+ "jmp label2;"
+ :
+ : "i"(JumpDestination));
+}
+# endif
+
+# if !defined(_M_ARM64)
+__declspec(dllexport) __attribute__((naked)) void UnsupportedOp() {
+ asm volatile(
+ "ud2;"
+ "nop;nop;nop;nop;nop;nop;nop;nop;"
+ "nop;nop;nop;nop;nop;nop;nop;nop;");
+}
+# endif // !defined(_M_ARM64)
+
+#endif // defined(__clang__)
+
+} // extern "C"
+
+#endif // mozilla_AssemblyPayloads_h
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;
+}
diff --git a/toolkit/xre/dllservices/tests/TestDllInterceptor.cpp b/toolkit/xre/dllservices/tests/TestDllInterceptor.cpp
new file mode 100644
index 0000000000..2b70c9bcd4
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/TestDllInterceptor.cpp
@@ -0,0 +1,1475 @@
+/* -*- 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 <shlobj.h>
+#include <stdio.h>
+#include <commdlg.h>
+#define SECURITY_WIN32
+#include <security.h>
+#include <wininet.h>
+#include <schnlsp.h>
+#include <winternl.h>
+#include <processthreadsapi.h>
+
+#include <bcrypt.h>
+#pragma comment(lib, "bcrypt.lib")
+
+#include "AssemblyPayloads.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsWindowsDllInterceptor.h"
+#include "nsWindowsHelpers.h"
+
+NTSTATUS NTAPI NtFlushBuffersFile(HANDLE, PIO_STATUS_BLOCK);
+NTSTATUS NTAPI NtReadFile(HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID,
+ PIO_STATUS_BLOCK, PVOID, ULONG, PLARGE_INTEGER,
+ PULONG);
+NTSTATUS NTAPI NtReadFileScatter(HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID,
+ PIO_STATUS_BLOCK, PFILE_SEGMENT_ELEMENT, ULONG,
+ PLARGE_INTEGER, PULONG);
+NTSTATUS NTAPI NtWriteFile(HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID,
+ PIO_STATUS_BLOCK, PVOID, ULONG, PLARGE_INTEGER,
+ PULONG);
+NTSTATUS NTAPI NtWriteFileGather(HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID,
+ PIO_STATUS_BLOCK, PFILE_SEGMENT_ELEMENT, ULONG,
+ PLARGE_INTEGER, PULONG);
+NTSTATUS NTAPI NtQueryFullAttributesFile(POBJECT_ATTRIBUTES, PVOID);
+NTSTATUS NTAPI LdrLoadDll(PWCHAR filePath, PULONG flags,
+ PUNICODE_STRING moduleFileName, PHANDLE handle);
+NTSTATUS NTAPI LdrUnloadDll(HMODULE);
+
+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);
+
+// These pointers are disguised as PVOID to avoid pulling in obscure headers
+PVOID NTAPI LdrResolveDelayLoadedAPI(PVOID, PVOID, PVOID, PVOID, PVOID, ULONG);
+void CALLBACK ProcessCaretEvents(HWINEVENTHOOK, DWORD, HWND, LONG, LONG, DWORD,
+ DWORD);
+void __fastcall BaseThreadInitThunk(BOOL aIsInitialThread, void* aStartAddress,
+ void* aThreadParam);
+
+BOOL WINAPI ApiSetQueryApiSetPresence(PCUNICODE_STRING, PBOOLEAN);
+
+#if (_WIN32_WINNT < 0x0602)
+BOOL WINAPI
+SetProcessMitigationPolicy(PROCESS_MITIGATION_POLICY aMitigationPolicy,
+ PVOID aBuffer, SIZE_T aBufferLen);
+#endif // (_WIN32_WINNT < 0x0602)
+
+#define RtlGenRandom SystemFunction036
+extern "C" BOOLEAN NTAPI RtlGenRandom(PVOID aRandomBuffer,
+ ULONG aRandomBufferLength);
+
+using namespace mozilla;
+
+struct payload {
+ UINT64 a;
+ UINT64 b;
+ UINT64 c;
+
+ bool operator==(const payload& other) const {
+ return (a == other.a && b == other.b && c == other.c);
+ }
+};
+
+extern "C" __declspec(dllexport) __declspec(noinline) payload
+ rotatePayload(payload p) {
+ UINT64 tmp = p.a;
+ p.a = p.b;
+ p.b = p.c;
+ p.c = tmp;
+ return p;
+}
+
+// payloadNotHooked is a target function for a test to expect a negative result.
+// We cannot use rotatePayload for that purpose because our detour cannot hook
+// a function detoured already. Please keep this function always unhooked.
+extern "C" __declspec(dllexport) __declspec(noinline) payload
+ payloadNotHooked(payload p) {
+ // Do something different from rotatePayload to avoid ICF.
+ p.a ^= p.b;
+ p.b ^= p.c;
+ p.c ^= p.a;
+ return p;
+}
+
+// Declared as volatile to prevent optimizers from incorrectly eliding accesses
+// to it. (See bug 1769001 for a motivating example.)
+static volatile bool patched_func_called = false;
+
+static WindowsDllInterceptor::FuncHookType<decltype(&rotatePayload)>
+ orig_rotatePayload;
+
+static WindowsDllInterceptor::FuncHookType<decltype(&payloadNotHooked)>
+ orig_payloadNotHooked;
+
+static payload patched_rotatePayload(payload p) {
+ patched_func_called = true;
+ return orig_rotatePayload(p);
+}
+
+// Invoke aFunc by taking aArg's contents and using them as aFunc's arguments
+template <typename OrigFuncT, typename... Args,
+ typename ArgTuple = std::tuple<Args...>, size_t... Indices>
+decltype(auto) Apply(OrigFuncT& aFunc, ArgTuple&& aArgs,
+ std::index_sequence<Indices...>) {
+ return std::apply(aFunc, aArgs);
+}
+
+#define DEFINE_TEST_FUNCTION(calling_convention) \
+ template <typename R, typename... Args, typename... TestArgs> \
+ bool TestFunction(R(calling_convention* aFunc)(Args...), bool (*aPred)(R), \
+ TestArgs&&... aArgs) { \
+ using ArgTuple = std::tuple<Args...>; \
+ using Indices = std::index_sequence_for<Args...>; \
+ ArgTuple fakeArgs{std::forward<TestArgs>(aArgs)...}; \
+ patched_func_called = false; \
+ return aPred(Apply(aFunc, std::forward<ArgTuple>(fakeArgs), Indices())) && \
+ patched_func_called; \
+ } \
+ \
+ /* Specialization for functions returning void */ \
+ template <typename PredT, typename... Args, typename... TestArgs> \
+ bool TestFunction(void(calling_convention * aFunc)(Args...), PredT, \
+ TestArgs&&... aArgs) { \
+ using ArgTuple = std::tuple<Args...>; \
+ using Indices = std::index_sequence_for<Args...>; \
+ ArgTuple fakeArgs{std::forward<TestArgs>(aArgs)...}; \
+ patched_func_called = false; \
+ Apply(aFunc, std::forward<ArgTuple>(fakeArgs), Indices()); \
+ return patched_func_called; \
+ }
+
+// C++11 allows empty arguments to macros. clang works just fine. MSVC does the
+// right thing, but it also throws up warning C4003.
+#if defined(_MSC_VER) && !defined(__clang__)
+DEFINE_TEST_FUNCTION(__cdecl)
+#else
+DEFINE_TEST_FUNCTION()
+#endif
+
+#ifdef _M_IX86
+DEFINE_TEST_FUNCTION(__stdcall)
+DEFINE_TEST_FUNCTION(__fastcall)
+#endif // _M_IX86
+
+// Test the hooked function against the supplied predicate
+template <typename OrigFuncT, typename PredicateT, typename... Args>
+bool CheckHook(OrigFuncT& aOrigFunc, const char* aDllName,
+ const char* aFuncName, PredicateT&& aPred, Args&&... aArgs) {
+ if (TestFunction(aOrigFunc, std::forward<PredicateT>(aPred),
+ std::forward<Args>(aArgs)...)) {
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "Executed hooked function %s from %s\n",
+ aFuncName, aDllName);
+ fflush(stdout);
+ return true;
+ }
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Failed to execute hooked function %s from %s\n",
+ aFuncName, aDllName);
+ return false;
+}
+
+struct InterceptorFunction {
+ static const size_t EXEC_MEMBLOCK_SIZE = 64 * 1024; // 64K
+
+ static InterceptorFunction& Create() {
+ // Make sure the executable memory is allocated
+ if (!sBlock) {
+ Init();
+ }
+ MOZ_ASSERT(sBlock);
+
+ // Make sure we aren't making more functions than we allocated room for
+ MOZ_RELEASE_ASSERT((sNumInstances + 1) * sizeof(InterceptorFunction) <=
+ EXEC_MEMBLOCK_SIZE);
+
+ // Grab the next InterceptorFunction from executable memory
+ InterceptorFunction& ret = *reinterpret_cast<InterceptorFunction*>(
+ sBlock + (sNumInstances++ * sizeof(InterceptorFunction)));
+
+ // Set the InterceptorFunction to the code template.
+ auto funcCode = &ret[0];
+ memcpy(funcCode, sInterceptorTemplate, TemplateLength);
+
+ // Fill in the patched_func_called pointer in the template.
+ auto pfPtr =
+ reinterpret_cast<volatile bool**>(&ret[PatchedFuncCalledIndex]);
+ *pfPtr = &patched_func_called;
+ return ret;
+ }
+
+ uint8_t& operator[](size_t i) { return mFuncCode[i]; }
+
+ uint8_t* GetFunction() { return mFuncCode; }
+
+ void SetStub(uintptr_t aStub) {
+ auto pfPtr = reinterpret_cast<uintptr_t*>(&mFuncCode[StubFuncIndex]);
+ *pfPtr = aStub;
+ }
+
+ private:
+ // We intercept functions with short machine-code functions that set a boolean
+ // and run the stub that launches the original function. Each entry in the
+ // array is the code for one of those interceptor functions. We cannot
+ // free this memory until the test shuts down.
+ // The templates have spots for the address of patched_func_called
+ // and for the address of the stub function. Their indices in the byte
+ // array are given as constants below and they appear as blocks of
+ // 0xff bytes in the templates.
+#if defined(_M_X64)
+ // 0: 48 b8 ff ff ff ff ff ff ff ff movabs rax, &patched_func_called
+ // a: c6 00 01 mov BYTE PTR [rax],0x1
+ // d: 48 b8 ff ff ff ff ff ff ff ff movabs rax, &stub_func_ptr
+ // 17: ff e0 jmp rax
+ static constexpr uint8_t sInterceptorTemplate[] = {
+ 0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xC6, 0x00, 0x01, 0x48, 0xB8, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0};
+ static const size_t PatchedFuncCalledIndex = 0x2;
+ static const size_t StubFuncIndex = 0xf;
+#elif defined(_M_IX86)
+ // 0: c6 05 ff ff ff ff 01 mov BYTE PTR &patched_func_called, 0x1
+ // 7: 68 ff ff ff ff push &stub_func_ptr
+ // c: c3 ret
+ static constexpr uint8_t sInterceptorTemplate[] = {
+ 0xC6, 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0x01,
+ 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3};
+ static const size_t PatchedFuncCalledIndex = 0x2;
+ static const size_t StubFuncIndex = 0x8;
+#elif defined(_M_ARM64)
+ // 0: 31 00 80 52 movz w17, #0x1
+ // 4: 90 00 00 58 ldr x16, #16
+ // 8: 11 02 00 39 strb w17, [x16]
+ // c: 90 00 00 58 ldr x16, #16
+ // 10: 00 02 1F D6 br x16
+ // 14: &patched_func_called
+ // 1c: &stub_func_ptr
+ static constexpr uint8_t sInterceptorTemplate[] = {
+ 0x31, 0x00, 0x80, 0x52, 0x90, 0x00, 0x00, 0x58, 0x11, 0x02, 0x00, 0x39,
+ 0x90, 0x00, 0x00, 0x58, 0x00, 0x02, 0x1F, 0xD6, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+ static const size_t PatchedFuncCalledIndex = 0x14;
+ static const size_t StubFuncIndex = 0x1c;
+#else
+# error "Missing template for architecture"
+#endif
+
+ static const size_t TemplateLength = sizeof(sInterceptorTemplate);
+ uint8_t mFuncCode[TemplateLength];
+
+ InterceptorFunction() = delete;
+ InterceptorFunction(const InterceptorFunction&) = delete;
+ InterceptorFunction& operator=(const InterceptorFunction&) = delete;
+
+ static void Init() {
+ MOZ_ASSERT(!sBlock);
+ sBlock = reinterpret_cast<uint8_t*>(
+ ::VirtualAlloc(nullptr, EXEC_MEMBLOCK_SIZE, MEM_RESERVE | MEM_COMMIT,
+ PAGE_EXECUTE_READWRITE));
+ }
+
+ static uint8_t* sBlock;
+ static size_t sNumInstances;
+};
+
+uint8_t* InterceptorFunction::sBlock = nullptr;
+size_t InterceptorFunction::sNumInstances = 0;
+
+constexpr uint8_t InterceptorFunction::sInterceptorTemplate[];
+
+#ifdef _M_X64
+
+// To check that unwind information propagates from hooked functions to their
+// stubs, we need to find the real address where the detoured code lives.
+class RedirectionResolver : public interceptor::WindowsDllPatcherBase<
+ interceptor::VMSharingPolicyShared> {
+ public:
+ uintptr_t ResolveRedirectedAddressForTest(FARPROC aFunc) {
+ bool isWin8 = IsWin8OrLater() && (!IsWin8Point1OrLater());
+
+ bool isDuplicateHandle = (reinterpret_cast<void*>(aFunc) ==
+ reinterpret_cast<void*>(&::DuplicateHandle));
+
+ // We need to reproduce the behavior of WindowsDllInterceptor::AddDetour
+ // with respect to redirection, including the corner case for bug 1659398.
+ if (isWin8 && isDuplicateHandle) {
+ return reinterpret_cast<uintptr_t>(aFunc);
+ }
+
+ return ResolveRedirectedAddress(aFunc).GetAddress();
+ }
+};
+
+#endif // _M_X64
+
+// Hook the function and optionally attempt calling it
+template <typename OrigFuncT, size_t N, typename PredicateT, typename... Args>
+bool TestHook(const char (&dll)[N], const char* func, PredicateT&& aPred,
+ Args&&... aArgs) {
+ auto orig_func(
+ mozilla::MakeUnique<WindowsDllInterceptor::FuncHookType<OrigFuncT>>());
+ wchar_t dllW[N];
+ std::copy(std::begin(dll), std::end(dll), std::begin(dllW));
+
+ HMODULE module = ::LoadLibraryW(dllW);
+ FARPROC funcAddr = ::GetProcAddress(module, func);
+ if (!funcAddr) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Failed to find %s from "
+ "%s\n",
+ func, dll);
+ fflush(stdout);
+ return false;
+ }
+
+#ifdef _M_X64
+
+ // Resolve what is the actual address of the code that will be detoured, as
+ // that's the code we want to compare with when we check for unwind
+ // information. Do that *before* detouring, although the address will only be
+ // used after detouring.
+ RedirectionResolver resolver;
+ auto detouredCodeAddr = resolver.ResolveRedirectedAddressForTest(funcAddr);
+
+#endif // _M_X64
+
+ bool successful = false;
+ WindowsDllInterceptor TestIntercept;
+ TestIntercept.Init(dll);
+
+ InterceptorFunction& interceptorFunc = InterceptorFunction::Create();
+ successful = orig_func->Set(
+ TestIntercept, func,
+ reinterpret_cast<OrigFuncT>(interceptorFunc.GetFunction()));
+
+ if (successful) {
+ auto stub = reinterpret_cast<uintptr_t>(orig_func->GetStub());
+ interceptorFunc.SetStub(stub);
+ printf("TEST-PASS | WindowsDllInterceptor | Could hook %s from %s\n", func,
+ dll);
+ fflush(stdout);
+
+#ifdef _M_X64
+
+ // Check that unwind information has been added if and only if it was
+ // present for the original detoured code.
+ uintptr_t funcImageBase = 0;
+ auto funcEntry =
+ RtlLookupFunctionEntry(detouredCodeAddr, &funcImageBase, nullptr);
+ bool funcHasUnwindInfo = bool(funcEntry);
+
+ uintptr_t stubImageBase = 0;
+ auto stubEntry = RtlLookupFunctionEntry(stub, &stubImageBase, nullptr);
+ bool stubHasUnwindInfo = bool(stubEntry);
+
+ if (funcHasUnwindInfo == stubHasUnwindInfo) {
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | The hook for %s from %s and "
+ "the original function are coherent with respect to unwind info: "
+ "funcHasUnwindInfo (%d) == stubHasUnwindInfo (%d).\n",
+ func, dll, funcHasUnwindInfo, stubHasUnwindInfo);
+ fflush(stdout);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Hook for %s from %s "
+ "and the original function are not coherent with respect to unwind "
+ "info: "
+ "funcHasUnwindInfo (%d) != stubHasUnwindInfo (%d).\n",
+ func, dll, funcHasUnwindInfo, stubHasUnwindInfo);
+ fflush(stdout);
+ return false;
+ }
+
+ if (stubHasUnwindInfo) {
+ if (stub == (stubImageBase + stubEntry->BeginAddress)) {
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | The hook for %s from %s has "
+ "coherent unwind info.\n",
+ func, dll);
+ fflush(stdout);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | The hook for %s "
+ " from %s has incoherent unwind info.\n",
+ func, dll);
+ fflush(stdout);
+ return false;
+ }
+ }
+
+#endif // _M_X64
+
+ if (!aPred) {
+ printf(
+ "TEST-SKIPPED | WindowsDllInterceptor | "
+ "Will not attempt to execute patched %s.\n",
+ func);
+ fflush(stdout);
+ return true;
+ }
+
+ // Test the DLL function we just hooked.
+ return CheckHook(reinterpret_cast<OrigFuncT&>(funcAddr), dll, func,
+ std::forward<PredicateT>(aPred),
+ std::forward<Args>(aArgs)...);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Failed to hook %s from "
+ "%s\n",
+ func, dll);
+ fflush(stdout);
+
+ // Print out the function's bytes so that we can easily analyze the error.
+ nsModuleHandle mod(::LoadLibraryW(dllW));
+ FARPROC funcAddr = ::GetProcAddress(mod, func);
+ if (funcAddr) {
+ const uint32_t kNumBytesToDump =
+ WindowsDllInterceptor::GetWorstCaseRequiredBytesToPatch();
+
+ printf("\tFirst %u bytes of function:\n\t", kNumBytesToDump);
+
+ auto code = reinterpret_cast<const uint8_t*>(funcAddr);
+ for (uint32_t i = 0; i < kNumBytesToDump; ++i) {
+ char suffix = (i < (kNumBytesToDump - 1)) ? ' ' : '\n';
+ printf("%02hhX%c", code[i], suffix);
+ }
+
+ fflush(stdout);
+ }
+ return false;
+ }
+}
+
+// Detour the function and optionally attempt calling it
+template <typename OrigFuncT, size_t N, typename PredicateT>
+bool TestDetour(const char (&dll)[N], const char* func, PredicateT&& aPred) {
+ auto orig_func(
+ mozilla::MakeUnique<WindowsDllInterceptor::FuncHookType<OrigFuncT>>());
+ wchar_t dllW[N];
+ std::copy(std::begin(dll), std::end(dll), std::begin(dllW));
+
+ bool successful = false;
+ WindowsDllInterceptor TestIntercept;
+ TestIntercept.Init(dll);
+
+ InterceptorFunction& interceptorFunc = InterceptorFunction::Create();
+ successful = orig_func->Set(
+ TestIntercept, func,
+ reinterpret_cast<OrigFuncT>(interceptorFunc.GetFunction()));
+
+ if (successful) {
+ interceptorFunc.SetStub(reinterpret_cast<uintptr_t>(orig_func->GetStub()));
+ printf("TEST-PASS | WindowsDllInterceptor | Could detour %s from %s\n",
+ func, dll);
+ fflush(stdout);
+ if (!aPred) {
+ printf(
+ "TEST-SKIPPED | WindowsDllInterceptor | "
+ "Will not attempt to execute patched %s.\n",
+ func);
+ fflush(stdout);
+ return true;
+ }
+
+ // Test the DLL function we just hooked.
+ HMODULE module = ::LoadLibraryW(dllW);
+ FARPROC funcAddr = ::GetProcAddress(module, func);
+ if (!funcAddr) {
+ return false;
+ }
+
+ return CheckHook(reinterpret_cast<OrigFuncT&>(funcAddr), dll, func,
+ std::forward<PredicateT>(aPred));
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Failed to detour %s "
+ "from %s\n",
+ func, dll);
+ fflush(stdout);
+ return false;
+ }
+}
+
+// If a function pointer's type returns void*, this template converts that type
+// to return uintptr_t instead, for the purposes of predicates.
+template <typename FuncT>
+struct SubstituteForVoidPtr {
+ using Type = FuncT;
+};
+
+template <typename... Args>
+struct SubstituteForVoidPtr<void* (*)(Args...)> {
+ using Type = uintptr_t (*)(Args...);
+};
+
+#ifdef _M_IX86
+template <typename... Args>
+struct SubstituteForVoidPtr<void*(__stdcall*)(Args...)> {
+ using Type = uintptr_t(__stdcall*)(Args...);
+};
+
+template <typename... Args>
+struct SubstituteForVoidPtr<void*(__fastcall*)(Args...)> {
+ using Type = uintptr_t(__fastcall*)(Args...);
+};
+#endif // _M_IX86
+
+// Determines the function's return type
+template <typename FuncT>
+struct ReturnType;
+
+template <typename R, typename... Args>
+struct ReturnType<R (*)(Args...)> {
+ using Type = R;
+};
+
+#ifdef _M_IX86
+template <typename R, typename... Args>
+struct ReturnType<R(__stdcall*)(Args...)> {
+ using Type = R;
+};
+
+template <typename R, typename... Args>
+struct ReturnType<R(__fastcall*)(Args...)> {
+ using Type = R;
+};
+#endif // _M_IX86
+
+// Predicates that may be supplied during tests
+template <typename FuncT>
+struct Predicates {
+ using ArgType = typename ReturnType<FuncT>::Type;
+
+ template <ArgType CompVal>
+ static bool Equals(ArgType aValue) {
+ return CompVal == aValue;
+ }
+
+ template <ArgType CompVal>
+ static bool NotEquals(ArgType aValue) {
+ return CompVal != aValue;
+ }
+
+ template <ArgType CompVal>
+ static bool Ignore(ArgType aValue) {
+ return true;
+ }
+};
+
+// Functions that return void should be ignored, so we specialize the
+// Ignore predicate for that case. Use nullptr as the value to compare against.
+template <typename... Args>
+struct Predicates<void (*)(Args...)> {
+ template <nullptr_t DummyVal>
+ static bool Ignore() {
+ return true;
+ }
+};
+
+#ifdef _M_IX86
+template <typename... Args>
+struct Predicates<void(__stdcall*)(Args...)> {
+ template <nullptr_t DummyVal>
+ static bool Ignore() {
+ return true;
+ }
+};
+
+template <typename... Args>
+struct Predicates<void(__fastcall*)(Args...)> {
+ template <nullptr_t DummyVal>
+ static bool Ignore() {
+ return true;
+ }
+};
+#endif // _M_IX86
+
+// The standard test. Hook |func|, and then try executing it with all zero
+// arguments, using |pred| and |comp| to determine whether the call successfully
+// executed. In general, you want set pred and comp such that they return true
+// when the function is returning whatever value is expected with all-zero
+// arguments.
+//
+// Note: When |func| returns void, you must supply |Ignore| and |nullptr| as the
+// |pred| and |comp| arguments, respectively.
+#define TEST_HOOK_HELPER(dll, func, pred, comp) \
+ TestHook<decltype(&func)>(dll, #func, \
+ &Predicates<decltype(&func)>::pred<comp>)
+
+#define TEST_HOOK(dll, func, pred, comp) TEST_HOOK_HELPER(dll, func, pred, comp)
+
+// We need to special-case functions that return INVALID_HANDLE_VALUE
+// (ie, CreateFile). Our template machinery for comparing values doesn't work
+// with integer constants passed as pointers (well, it works on MSVC, but not
+// clang, because that is not standard-compliant).
+#define TEST_HOOK_FOR_INVALID_HANDLE_VALUE(dll, func) \
+ TestHook<SubstituteForVoidPtr<decltype(&func)>::Type>( \
+ dll, #func, \
+ &Predicates<SubstituteForVoidPtr<decltype(&func)>::Type>::Equals< \
+ uintptr_t(-1)>)
+
+// This variant allows you to explicitly supply arguments to the hooked function
+// during testing. You want to provide arguments that produce the conditions
+// that induce the function to return a value that is accepted by your
+// predicate.
+#define TEST_HOOK_PARAMS(dll, func, pred, comp, ...) \
+ TestHook<decltype(&func)>( \
+ dll, #func, &Predicates<decltype(&func)>::pred<comp>, __VA_ARGS__)
+
+// This is for cases when we want to hook |func|, but it is unsafe to attempt
+// to execute the function in the context of a test.
+#define TEST_HOOK_SKIP_EXEC(dll, func) \
+ TestHook<decltype(&func)>( \
+ dll, #func, \
+ reinterpret_cast<bool (*)(typename ReturnType<decltype(&func)>::Type)>( \
+ NULL))
+
+// The following three variants are identical to the previous macros,
+// however the forcibly use a Detour on 32-bit Windows. On 64-bit Windows,
+// these macros are identical to their TEST_HOOK variants.
+#define TEST_DETOUR(dll, func, pred, comp) \
+ TestDetour<decltype(&func)>(dll, #func, \
+ &Predicates<decltype(&func)>::pred<comp>)
+
+#define TEST_DETOUR_PARAMS(dll, func, pred, comp, ...) \
+ TestDetour<decltype(&func)>( \
+ dll, #func, &Predicates<decltype(&func)>::pred<comp>, __VA_ARGS__)
+
+#define TEST_DETOUR_SKIP_EXEC(dll, func) \
+ TestDetour<decltype(&func)>( \
+ dll, #func, \
+ reinterpret_cast<bool (*)(typename ReturnType<decltype(&func)>::Type)>( \
+ NULL))
+
+template <typename OrigFuncT, size_t N, typename PredicateT, typename... Args>
+bool MaybeTestHook(const bool cond, const char (&dll)[N], const char* func,
+ PredicateT&& aPred, Args&&... aArgs) {
+ if (!cond) {
+ printf(
+ "TEST-SKIPPED | WindowsDllInterceptor | Skipped hook test for %s from "
+ "%s\n",
+ func, dll);
+ fflush(stdout);
+ return true;
+ }
+
+ return TestHook<OrigFuncT>(dll, func, std::forward<PredicateT>(aPred),
+ std::forward<Args>(aArgs)...);
+}
+
+// Like TEST_HOOK, but the test is only executed when cond is true.
+#define MAYBE_TEST_HOOK(cond, dll, func, pred, comp) \
+ MaybeTestHook<decltype(&func)>(cond, dll, #func, \
+ &Predicates<decltype(&func)>::pred<comp>)
+
+#define MAYBE_TEST_HOOK_PARAMS(cond, dll, func, pred, comp, ...) \
+ MaybeTestHook<decltype(&func)>( \
+ cond, dll, #func, &Predicates<decltype(&func)>::pred<comp>, __VA_ARGS__)
+
+#define MAYBE_TEST_HOOK_SKIP_EXEC(cond, dll, func) \
+ MaybeTestHook<decltype(&func)>( \
+ cond, dll, #func, \
+ reinterpret_cast<bool (*)(typename ReturnType<decltype(&func)>::Type)>( \
+ NULL))
+
+bool ShouldTestTipTsf() {
+ if (!IsWin8OrLater()) {
+ return false;
+ }
+
+ mozilla::DynamicallyLinkedFunctionPtr<decltype(&SHGetKnownFolderPath)>
+ pSHGetKnownFolderPath(L"shell32.dll", "SHGetKnownFolderPath");
+ if (!pSHGetKnownFolderPath) {
+ return false;
+ }
+
+ PWSTR commonFilesPath = nullptr;
+ if (FAILED(pSHGetKnownFolderPath(FOLDERID_ProgramFilesCommon, 0, nullptr,
+ &commonFilesPath))) {
+ return false;
+ }
+
+ wchar_t fullPath[MAX_PATH + 1] = {};
+ wcscpy(fullPath, commonFilesPath);
+ wcscat(fullPath, L"\\Microsoft Shared\\Ink\\tiptsf.dll");
+ CoTaskMemFree(commonFilesPath);
+
+ if (!LoadLibraryW(fullPath)) {
+ return false;
+ }
+
+ // Leak the module so that it's loaded for the interceptor test
+ return true;
+}
+
+static const wchar_t gEmptyUnicodeStringLiteral[] = L"";
+static UNICODE_STRING gEmptyUnicodeString;
+static BOOLEAN gIsPresent;
+
+bool HasApiSetQueryApiSetPresence() {
+ mozilla::DynamicallyLinkedFunctionPtr<decltype(&ApiSetQueryApiSetPresence)>
+ func(L"Api-ms-win-core-apiquery-l1-1-0.dll", "ApiSetQueryApiSetPresence");
+ if (!func) {
+ return false;
+ }
+
+ // Prepare gEmptyUnicodeString for the test
+ ::RtlInitUnicodeString(&gEmptyUnicodeString, gEmptyUnicodeStringLiteral);
+
+ return true;
+}
+
+// Set this to true to test function unhooking (currently broken).
+const bool ShouldTestUnhookFunction = false;
+
+#if defined(_M_X64) || defined(_M_ARM64)
+
+// Use VMSharingPolicyUnique for the ShortInterceptor, as it needs to
+// reserve its trampoline memory in a special location.
+using ShortInterceptor = mozilla::interceptor::WindowsDllInterceptor<
+ mozilla::interceptor::VMSharingPolicyUnique<
+ mozilla::interceptor::MMPolicyInProcess>>;
+
+static ShortInterceptor::FuncHookType<decltype(&::NtMapViewOfSection)>
+ orig_NtMapViewOfSection;
+
+#endif // defined(_M_X64) || defined(_M_ARM64)
+
+bool TestShortDetour() {
+#if defined(_M_X64) || defined(_M_ARM64)
+ auto pNtMapViewOfSection = reinterpret_cast<decltype(&::NtMapViewOfSection)>(
+ ::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"), "NtMapViewOfSection"));
+ if (!pNtMapViewOfSection) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Failed to resolve ntdll!NtMapViewOfSection\n");
+ fflush(stdout);
+ return false;
+ }
+
+ { // Scope for shortInterceptor
+ ShortInterceptor shortInterceptor;
+ shortInterceptor.TestOnlyDetourInit(
+ L"ntdll.dll",
+ mozilla::interceptor::DetourFlags::eTestOnlyForceShortPatch);
+
+ InterceptorFunction& interceptorFunc = InterceptorFunction::Create();
+ if (!orig_NtMapViewOfSection.SetDetour(
+ shortInterceptor, "NtMapViewOfSection",
+ reinterpret_cast<decltype(&::NtMapViewOfSection)>(
+ interceptorFunc.GetFunction()))) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Failed to hook ntdll!NtMapViewOfSection via 10-byte patch\n");
+ fflush(stdout);
+ return false;
+ }
+
+ interceptorFunc.SetStub(
+ reinterpret_cast<uintptr_t>(orig_NtMapViewOfSection.GetStub()));
+
+ auto pred =
+ &Predicates<decltype(&::NtMapViewOfSection)>::Ignore<((NTSTATUS)0)>;
+
+ if (!CheckHook(pNtMapViewOfSection, "ntdll.dll", "NtMapViewOfSection",
+ pred)) {
+ // CheckHook has already printed the error message for us
+ return false;
+ }
+ }
+
+ // Now ensure that our hook cleanup worked
+ if (ShouldTestUnhookFunction) {
+ NTSTATUS status =
+ pNtMapViewOfSection(nullptr, nullptr, nullptr, 0, 0, nullptr, nullptr,
+ ((SECTION_INHERIT)0), 0, 0);
+ if (NT_SUCCESS(status)) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Unexpected successful call to ntdll!NtMapViewOfSection after "
+ "removing short-patched hook\n");
+ fflush(stdout);
+ return false;
+ }
+
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "Successfully unhooked ntdll!NtMapViewOfSection via short patch\n");
+ fflush(stdout);
+ }
+
+ return true;
+#else
+ return true;
+#endif
+}
+
+constexpr uintptr_t NoStubAddressCheck = 0;
+constexpr uintptr_t ExpectedFail = 1;
+struct TestCase {
+ const char* mFunctionName;
+ uintptr_t mExpectedStub;
+ bool mPatchedOnce;
+ explicit TestCase(const char* aFunctionName, uintptr_t aExpectedStub)
+ : mFunctionName(aFunctionName),
+ mExpectedStub(aExpectedStub),
+ mPatchedOnce(false) {}
+} g_AssemblyTestCases[] = {
+#if defined(__clang__)
+// We disable these testcases because the code coverage instrumentation injects
+// code in a way that WindowsDllInterceptor doesn't understand.
+# ifndef MOZ_CODE_COVERAGE
+# if defined(_M_X64)
+ // Since we have PatchIfTargetIsRecognizedTrampoline for x64, we expect the
+ // original jump destination is returned as a stub.
+ TestCase("MovPushRet", JumpDestination),
+ TestCase("MovRaxJump", JumpDestination),
+ TestCase("DoubleJump", JumpDestination),
+
+ // Passing NoStubAddressCheck as the following testcases return
+ // a trampoline address instead of the original destination.
+ TestCase("NearJump", NoStubAddressCheck),
+ TestCase("OpcodeFF", NoStubAddressCheck),
+ TestCase("IndirectCall", NoStubAddressCheck),
+ TestCase("MovImm64", NoStubAddressCheck),
+# elif defined(_M_IX86)
+ // Skip the stub address check as we always generate a trampoline for x86.
+ TestCase("PushRet", NoStubAddressCheck),
+ TestCase("MovEaxJump", NoStubAddressCheck),
+ TestCase("DoubleJump", NoStubAddressCheck),
+ TestCase("Opcode83", NoStubAddressCheck),
+ TestCase("LockPrefix", NoStubAddressCheck),
+ TestCase("LooksLikeLockPrefix", NoStubAddressCheck),
+# endif
+# if !defined(DEBUG)
+ // Skip on Debug build because it hits MOZ_ASSERT_UNREACHABLE.
+ TestCase("UnsupportedOp", ExpectedFail),
+# endif // !defined(DEBUG)
+# endif // MOZ_CODE_COVERAGE
+#endif // defined(__clang__)
+};
+
+template <typename InterceptorType>
+bool TestAssemblyFunctions() {
+ static const auto patchedFunction = []() { patched_func_called = true; };
+
+ InterceptorType interceptor;
+ interceptor.Init("TestDllInterceptor.exe");
+
+ for (auto& testCase : g_AssemblyTestCases) {
+ if (testCase.mExpectedStub == NoStubAddressCheck && testCase.mPatchedOnce) {
+ // For the testcases with NoStubAddressCheck, we revert a hook by
+ // jumping into the original stub, which is not detourable again.
+ continue;
+ }
+
+ typename InterceptorType::template FuncHookType<void (*)()> hook;
+ bool result =
+ hook.Set(interceptor, testCase.mFunctionName, patchedFunction);
+ if (testCase.mExpectedStub == ExpectedFail) {
+ if (result) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Unexpectedly succeeded to detour %s.\n",
+ testCase.mFunctionName);
+ return false;
+ }
+#if defined(NIGHTLY_BUILD)
+ const Maybe<DetourError>& maybeError = interceptor.GetLastDetourError();
+ if (maybeError.isNothing()) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "DetourError was not set on detour error.\n");
+ return false;
+ }
+ if (maybeError.ref().mErrorCode !=
+ DetourResultCode::DETOUR_PATCHER_CREATE_TRAMPOLINE_ERROR) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "A wrong detour errorcode was set on detour error.\n");
+ return false;
+ }
+#endif // defined(NIGHTLY_BUILD)
+ printf("TEST-PASS | WindowsDllInterceptor | %s\n",
+ testCase.mFunctionName);
+ continue;
+ }
+
+ if (!result) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Failed to detour %s.\n",
+ testCase.mFunctionName);
+ return false;
+ }
+
+ testCase.mPatchedOnce = true;
+
+ const auto actualStub = reinterpret_cast<uintptr_t>(hook.GetStub());
+ if (testCase.mExpectedStub != NoStubAddressCheck &&
+ actualStub != testCase.mExpectedStub) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Wrong stub was backed up for %s: %zx\n",
+ testCase.mFunctionName, actualStub);
+ return false;
+ }
+
+ patched_func_called = false;
+
+ auto originalFunction = reinterpret_cast<void (*)()>(
+ GetProcAddress(GetModuleHandleW(nullptr), testCase.mFunctionName));
+ originalFunction();
+
+ if (!patched_func_called) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Hook from %s was not called\n",
+ testCase.mFunctionName);
+ return false;
+ }
+
+ printf("TEST-PASS | WindowsDllInterceptor | %s\n", testCase.mFunctionName);
+ }
+
+ return true;
+}
+
+#if defined(_M_X64) && !defined(MOZ_CODE_COVERAGE)
+// We want to test hooking and unhooking with unwind information, so we need:
+// - a VMSharingPolicy such that ShouldUnhookUponDestruction() is true and
+// Items() is implemented;
+// - a MMPolicy such that ShouldUnhookUponDestruction() is true and
+// kSupportsUnwindInfo is true.
+using DetouredCallInterceptor = mozilla::interceptor::WindowsDllInterceptor<
+ mozilla::interceptor::VMSharingPolicyUnique<
+ mozilla::interceptor::MMPolicyInProcess>>;
+
+struct DetouredCallChunk {
+ alignas(uint32_t) RUNTIME_FUNCTION functionTable[1];
+ alignas(uint32_t) uint8_t unwindInfo[sizeof(gDetouredCallUnwindInfo)];
+ uint8_t code[gDetouredCallCodeSize];
+};
+
+// Unfortunately using RtlAddFunctionTable for static code that lives within
+// a module doesn't seem to work. Presumably it conflicts with the static
+// function tables. So we recreate gDetouredCall as dynamic code to be able to
+// associate it with unwind information.
+decltype(&DetouredCallCode) gDetouredCall =
+ []() -> decltype(&DetouredCallCode) {
+ // We first adjust the detoured call jumper from:
+ // ff 25 00 00 00 00 jmp qword ptr [rip + 0]
+ // to:
+ // ff 25 XX XX XX XX jmp qword ptr [rip + offset gDetouredCall]
+ uint8_t bytes[6]{0xff, 0x25, 0, 0, 0, 0};
+ if (0 != memcmp(bytes, reinterpret_cast<void*>(DetouredCallJumper),
+ sizeof bytes)) {
+ return nullptr;
+ }
+
+ DWORD oldProtect{};
+ if (!VirtualProtect(reinterpret_cast<void*>(DetouredCallJumper), sizeof bytes,
+ PAGE_READWRITE, &oldProtect)) {
+ return nullptr;
+ }
+
+ *reinterpret_cast<uint32_t*>(&bytes[2]) = static_cast<uint32_t>(
+ reinterpret_cast<uintptr_t>(&gDetouredCall) -
+ (reinterpret_cast<uintptr_t>(DetouredCallJumper) + sizeof bytes));
+ memcpy(reinterpret_cast<void*>(DetouredCallJumper), bytes, sizeof bytes);
+
+ if (!VirtualProtect(reinterpret_cast<void*>(DetouredCallJumper), sizeof bytes,
+ oldProtect, &oldProtect)) {
+ return nullptr;
+ }
+
+ auto detouredCallChunk = reinterpret_cast<DetouredCallChunk*>(
+ VirtualAlloc(nullptr, sizeof(DetouredCallChunk), MEM_RESERVE | MEM_COMMIT,
+ PAGE_READWRITE));
+ if (!detouredCallChunk) {
+ return nullptr;
+ }
+
+ detouredCallChunk->functionTable[0].BeginAddress =
+ offsetof(DetouredCallChunk, code);
+ detouredCallChunk->functionTable[0].EndAddress =
+ offsetof(DetouredCallChunk, code) + gDetouredCallCodeSize;
+ detouredCallChunk->functionTable[0].UnwindData =
+ offsetof(DetouredCallChunk, unwindInfo);
+ memcpy(reinterpret_cast<void*>(&detouredCallChunk->unwindInfo),
+ reinterpret_cast<void*>(gDetouredCallUnwindInfo),
+ sizeof(detouredCallChunk->unwindInfo));
+ memcpy(reinterpret_cast<void*>(&detouredCallChunk->code[0]),
+ reinterpret_cast<void*>(DetouredCallCode),
+ sizeof(detouredCallChunk->code));
+
+ if (!VirtualProtect(reinterpret_cast<void*>(detouredCallChunk),
+ sizeof(DetouredCallChunk), PAGE_EXECUTE_READ,
+ &oldProtect)) {
+ VirtualFree(detouredCallChunk, 0, MEM_RELEASE);
+ return nullptr;
+ }
+
+ if (!RtlAddFunctionTable(detouredCallChunk->functionTable, 1,
+ reinterpret_cast<uintptr_t>(detouredCallChunk))) {
+ VirtualFree(detouredCallChunk, 0, MEM_RELEASE);
+ return nullptr;
+ }
+
+ return reinterpret_cast<decltype(&DetouredCallCode)>(detouredCallChunk->code);
+}();
+
+// We use our own variable instead of patched_func_called because the callee of
+// gDetouredCall could end up calling other, already hooked functions could
+// change patched_func_called.
+static volatile bool sCalledPatchedDetouredCall = false;
+static volatile bool sCalledDetouredCallCallee = false;
+static volatile bool sCouldUnwindFromDetouredCallCallee = false;
+void DetouredCallCallee() {
+ sCalledDetouredCallCallee = true;
+
+ // Check that we can fully unwind the stack
+ CONTEXT contextRecord{};
+ RtlCaptureContext(&contextRecord);
+ if (!contextRecord.Rip) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "DetouredCallCallee was unable to get an initial context to work "
+ "with\n");
+ fflush(stdout);
+ return;
+ }
+ while (contextRecord.Rip) {
+ DWORD64 imageBase = 0;
+ auto FunctionEntry =
+ RtlLookupFunctionEntry(contextRecord.Rip, &imageBase, nullptr);
+ if (!FunctionEntry) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "DetouredCallCallee was unable to get unwind info for ControlPc=%p\n",
+ reinterpret_cast<void*>(contextRecord.Rip));
+ fflush(stdout);
+ return;
+ }
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "DetouredCallCallee was able to get unwind info for ControlPc=%p\n",
+ reinterpret_cast<void*>(contextRecord.Rip));
+ fflush(stdout);
+ void* handlerData = nullptr;
+ DWORD64 establisherFrame = 0;
+ RtlVirtualUnwind(UNW_FLAG_NHANDLER, imageBase, contextRecord.Rip,
+ FunctionEntry, &contextRecord, &handlerData,
+ &establisherFrame, nullptr);
+ }
+ sCouldUnwindFromDetouredCallCallee = true;
+}
+
+static DetouredCallInterceptor::FuncHookType<decltype(&DetouredCallCode)>
+ orig_DetouredCall;
+
+static void patched_DetouredCall(uintptr_t aCallee) {
+ sCalledPatchedDetouredCall = true;
+ return orig_DetouredCall(aCallee);
+}
+
+bool TestCallingDetouredCall(const char* aTestDescription,
+ bool aExpectCalledPatchedDetouredCall) {
+ sCalledPatchedDetouredCall = false;
+ sCalledDetouredCallCallee = false;
+ sCouldUnwindFromDetouredCallCallee = false;
+ DetouredCallJumper(reinterpret_cast<uintptr_t>(DetouredCallCallee));
+
+ if (aExpectCalledPatchedDetouredCall != sCalledPatchedDetouredCall) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "%s: expectCalledPatchedDetouredCall (%d) differs from "
+ "sCalledPatchedDetouredCall (%d)\n",
+ aTestDescription, aExpectCalledPatchedDetouredCall,
+ sCalledPatchedDetouredCall);
+ fflush(stdout);
+ return false;
+ }
+
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "%s: expectCalledPatchedDetouredCall (%d) matches with "
+ "sCalledPatchedDetouredCall (%d)\n",
+ aTestDescription, aExpectCalledPatchedDetouredCall,
+ sCalledPatchedDetouredCall);
+ fflush(stdout);
+
+ if (!sCalledDetouredCallCallee) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "%s: gDetouredCall failed to call its callee\n",
+ aTestDescription);
+ fflush(stdout);
+ return false;
+ }
+
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "%s: gDetouredCall successfully called its callee\n",
+ aTestDescription);
+ fflush(stdout);
+
+ if (!sCouldUnwindFromDetouredCallCallee) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "%s: the callee of gDetouredCall failed to unwind\n",
+ aTestDescription);
+ fflush(stdout);
+ return false;
+ }
+
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "%s: the callee of gDetouredCall successfully unwinded\n",
+ aTestDescription);
+ fflush(stdout);
+ return true;
+}
+
+// Test that detouring a call preserves unwind information (bug 1798787).
+bool TestDetouredCallUnwindInfo() {
+ if (!gDetouredCall) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "Failed to generate dynamic gDetouredCall code\n");
+ fflush(stdout);
+ return false;
+ }
+
+ uintptr_t imageBase = 0;
+ if (!RtlLookupFunctionEntry(reinterpret_cast<uintptr_t>(gDetouredCall),
+ &imageBase, nullptr)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "Failed to find unwind information for dynamic gDetouredCall code\n");
+ fflush(stdout);
+ return false;
+ }
+
+ // We first double check that we manage to unwind when we *do not* detour
+ if (!TestCallingDetouredCall("Before hooking", false)) {
+ return false;
+ }
+
+ uintptr_t StubAddress = 0;
+
+ // The real test starts here: let's detour and check if we can still unwind
+ {
+ DetouredCallInterceptor ExeIntercept;
+ ExeIntercept.Init("TestDllInterceptor.exe");
+ if (!orig_DetouredCall.Set(ExeIntercept, "DetouredCallJumper",
+ &patched_DetouredCall)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "Failed to hook the detoured call jumper.\n");
+ fflush(stdout);
+ return false;
+ }
+
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "Successfully hooked the detoured call jumper.\n");
+ fflush(stdout);
+
+ StubAddress = reinterpret_cast<uintptr_t>(orig_DetouredCall.GetStub());
+ if (!RtlLookupFunctionEntry(StubAddress, &imageBase, nullptr)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "Failed to find unwind information for detoured code of "
+ "gDetouredCall\n");
+ fflush(stdout);
+ return false;
+ }
+
+ TestCallingDetouredCall("After hooking", true);
+ }
+
+ // Check that we can still unwind after clearing the hook.
+ return TestCallingDetouredCall("After unhooking", false);
+}
+#endif // defined(_M_X64) && !defined(MOZ_CODE_COVERAGE)
+
+bool TestDynamicCodePolicy() {
+ if (!IsWin8Point1OrLater()) {
+ // Skip if a platform does not support this policy.
+ return true;
+ }
+
+ PROCESS_MITIGATION_DYNAMIC_CODE_POLICY policy = {};
+ policy.ProhibitDynamicCode = true;
+
+ mozilla::DynamicallyLinkedFunctionPtr<decltype(&SetProcessMitigationPolicy)>
+ pSetProcessMitigationPolicy(L"kernel32.dll",
+ "SetProcessMitigationPolicy");
+ if (!pSetProcessMitigationPolicy) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "SetProcessMitigationPolicy does not exist.\n");
+ fflush(stdout);
+ return false;
+ }
+
+ if (!pSetProcessMitigationPolicy(ProcessDynamicCodePolicy, &policy,
+ sizeof(policy))) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "Fail to enable ProcessDynamicCodePolicy.\n");
+ fflush(stdout);
+ return false;
+ }
+
+ WindowsDllInterceptor ExeIntercept;
+ ExeIntercept.Init("TestDllInterceptor.exe");
+
+ // Make sure we fail to hook a function if ProcessDynamicCodePolicy is on
+ // because we cannot create an executable trampoline region.
+ if (orig_payloadNotHooked.Set(ExeIntercept, "payloadNotHooked",
+ &patched_rotatePayload)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "ProcessDynamicCodePolicy is not working.\n");
+ fflush(stdout);
+ return false;
+ }
+
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "Successfully passed TestDynamicCodePolicy.\n");
+ fflush(stdout);
+ return true;
+}
+
+extern "C" int wmain(int argc, wchar_t* argv[]) {
+ LARGE_INTEGER start;
+ QueryPerformanceCounter(&start);
+
+ // We disable this part of the test because the code coverage instrumentation
+ // injects code in rotatePayload in a way that WindowsDllInterceptor doesn't
+ // understand.
+#ifndef MOZ_CODE_COVERAGE
+ payload initial = {0x12345678, 0xfc4e9d31, 0x87654321};
+ payload p0, p1;
+ ZeroMemory(&p0, sizeof(p0));
+ ZeroMemory(&p1, sizeof(p1));
+
+ p0 = rotatePayload(initial);
+
+ {
+ WindowsDllInterceptor ExeIntercept;
+ ExeIntercept.Init("TestDllInterceptor.exe");
+ if (orig_rotatePayload.Set(ExeIntercept, "rotatePayload",
+ &patched_rotatePayload)) {
+ printf("TEST-PASS | WindowsDllInterceptor | Hook added\n");
+ fflush(stdout);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Failed to add "
+ "hook\n");
+ fflush(stdout);
+ return 1;
+ }
+
+ p1 = rotatePayload(initial);
+
+ if (patched_func_called) {
+ printf("TEST-PASS | WindowsDllInterceptor | Hook called\n");
+ fflush(stdout);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Hook was not "
+ "called\n");
+ fflush(stdout);
+ return 1;
+ }
+
+ if (p0 == p1) {
+ printf("TEST-PASS | WindowsDllInterceptor | Hook works properly\n");
+ fflush(stdout);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Hook didn't return "
+ "the right information\n");
+ fflush(stdout);
+ return 1;
+ }
+ }
+
+ patched_func_called = false;
+ ZeroMemory(&p1, sizeof(p1));
+
+ p1 = rotatePayload(initial);
+
+ if (ShouldTestUnhookFunction != patched_func_called) {
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | Hook was %scalled after "
+ "unregistration\n",
+ ShouldTestUnhookFunction ? "not " : "");
+ fflush(stdout);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Hook was %scalled "
+ "after unregistration\n",
+ ShouldTestUnhookFunction ? "" : "not ");
+ fflush(stdout);
+ return 1;
+ }
+
+ if (p0 == p1) {
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | Original function worked "
+ "properly\n");
+ fflush(stdout);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Original function "
+ "didn't return the right information\n");
+ fflush(stdout);
+ return 1;
+ }
+#endif
+
+ CredHandle credHandle;
+ memset(&credHandle, 0, sizeof(CredHandle));
+ OBJECT_ATTRIBUTES attributes = {};
+
+ // NB: These tests should be ordered such that lower-level APIs are tested
+ // before higher-level APIs.
+ if (TestShortDetour() &&
+ // Run <ShortInterceptor> first because <WindowsDllInterceptor>
+ // does not clean up hooks.
+#if defined(_M_X64)
+ TestAssemblyFunctions<ShortInterceptor>() &&
+#endif
+ TestAssemblyFunctions<WindowsDllInterceptor>() &&
+#ifdef _M_IX86
+ // We keep this test to hook complex code on x86. (Bug 850957)
+ TEST_HOOK("ntdll.dll", NtFlushBuffersFile, NotEquals, 0) &&
+#endif
+ TEST_HOOK("ntdll.dll", NtCreateFile, NotEquals, 0) &&
+ TEST_HOOK("ntdll.dll", NtReadFile, NotEquals, 0) &&
+ TEST_HOOK("ntdll.dll", NtReadFileScatter, NotEquals, 0) &&
+ TEST_HOOK("ntdll.dll", NtWriteFile, NotEquals, 0) &&
+ TEST_HOOK("ntdll.dll", NtWriteFileGather, NotEquals, 0) &&
+ TEST_HOOK_PARAMS("ntdll.dll", NtQueryFullAttributesFile, NotEquals, 0,
+ &attributes, nullptr) &&
+ TEST_DETOUR_SKIP_EXEC("ntdll.dll", LdrLoadDll) &&
+ TEST_HOOK("ntdll.dll", LdrUnloadDll, NotEquals, 0) &&
+ MAYBE_TEST_HOOK_SKIP_EXEC(IsWin8OrLater(), "ntdll.dll",
+ LdrResolveDelayLoadedAPI) &&
+ MAYBE_TEST_HOOK_PARAMS(HasApiSetQueryApiSetPresence(),
+ "Api-ms-win-core-apiquery-l1-1-0.dll",
+ ApiSetQueryApiSetPresence, Equals, FALSE,
+ &gEmptyUnicodeString, &gIsPresent) &&
+ TEST_HOOK("kernelbase.dll", QueryDosDeviceW, Equals, 0) &&
+ TEST_HOOK("kernel32.dll", GetFileAttributesW, Equals,
+ INVALID_FILE_ATTRIBUTES) &&
+#if !defined(_M_ARM64)
+# ifndef MOZ_ASAN
+ // Bug 733892: toolkit/crashreporter/nsExceptionHandler.cpp
+ // This fails on ASan because the ASan runtime already hooked this
+ // function
+ TEST_HOOK("kernel32.dll", SetUnhandledExceptionFilter, Ignore, nullptr) &&
+# endif
+#endif // !defined(_M_ARM64)
+#ifdef _M_IX86
+ TEST_HOOK_FOR_INVALID_HANDLE_VALUE("kernel32.dll", CreateFileW) &&
+#endif
+#if !defined(_M_ARM64)
+ TEST_HOOK_FOR_INVALID_HANDLE_VALUE("kernel32.dll", CreateFileA) &&
+#endif // !defined(_M_ARM64)
+#if !defined(_M_ARM64)
+ TEST_HOOK("kernel32.dll", TlsAlloc, NotEquals, TLS_OUT_OF_INDEXES) &&
+ TEST_HOOK_PARAMS("kernel32.dll", TlsFree, Equals, FALSE,
+ TLS_OUT_OF_INDEXES) &&
+ TEST_HOOK("kernel32.dll", CloseHandle, Equals, FALSE) &&
+ TEST_HOOK("kernel32.dll", DuplicateHandle, Equals, FALSE) &&
+#endif // !defined(_M_ARM64)
+ TEST_DETOUR_SKIP_EXEC("kernel32.dll", BaseThreadInitThunk) &&
+#if defined(_M_X64) || defined(_M_ARM64)
+ MAYBE_TEST_HOOK(!IsWin8OrLater(), "kernel32.dll",
+ RtlInstallFunctionTableCallback, Equals, FALSE) &&
+ TEST_HOOK("user32.dll", GetKeyState, Ignore, 0) && // see Bug 1316415
+#endif
+ TEST_HOOK("user32.dll", GetWindowInfo, Equals, FALSE) &&
+ TEST_HOOK("user32.dll", TrackPopupMenu, Equals, FALSE) &&
+ TEST_DETOUR("user32.dll", CreateWindowExW, Equals, nullptr) &&
+ TEST_HOOK("user32.dll", InSendMessageEx, Equals, ISMEX_NOSEND) &&
+ TEST_HOOK("user32.dll", SendMessageTimeoutW, Equals, 0) &&
+ TEST_HOOK("user32.dll", SetCursorPos, NotEquals, FALSE) &&
+ TEST_HOOK("bcrypt.dll", BCryptGenRandom, Equals,
+ static_cast<NTSTATUS>(STATUS_INVALID_HANDLE)) &&
+ TEST_HOOK("advapi32.dll", RtlGenRandom, Equals, TRUE) &&
+#if !defined(_M_ARM64)
+ TEST_HOOK("imm32.dll", ImmGetContext, Equals, nullptr) &&
+#endif // !defined(_M_ARM64)
+ TEST_HOOK("imm32.dll", ImmGetCompositionStringW, Ignore, 0) &&
+ TEST_HOOK_SKIP_EXEC("imm32.dll", ImmSetCandidateWindow) &&
+ TEST_HOOK("imm32.dll", ImmNotifyIME, Equals, 0) &&
+ TEST_HOOK("comdlg32.dll", GetSaveFileNameW, Ignore, FALSE) &&
+ TEST_HOOK("comdlg32.dll", GetOpenFileNameW, Ignore, FALSE) &&
+#if defined(_M_X64)
+ TEST_HOOK("comdlg32.dll", PrintDlgW, Ignore, 0) &&
+#endif
+ MAYBE_TEST_HOOK(ShouldTestTipTsf(), "tiptsf.dll", ProcessCaretEvents,
+ Ignore, nullptr) &&
+ TEST_HOOK("wininet.dll", InternetOpenA, NotEquals, nullptr) &&
+ TEST_HOOK("wininet.dll", InternetCloseHandle, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", InternetConnectA, Equals, nullptr) &&
+ TEST_HOOK("wininet.dll", InternetQueryDataAvailable, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", InternetReadFile, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", InternetWriteFile, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", InternetSetOptionA, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", HttpAddRequestHeadersA, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", HttpOpenRequestA, Equals, nullptr) &&
+ TEST_HOOK("wininet.dll", HttpQueryInfoA, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", HttpSendRequestA, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", HttpSendRequestExA, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", HttpEndRequestA, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", InternetQueryOptionA, Equals, FALSE) &&
+ TEST_HOOK("sspicli.dll", AcquireCredentialsHandleA, NotEquals,
+ SEC_E_OK) &&
+ TEST_HOOK_PARAMS("sspicli.dll", QueryCredentialsAttributesA, Equals,
+ SEC_E_INVALID_HANDLE, &credHandle, 0, nullptr) &&
+ TEST_HOOK_PARAMS("sspicli.dll", FreeCredentialsHandle, Equals,
+ SEC_E_INVALID_HANDLE, &credHandle) &&
+#if defined(_M_X64) && !defined(MOZ_CODE_COVERAGE)
+ TestDetouredCallUnwindInfo() &&
+#endif // defined(_M_X64) && !defined(MOZ_CODE_COVERAGE)
+ // Run TestDynamicCodePolicy() at the end because the policy is
+ // irreversible.
+ TestDynamicCodePolicy()) {
+ printf("TEST-PASS | WindowsDllInterceptor | 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;
+ }
+
+ return 1;
+}
diff --git a/toolkit/xre/dllservices/tests/TestDllInterceptor.exe.manifest b/toolkit/xre/dllservices/tests/TestDllInterceptor.exe.manifest
new file mode 100644
index 0000000000..11287012c5
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/TestDllInterceptor.exe.manifest
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1"
+ manifestVersion="1.0"
+ xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
+ <assemblyIdentity type="win32"
+ name="TestDllInterceptor"
+ version="1.0.0.0" />
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!-- Need this to use functions in WindowsVersion.h -->
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> <!-- Win10 -->
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> <!-- Win8.1 -->
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> <!-- Win8 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/> <!-- Win7 -->
+ </application>
+ </compatibility>
+</assembly>
diff --git a/toolkit/xre/dllservices/tests/TestDllInterceptorCrossProcess.cpp b/toolkit/xre/dllservices/tests/TestDllInterceptorCrossProcess.cpp
new file mode 100644
index 0000000000..32cf90bac1
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/TestDllInterceptorCrossProcess.cpp
@@ -0,0 +1,159 @@
+/* -*- 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/Attributes.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "nsWindowsDllInterceptor.h"
+#include "nsWindowsHelpers.h"
+
+#include <string>
+
+using std::wstring;
+
+extern "C" __declspec(dllexport) int ReturnResult() { return 2; }
+
+static mozilla::CrossProcessDllInterceptor::FuncHookType<
+ decltype(&ReturnResult)>
+ gOrigReturnResult;
+
+static int ReturnResultHook() {
+ if (gOrigReturnResult() != 2) {
+ return 3;
+ }
+
+ return 0;
+}
+
+int ParentMain(int argc, wchar_t* argv[]) {
+ mozilla::SetArgv0ToFullBinaryPath(argv);
+
+ // We'll add the child process to a job so that, in the event of a failure in
+ // this parent process, the child process will be automatically terminated.
+ nsAutoHandle job(::CreateJobObjectW(nullptr, nullptr));
+ if (!job) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Job creation "
+ "failed\n");
+ return 1;
+ }
+
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo = {};
+ jobInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
+
+ if (!::SetInformationJobObject(job.get(), JobObjectExtendedLimitInformation,
+ &jobInfo, sizeof(jobInfo))) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Job config "
+ "failed\n");
+ return 1;
+ }
+
+ wchar_t childArgv_1[] = L"-child";
+
+ wchar_t* childArgv[] = {argv[0], childArgv_1};
+
+ mozilla::UniquePtr<wchar_t[]> cmdLine(
+ mozilla::MakeCommandLine(mozilla::ArrayLength(childArgv), childArgv));
+
+ STARTUPINFOW si = {sizeof(si)};
+ PROCESS_INFORMATION pi;
+ if (!::CreateProcessW(argv[0], cmdLine.get(), nullptr, nullptr, FALSE,
+ CREATE_SUSPENDED, nullptr, nullptr, &si, &pi)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Failed to spawn "
+ "child process\n");
+ return 1;
+ }
+
+ nsAutoHandle childProcess(pi.hProcess);
+ nsAutoHandle childMainThread(pi.hThread);
+
+ if (!::AssignProcessToJobObject(job.get(), childProcess.get())) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Failed to assign "
+ "child process to job\n");
+ ::TerminateProcess(childProcess.get(), 1);
+ return 1;
+ }
+
+ mozilla::nt::CrossExecTransferManager transferMgr(childProcess);
+ if (!transferMgr) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | "
+ "CrossExecTransferManager instantiation failed.\n");
+ return 1;
+ }
+
+ mozilla::CrossProcessDllInterceptor intcpt(childProcess.get());
+ intcpt.Init("TestDllInterceptorCrossProcess.exe");
+
+ if (!gOrigReturnResult.Set(transferMgr, intcpt, "ReturnResult",
+ &ReturnResultHook)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Failed to add "
+ "hook\n");
+ return 1;
+ }
+
+ printf("TEST-PASS | DllInterceptorCrossProcess | Hook added\n");
+
+ if (::ResumeThread(childMainThread.get()) == static_cast<DWORD>(-1)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Failed to resume "
+ "child thread\n");
+ return 1;
+ }
+
+ BOOL remoteDebugging;
+ bool debugging =
+ ::IsDebuggerPresent() ||
+ (::CheckRemoteDebuggerPresent(childProcess.get(), &remoteDebugging) &&
+ remoteDebugging);
+
+ DWORD waitResult =
+ ::WaitForSingleObject(childProcess.get(), debugging ? INFINITE : 60000);
+ if (waitResult != WAIT_OBJECT_0) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Child process "
+ "failed to finish\n");
+ return 1;
+ }
+
+ DWORD childExitCode;
+ if (!::GetExitCodeProcess(childProcess.get(), &childExitCode)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Failed to obtain "
+ "child process exit code\n");
+ return 1;
+ }
+
+ if (childExitCode) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Child process "
+ "exit code is %lu instead of 0\n",
+ childExitCode);
+ return 1;
+ }
+
+ printf(
+ "TEST-PASS | DllInterceptorCrossProcess | Child process exit code is "
+ "zero\n");
+ return 0;
+}
+
+extern "C" int wmain(int argc, wchar_t* argv[]) {
+ if (argc > 1) {
+ // clang keeps inlining this call despite every attempt to force it to do
+ // otherwise. We'll use GetProcAddress and call its function pointer
+ // instead.
+ auto pReturnResult = reinterpret_cast<decltype(&ReturnResult)>(
+ ::GetProcAddress(::GetModuleHandleW(nullptr), "ReturnResult"));
+ return pReturnResult();
+ }
+
+ return ParentMain(argc, argv);
+}
diff --git a/toolkit/xre/dllservices/tests/TestIATPatcher.cpp b/toolkit/xre/dllservices/tests/TestIATPatcher.cpp
new file mode 100644
index 0000000000..2f84c0541a
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/TestIATPatcher.cpp
@@ -0,0 +1,121 @@
+/* -*- 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/Assertions.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "nsWindowsDllInterceptor.h"
+#include "nsWindowsHelpers.h"
+
+#include <shlwapi.h>
+
+static int NormalImport() { return ::GetSystemMetrics(SM_CYCAPTION); }
+
+static bool DelayLoadImport() {
+ return !!::UrlIsW(L"http://example.com/", URLIS_FILEURL);
+}
+
+static mozilla::WindowsIATPatcher::FuncHookType<decltype(&::GetSystemMetrics)>
+ gGetSystemMetricsHook;
+
+static mozilla::WindowsIATPatcher::FuncHookType<decltype(&::MessageBoxA)>
+ gMessageBoxAHook;
+
+static mozilla::WindowsIATPatcher::FuncHookType<decltype(&::UrlIsW)> gUrlIsHook;
+
+static bool gGetSystemMetricsHookCalled = false;
+
+static int WINAPI GetSystemMetricsHook(int aIndex) {
+ MOZ_DIAGNOSTIC_ASSERT(aIndex == SM_CYCAPTION);
+ gGetSystemMetricsHookCalled = true;
+ return 0;
+}
+
+static bool gUrlIsHookCalled = false;
+
+static BOOL WINAPI UrlIsWHook(PCWSTR aUrl, URLIS aFlags) {
+ gUrlIsHookCalled = true;
+ return TRUE;
+}
+
+static HMODULE GetStrongReferenceToExeModule() {
+ HMODULE result;
+ if (!::GetModuleHandleExW(0, nullptr, &result)) {
+ return nullptr;
+ }
+
+ return result;
+}
+
+#define PRINT_FAIL(msg) printf("TEST-UNEXPECTED-FAIL | IATPatcher | " msg "\n")
+
+extern "C" int wmain(int argc, wchar_t* argv[]) {
+ nsModuleHandle ourModule1(GetStrongReferenceToExeModule());
+ if (!ourModule1) {
+ PRINT_FAIL("Failed obtaining HMODULE for executable");
+ return 1;
+ }
+
+ if (!gGetSystemMetricsHook.Set(ourModule1, "user32.dll", "GetSystemMetrics",
+ &GetSystemMetricsHook)) {
+ PRINT_FAIL("Failed setting GetSystemMetrics hook");
+ return 1;
+ }
+
+ if (NormalImport() || !gGetSystemMetricsHookCalled) {
+ PRINT_FAIL("GetSystemMetrics hook was not called");
+ return 1;
+ }
+
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::GetSystemMetrics)>
+ pRealGetSystemMetrics(L"user32.dll", "GetSystemMetrics");
+ if (!pRealGetSystemMetrics) {
+ PRINT_FAIL("Failed resolving real GetSystemMetrics pointer");
+ return 1;
+ }
+
+ if (gGetSystemMetricsHook.GetStub() != pRealGetSystemMetrics) {
+ PRINT_FAIL(
+ "GetSystemMetrics hook stub pointer does not match real "
+ "GetSystemMetrics pointer");
+ return 1;
+ }
+
+ nsModuleHandle ourModule2(GetStrongReferenceToExeModule());
+ if (!ourModule2) {
+ PRINT_FAIL("Failed obtaining HMODULE for executable");
+ return 1;
+ }
+
+ // This should fail becuase the test never calls, and thus never imports,
+ // MessageBoxA
+ if (gMessageBoxAHook.Set(ourModule2, "user32.dll", "MessageBoxA", nullptr)) {
+ PRINT_FAIL("Setting MessageBoxA hook succeeded when it should have failed");
+ return 1;
+ }
+
+ nsModuleHandle ourModule3(GetStrongReferenceToExeModule());
+ if (!ourModule3) {
+ PRINT_FAIL("Failed obtaining HMODULE for executable");
+ return 1;
+ }
+
+ // These tests involve a delay-loaded import, which are not supported; we
+ // expect these tests to FAIL.
+
+ if (gUrlIsHook.Set(ourModule3, "shlwapi.dll", "UrlIsW", &UrlIsWHook)) {
+ PRINT_FAIL("gUrlIsHook.Set should have failed");
+ return 1;
+ }
+
+ if (DelayLoadImport() || gUrlIsHookCalled) {
+ PRINT_FAIL("gUrlIsHook should not have been called");
+ return 1;
+ }
+
+ printf("TEST-PASS | IATPatcher | All tests passed.\n");
+ return 0;
+}
diff --git a/toolkit/xre/dllservices/tests/TestMMPolicy.cpp b/toolkit/xre/dllservices/tests/TestMMPolicy.cpp
new file mode 100644
index 0000000000..1ae93a4ed1
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/TestMMPolicy.cpp
@@ -0,0 +1,204 @@
+/* -*- 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 "nsWindowsDllInterceptor.h"
+
+#include <functional>
+
+mozilla::interceptor::MMPolicyInProcess gPolicy;
+
+void DepleteVirtualAddress(
+ uint8_t* aStart, size_t aSize,
+ const std::function<void(void*)>& aPostAllocCallback) {
+ const DWORD granularity = gPolicy.GetAllocGranularity();
+ if (aStart == 0 || aSize < granularity) {
+ return;
+ }
+
+ uint8_t* alignedStart = reinterpret_cast<uint8_t*>(
+ (((reinterpret_cast<uintptr_t>(aStart) - 1) / granularity) + 1) *
+ granularity);
+ aSize -= (alignedStart - aStart);
+ if (auto p = VirtualAlloc(alignedStart, aSize, MEM_RESERVE, PAGE_NOACCESS)) {
+ aPostAllocCallback(p);
+ return;
+ }
+
+ uintptr_t mask = ~(static_cast<uintptr_t>(granularity) - 1);
+ size_t halfSize = (aSize >> 1) & mask;
+ if (halfSize == 0) {
+ return;
+ }
+
+ DepleteVirtualAddress(aStart, halfSize, aPostAllocCallback);
+ DepleteVirtualAddress(aStart + halfSize, aSize - halfSize,
+ aPostAllocCallback);
+}
+
+bool ValidateFreeRegion(LPVOID aRegion, size_t aDesiredLen) {
+ MEMORY_BASIC_INFORMATION mbi;
+ if (VirtualQuery(aRegion, &mbi, sizeof(mbi)) != sizeof(mbi)) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "VirtualQuery(%p) failed - %08lx\n",
+ aRegion, GetLastError());
+ return false;
+ }
+
+ if (mbi.State != MEM_FREE) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "%p is not within a free region\n",
+ aRegion);
+ return false;
+ }
+
+ if (aRegion != mbi.BaseAddress ||
+ reinterpret_cast<uintptr_t>(mbi.BaseAddress) %
+ gPolicy.GetAllocGranularity()) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "%p is not a region's start address\n",
+ aRegion);
+ return false;
+ }
+
+ LPVOID allocated = VirtualAlloc(aRegion, aDesiredLen,
+ MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
+ if (!allocated) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "VirtualAlloc(%p) failed - %08lx\n",
+ aRegion, GetLastError());
+ return false;
+ }
+
+ if (!VirtualFree(allocated, 0, MEM_RELEASE)) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "VirtualFree(%p) failed - %08lx\n",
+ allocated, GetLastError());
+ return false;
+ }
+
+ return true;
+}
+
+bool TestFindRegion() {
+ // Skip the near-null addresses
+ uint8_t* minAddr = reinterpret_cast<uint8_t*>(
+ std::max(gPolicy.GetAllocGranularity(), 0x1000000ul));
+ // 64bit address space is too large to deplete. 32bit space is enough.
+ uint8_t* maxAddr = reinterpret_cast<uint8_t*>(std::min(
+ gPolicy.GetMaxUserModeAddress(), static_cast<uintptr_t>(0xffffffff)));
+
+ // Keep one of the regions we allocate so that we can release it later.
+ void* lastResort = nullptr;
+
+ // Reserve all free regions in the range [minAddr, maxAddr]
+ for (uint8_t* address = minAddr; address <= maxAddr;) {
+ MEMORY_BASIC_INFORMATION mbi;
+ if (VirtualQuery(address, &mbi, sizeof(mbi)) != sizeof(mbi)) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "VirtualQuery(%p) failed - %08lx\n",
+ address, GetLastError());
+ break;
+ }
+
+ address = reinterpret_cast<uint8_t*>(mbi.BaseAddress);
+ if (mbi.State == MEM_FREE) {
+ DepleteVirtualAddress(address, mbi.RegionSize,
+ [&lastResort](void* aAllocated) {
+ // Pick the first address we allocate to make sure
+ // FindRegion scans the full range.
+ if (!lastResort) {
+ lastResort = aAllocated;
+ }
+ });
+ }
+
+ address += mbi.RegionSize;
+ }
+
+ if (!lastResort) {
+ printf(
+ "TEST-SKIPPED | TestMMPolicy | "
+ "No free region in [%p - %p]. Skipping the testcase.\n",
+ minAddr, maxAddr);
+ return true;
+ }
+
+ // Make sure there are no free regions
+ PVOID freeRegion =
+ gPolicy.FindRegion(GetCurrentProcess(), 1, minAddr, maxAddr);
+ if (freeRegion) {
+ if (reinterpret_cast<uintptr_t>(freeRegion) %
+ gPolicy.GetAllocGranularity()) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "MMPolicyBase::FindRegion returned an unaligned address %p.\n",
+ freeRegion);
+ return false;
+ }
+
+ printf(
+ "TEST-SKIPPED | TestMMPolicy | "
+ "%p was freed after depletion. Skipping the testcase.\n",
+ freeRegion);
+ return true;
+ }
+
+ // Free one region, and thus we can expect FindRegion finds this region
+ if (!VirtualFree(lastResort, 0, MEM_RELEASE)) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "VirtualFree(%p) failed - %08lx\n",
+ lastResort, GetLastError());
+ return false;
+ }
+ printf("The region starting from %p has been freed.\n", lastResort);
+
+ // Run the function several times because it uses a randon number inside
+ // and its result is nondeterministic.
+ for (int i = 0; i < 50; ++i) {
+ // Because one region was freed, a desire up to one region
+ // should be fulfilled.
+ const size_t desiredLengths[] = {1, gPolicy.GetAllocGranularity()};
+
+ for (auto desiredLen : desiredLengths) {
+ freeRegion =
+ gPolicy.FindRegion(GetCurrentProcess(), desiredLen, minAddr, maxAddr);
+ if (!freeRegion) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "Failed to find a free region.\n");
+ return false;
+ }
+
+ if (!ValidateFreeRegion(freeRegion, desiredLen)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+extern "C" int wmain(int argc, wchar_t* argv[]) {
+ // Preload delayload modules (e.g. advapi32.dll, bcryptPrimitives.dll, etc.)
+ // by calling rand_s(), which is used in MMPolicy::FindRegion, before
+ // depleting the process memory during the test.
+ unsigned int rnd = 0;
+ rand_s(&rnd);
+
+ if (!TestFindRegion()) {
+ return 1;
+ }
+
+ printf("TEST-PASS | TestMMPolicy | All tests passed.\n");
+ return 0;
+}
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDLLBlocklist.cpp b/toolkit/xre/dllservices/tests/gtest/TestDLLBlocklist.cpp
new file mode 100644
index 0000000000..938f63a524
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDLLBlocklist.cpp
@@ -0,0 +1,475 @@
+/* -*- 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 <winternl.h>
+
+#include <process.h>
+
+#include <array>
+#include <functional>
+
+#include "gtest/gtest.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Char16.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/Services.h"
+#include "mozilla/WinDllServices.h"
+#include "mozilla/WindowsStackCookie.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIThread.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsUnicharUtils.h"
+#include "nsWindowsHelpers.h"
+
+static nsString GetFullPath(const nsAString& aLeaf) {
+ nsCOMPtr<nsIFile> f;
+
+ EXPECT_TRUE(NS_SUCCEEDED(
+ NS_GetSpecialDirectory(NS_OS_CURRENT_WORKING_DIR, getter_AddRefs(f))));
+
+ EXPECT_NS_SUCCEEDED(f->Append(aLeaf));
+
+ bool exists;
+ EXPECT_TRUE(NS_SUCCEEDED(f->Exists(&exists)) && exists);
+
+ nsString ret;
+ EXPECT_NS_SUCCEEDED(f->GetPath(ret));
+ return ret;
+}
+
+void FlushMainThreadLoop() {
+ nsCOMPtr<nsIThread> mainThread;
+ nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread));
+ ASSERT_NS_SUCCEEDED(rv);
+
+ rv = NS_OK;
+ bool processed = true;
+ while (processed && NS_SUCCEEDED(rv)) {
+ rv = mainThread->ProcessNextEvent(false, &processed);
+ }
+}
+
+class TestDLLLoadObserver : public nsIObserver {
+ public:
+ using DLLFilter = std::function<bool(const char16_t*)>;
+
+ explicit TestDLLLoadObserver(DLLFilter dllFilter)
+ : mDllFilter(std::move(dllFilter)),
+ mMainThreadNotificationsCount(0),
+ mNonMainThreadNotificationsCount(0){};
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override {
+ if (!mDllFilter(aData)) {
+ return NS_OK;
+ }
+ if (0 == strcmp(aTopic, mozilla::DllServices::kTopicDllLoadedMainThread)) {
+ ++mMainThreadNotificationsCount;
+ } else {
+ EXPECT_TRUE(
+ 0 ==
+ strcmp(aTopic, mozilla::DllServices::kTopicDllLoadedNonMainThread));
+ ++mNonMainThreadNotificationsCount;
+ }
+ return NS_OK;
+ }
+
+ void Init() {
+ nsCOMPtr<nsIObserverService> obsServ(
+ mozilla::services::GetObserverService());
+
+ EXPECT_TRUE(obsServ);
+
+ obsServ->AddObserver(this, mozilla::DllServices::kTopicDllLoadedMainThread,
+ false);
+ obsServ->AddObserver(
+ this, mozilla::DllServices::kTopicDllLoadedNonMainThread, false);
+ }
+
+ void Exit() {
+ // Observe only gets called if/when we flush the main thread loop.
+ FlushMainThreadLoop();
+
+ nsCOMPtr<nsIObserverService> obsServ(
+ mozilla::services::GetObserverService());
+
+ EXPECT_TRUE(obsServ);
+
+ obsServ->RemoveObserver(this,
+ mozilla::DllServices::kTopicDllLoadedMainThread);
+ obsServ->RemoveObserver(this,
+ mozilla::DllServices::kTopicDllLoadedNonMainThread);
+ }
+
+ int MainThreadNotificationsCount() { return mMainThreadNotificationsCount; }
+
+ int NonMainThreadNotificationsCount() {
+ return mNonMainThreadNotificationsCount;
+ }
+
+ private:
+ virtual ~TestDLLLoadObserver() = default;
+
+ DLLFilter mDllFilter;
+ int mMainThreadNotificationsCount;
+ int mNonMainThreadNotificationsCount;
+};
+
+NS_IMPL_ISUPPORTS(TestDLLLoadObserver, nsIObserver)
+
+TEST(TestDllBlocklist, BlockDllByName)
+{
+ // The DLL name has capital letters, so this also tests that the comparison
+ // is case-insensitive.
+ constexpr auto kLeafName = u"TestDllBlocklist_MatchByName.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+ EXPECT_TRUE(!hDll);
+ EXPECT_TRUE(!::GetModuleHandleW(kLeafName.get()));
+
+ hDll.own(::LoadLibraryExW(dllPath.get(), nullptr, LOAD_LIBRARY_AS_DATAFILE));
+ // Mapped as MEM_MAPPED + PAGE_READONLY
+ EXPECT_TRUE(hDll);
+}
+
+TEST(TestDllBlocklist, BlockDllByVersion)
+{
+ constexpr auto kLeafName = u"TestDllBlocklist_MatchByVersion.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+ EXPECT_TRUE(!hDll);
+ EXPECT_TRUE(!::GetModuleHandleW(kLeafName.get()));
+
+ hDll.own(
+ ::LoadLibraryExW(dllPath.get(), nullptr, LOAD_LIBRARY_AS_IMAGE_RESOURCE));
+ // Mapped as MEM_IMAGE + PAGE_READONLY
+ EXPECT_TRUE(hDll);
+}
+
+TEST(TestDllBlocklist, AllowDllByVersion)
+{
+ constexpr auto kLeafName = u"TestDllBlocklist_AllowByVersion.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+ EXPECT_TRUE(!!hDll);
+ EXPECT_TRUE(!!::GetModuleHandleW(kLeafName.get()));
+}
+
+TEST(TestDllBlocklist, GPUProcessOnly_AllowInMainProcess)
+{
+ constexpr auto kLeafName = u"TestDllBlocklist_GPUProcessOnly.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+ EXPECT_TRUE(!!hDll);
+ EXPECT_TRUE(!!::GetModuleHandleW(kLeafName.get()));
+}
+
+TEST(TestDllBlocklist, SocketProcessOnly_AllowInMainProcess)
+{
+ constexpr auto kLeafName = u"TestDllBlocklist_SocketProcessOnly.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+ EXPECT_TRUE(!!hDll);
+ EXPECT_TRUE(!!::GetModuleHandleW(kLeafName.get()));
+}
+
+TEST(TestDllBlocklist, UtilityProcessOnly_AllowInMainProcess)
+{
+ constexpr auto kLeafName = u"TestDllBlocklist_UtilityProcessOnly.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+ EXPECT_TRUE(!!hDll);
+ EXPECT_TRUE(!!::GetModuleHandleW(kLeafName.get()));
+}
+
+TEST(TestDllBlocklist, GMPluginProcessOnly_AllowInMainProcess)
+{
+ constexpr auto kLeafName = u"TestDllBlocklist_GMPluginProcessOnly.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+ EXPECT_TRUE(!!hDll);
+ EXPECT_TRUE(!!::GetModuleHandleW(kLeafName.get()));
+}
+
+// This DLL has two entries; it's blocked for unversioned (i.e. DLLs that
+// have no version information) everywhere and blocked for versions 5.5.5.5 and
+// earlier only in the GPU process. Since the version we're trying to load
+// is 5.5.5.5, it should load in the main process.
+TEST(TestDllBlocklist, MultipleEntriesDifferentProcesses_AllowInMainProcess)
+{
+ constexpr auto kLeafName =
+ u"TestDllBlocklist_MultipleEntries_DifferentProcesses.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+ EXPECT_TRUE(!!hDll);
+ EXPECT_TRUE(!!::GetModuleHandleW(kLeafName.get()));
+}
+
+TEST(TestDllBlocklist, MultipleEntriesSameProcessBackward_Block)
+{
+ // One entry matches by version and many others do not, so
+ // we should block.
+ constexpr auto kLeafName =
+ u"TestDllBlocklist_MultipleEntries_SameProcessBackward.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+ EXPECT_TRUE(!hDll);
+ EXPECT_TRUE(!::GetModuleHandleW(kLeafName.get()));
+
+ hDll.own(
+ ::LoadLibraryExW(dllPath.get(), nullptr, LOAD_LIBRARY_AS_IMAGE_RESOURCE));
+ // Mapped as MEM_IMAGE + PAGE_READONLY
+ EXPECT_TRUE(hDll);
+}
+
+TEST(TestDllBlocklist, MultipleEntriesSameProcessForward_Block)
+{
+ // One entry matches by version and many others do not, so
+ // we should block.
+ constexpr auto kLeafName =
+ u"TestDllBlocklist_MultipleEntries_SameProcessForward.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+ EXPECT_TRUE(!hDll);
+ EXPECT_TRUE(!::GetModuleHandleW(kLeafName.get()));
+
+ hDll.own(
+ ::LoadLibraryExW(dllPath.get(), nullptr, LOAD_LIBRARY_AS_IMAGE_RESOURCE));
+ // Mapped as MEM_IMAGE + PAGE_READONLY
+ EXPECT_TRUE(hDll);
+}
+
+#if defined(MOZ_LAUNCHER_PROCESS)
+// RedirectToNoOpEntryPoint needs the launcher process.
+// This test will fail in debug x64 if we mistakenly reintroduce stack buffers
+// in patched_NtMapViewOfSection (see bug 1733532).
+TEST(TestDllBlocklist, NoOpEntryPoint)
+{
+ // DllMain of this dll has MOZ_RELEASE_ASSERT. This test makes sure we load
+ // the module successfully without running DllMain.
+ constexpr auto kLeafName = u"TestDllBlocklist_NoOpEntryPoint.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+# if defined(MOZ_ASAN)
+ // With ASAN, the test uses mozglue's blocklist where
+ // REDIRECT_TO_NOOP_ENTRYPOINT is ignored. So LoadLibraryW
+ // is expected to fail.
+ EXPECT_TRUE(!hDll);
+ EXPECT_TRUE(!::GetModuleHandleW(kLeafName.get()));
+# else
+ EXPECT_TRUE(!!hDll);
+ EXPECT_TRUE(!!::GetModuleHandleW(kLeafName.get()));
+# endif
+}
+
+// User blocklist needs the launcher process.
+// This test will fail in debug x64 if we mistakenly reintroduce stack buffers
+// in patched_NtMapViewOfSection (see bug 1733532).
+TEST(TestDllBlocklist, UserBlocked)
+{
+ constexpr auto kLeafName = u"TestDllBlocklist_UserBlocked.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+// With ASAN, the test uses mozglue's blocklist where
+// the user blocklist is not used.
+# if !defined(MOZ_ASAN)
+ EXPECT_TRUE(!hDll);
+ EXPECT_TRUE(!::GetModuleHandleW(kLeafName.get()));
+# endif
+ hDll.own(::LoadLibraryExW(dllPath.get(), nullptr, LOAD_LIBRARY_AS_DATAFILE));
+ // Mapped as MEM_MAPPED + PAGE_READONLY
+ EXPECT_TRUE(hDll);
+}
+#endif // defined(MOZ_LAUNCHER_PROCESS)
+
+#define DLL_BLOCKLIST_ENTRY(name, ...) {name, __VA_ARGS__},
+#define DLL_BLOCKLIST_STRING_TYPE const char*
+#include "mozilla/WindowsDllBlocklistLegacyDefs.h"
+
+TEST(TestDllBlocklist, BlocklistIntegrity)
+{
+ DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY(pFirst);
+ DECLARE_POINTER_TO_LAST_DLL_BLOCKLIST_ENTRY(pLast);
+
+ EXPECT_FALSE(pLast->mName || pLast->mMaxVersion || pLast->mFlags);
+
+ for (size_t i = 0; i < mozilla::ArrayLength(gWindowsDllBlocklist) - 1; ++i) {
+ auto pEntry = pFirst + i;
+
+ // Validate name
+ EXPECT_TRUE(!!pEntry->mName);
+ EXPECT_GT(strlen(pEntry->mName), 3U);
+
+ // Check the filename for valid characters.
+ for (auto pch = pEntry->mName; *pch != 0; ++pch) {
+ EXPECT_FALSE(*pch >= 'A' && *pch <= 'Z');
+ }
+ }
+}
+
+TEST(TestDllBlocklist, BlockThreadWithLoadLibraryEntryPoint)
+{
+ // Only supported on Nightly
+#if defined(NIGHTLY_BUILD)
+ using ThreadProc = unsigned(__stdcall*)(void*);
+
+ constexpr auto kLeafNameW = u"TestDllBlocklist_MatchByVersion.dll"_ns;
+
+ nsString fullPathW = GetFullPath(kLeafNameW);
+ EXPECT_FALSE(fullPathW.IsEmpty());
+
+ nsAutoHandle threadW(reinterpret_cast<HANDLE>(
+ _beginthreadex(nullptr, 0, reinterpret_cast<ThreadProc>(&::LoadLibraryW),
+ (void*)fullPathW.get(), 0, nullptr)));
+
+ EXPECT_TRUE(!!threadW);
+ EXPECT_EQ(::WaitForSingleObject(threadW, INFINITE), WAIT_OBJECT_0);
+
+# if !defined(MOZ_ASAN)
+ // ASAN builds under Windows 11 can have unexpected thread exit codes.
+ // See bug 1798796
+ DWORD exitCode;
+ EXPECT_TRUE(::GetExitCodeThread(threadW, &exitCode) && !exitCode);
+# endif // !defined(MOZ_ASAN)
+ EXPECT_TRUE(!::GetModuleHandleW(kLeafNameW.get()));
+
+ const NS_LossyConvertUTF16toASCII fullPathA(fullPathW);
+ EXPECT_FALSE(fullPathA.IsEmpty());
+
+ nsAutoHandle threadA(reinterpret_cast<HANDLE>(
+ _beginthreadex(nullptr, 0, reinterpret_cast<ThreadProc>(&::LoadLibraryA),
+ (void*)fullPathA.get(), 0, nullptr)));
+
+ EXPECT_TRUE(!!threadA);
+ EXPECT_EQ(::WaitForSingleObject(threadA, INFINITE), WAIT_OBJECT_0);
+# if !defined(MOZ_ASAN)
+ // ASAN builds under Windows 11 can have unexpected thread exit codes.
+ // See bug 1798796
+ EXPECT_TRUE(::GetExitCodeThread(threadA, &exitCode) && !exitCode);
+# endif // !defined(MOZ_ASAN)
+ EXPECT_TRUE(!::GetModuleHandleW(kLeafNameW.get()));
+#endif // defined(NIGHTLY_BUILD)
+}
+
+constexpr auto kSingleNotificationDll1Loads = 4;
+constexpr auto kSingleNotificationDll2Loads = 3;
+using DllFullPathsArray =
+ std::array<nsString,
+ kSingleNotificationDll1Loads + kSingleNotificationDll2Loads>;
+
+DWORD __stdcall LoadSingleNotificationModules(LPVOID aThreadParameter) {
+ auto* dllFullPaths = reinterpret_cast<DllFullPathsArray*>(aThreadParameter);
+
+ for (const auto& dllFullPath : *dllFullPaths) {
+ nsModuleHandle hDll(::LoadLibraryW(dllFullPath.get()));
+ EXPECT_TRUE(!hDll);
+ EXPECT_TRUE(!::GetModuleHandleW(dllFullPath.get()));
+ }
+
+ return 0;
+}
+
+// The next test is only relevant if we hook LdrLoadDll, so we reflect the
+// hooking condition from browser/app/winlauncher/DllBlocklistInit.cpp.
+#if !defined(MOZ_ASAN) && !defined(_M_ARM64)
+
+// This test relies on the fact that blocked DLL loads generate a DLL load
+// notification.
+TEST(TestDllBlocklist, SingleNotification)
+{
+ // We will block-load the two DLLs multiple times, with variations on case.
+ std::array<nsLiteralString, kSingleNotificationDll1Loads> dll1Variations{
+ u"TestDllBlocklist_SingleNotification1.dll"_ns,
+ u"TestDllBlocklist_SingleNotification1.dll"_ns,
+ u"testdllblocklist_singlenotification1.dll"_ns,
+ u"TeStDlLbLoCkLiSt_SiNgLeNoTiFiCaTiOn1.DlL"_ns,
+ };
+ std::array<nsLiteralString, kSingleNotificationDll2Loads> dll2Variations{
+ u"TestDllBlocklist_SingleNotification2.dll"_ns,
+ u"testdllblocklist_singlenotification2.dll"_ns,
+ u"TESTDLLBLOCKLIST_SINGLENOTIFICATION2.dll"_ns,
+ };
+ DllFullPathsArray dllFullPaths;
+ size_t i = 0;
+ for (const auto& dllName : dll1Variations) {
+ dllFullPaths[i] = GetFullPath(dllName);
+ ++i;
+ }
+ for (const auto& dllName : dll2Variations) {
+ dllFullPaths[i] = GetFullPath(dllName);
+ ++i;
+ }
+
+ // Register our observer.
+ TestDLLLoadObserver::DLLFilter dllFilter(
+ [](const char16_t* aLoadedPath) -> bool {
+ nsDependentString loadedPath(aLoadedPath);
+ return StringEndsWith(loadedPath,
+ u"\\testdllblocklist_singlenotification1.dll"_ns,
+ nsCaseInsensitiveStringComparator) ||
+ StringEndsWith(loadedPath,
+ u"\\testdllblocklist_singlenotification2.dll"_ns,
+ nsCaseInsensitiveStringComparator);
+ });
+ RefPtr<TestDLLLoadObserver> obs(new TestDLLLoadObserver(dllFilter));
+ obs->Init();
+
+ // Load DllServices. This is required for notifications to get dispatched.
+ RefPtr<mozilla::DllServices> dllSvc(mozilla::DllServices::Get());
+ EXPECT_TRUE(dllSvc);
+
+ // Block-load the two DLLs multiple times on the main thread.
+ LoadSingleNotificationModules(reinterpret_cast<void*>(&dllFullPaths));
+
+ // Block-load the two DLLs multiple times on a different thread.
+ HANDLE thread =
+ ::CreateThread(nullptr, 0, LoadSingleNotificationModules,
+ reinterpret_cast<void*>(&dllFullPaths), 0, nullptr);
+ EXPECT_EQ(::WaitForSingleObject(thread, 5000), WAIT_OBJECT_0);
+
+ // Unregister our observer and flush the main thread loop.
+ obs->Exit();
+
+ // Check how many notifications we received
+ EXPECT_EQ(obs->MainThreadNotificationsCount(), 2);
+ EXPECT_EQ(obs->NonMainThreadNotificationsCount(),
+ kSingleNotificationDll1Loads + kSingleNotificationDll2Loads);
+}
+
+#endif // !defined(MOZ_ASAN) && !defined(_M_ARM64)
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.cpp
new file mode 100644
index 0000000000..7bd936296e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.cpp
@@ -0,0 +1,7 @@
+/* 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>
+
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.rc b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.rc
new file mode 100644
index 0000000000..f56aa099ff
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.rc
@@ -0,0 +1,42 @@
+/* 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 <winver.h>
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 5,5,5,6
+ PRODUCTVERSION 5,5,5,1
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904e4"
+ BEGIN
+ VALUE "CompanyName", "mozilla.org"
+ VALUE "FileDescription", L"Test DLL"
+ VALUE "FileVersion", "1.0"
+ VALUE "InternalName", "Test DLL"
+ VALUE "OriginalFilename", "TestDllBlocklist_AllowByVersion.dll"
+ VALUE "ProductName", "Test DLL"
+ VALUE "ProductVersion", "1.0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0409, 1252
+ END
+END
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/moz.build
new file mode 100644
index 0000000000..0987cdde1a
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestDllBlocklist_AllowByVersion")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_AllowByVersion.cpp",
+]
+
+RCFILE = "TestDllBlocklist_AllowByVersion.rc"
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_AllowByVersion.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_GMPluginProcessOnly/TestDllBlocklist_GMPluginProcessOnly.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_GMPluginProcessOnly/TestDllBlocklist_GMPluginProcessOnly.cpp
new file mode 100644
index 0000000000..7bd936296e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_GMPluginProcessOnly/TestDllBlocklist_GMPluginProcessOnly.cpp
@@ -0,0 +1,7 @@
+/* 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>
+
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_GMPluginProcessOnly/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_GMPluginProcessOnly/moz.build
new file mode 100644
index 0000000000..a5c89c2b8e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_GMPluginProcessOnly/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestDllBlocklist_GMPluginProcessOnly")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_GMPluginProcessOnly.cpp",
+]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_GMPluginProcessOnly.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_GPUProcessOnly/TestDllBlocklist_GPUProcessOnly.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_GPUProcessOnly/TestDllBlocklist_GPUProcessOnly.cpp
new file mode 100644
index 0000000000..7bd936296e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_GPUProcessOnly/TestDllBlocklist_GPUProcessOnly.cpp
@@ -0,0 +1,7 @@
+/* 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>
+
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_GPUProcessOnly/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_GPUProcessOnly/moz.build
new file mode 100644
index 0000000000..748e9cf22c
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_GPUProcessOnly/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestDllBlocklist_GPUProcessOnly")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_GPUProcessOnly.cpp",
+]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_GPUProcessOnly.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/TestDllBlocklist_MatchByName.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/TestDllBlocklist_MatchByName.cpp
new file mode 100644
index 0000000000..7bd936296e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/TestDllBlocklist_MatchByName.cpp
@@ -0,0 +1,7 @@
+/* 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>
+
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/moz.build
new file mode 100644
index 0000000000..f34931898a
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestDllBlocklist_MatchByName")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_MatchByName.cpp",
+]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_MatchByName.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.cpp
new file mode 100644
index 0000000000..7bd936296e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.cpp
@@ -0,0 +1,7 @@
+/* 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>
+
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.rc b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.rc
new file mode 100644
index 0000000000..7390c1cb34
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.rc
@@ -0,0 +1,42 @@
+/* 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 <winver.h>
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 5,5,5,5
+ PRODUCTVERSION 5,5,5,1
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904e4"
+ BEGIN
+ VALUE "CompanyName", "mozilla.org"
+ VALUE "FileDescription", L"Test DLL"
+ VALUE "FileVersion", "1.0"
+ VALUE "InternalName", "Test DLL"
+ VALUE "OriginalFilename", "TestDllBlocklist_MatchByVersion.dll"
+ VALUE "ProductName", "Test DLL"
+ VALUE "ProductVersion", "1.0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0409, 1252
+ END
+END
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/moz.build
new file mode 100644
index 0000000000..38e10524c7
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestDllBlocklist_MatchByVersion")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_MatchByVersion.cpp",
+]
+
+RCFILE = "TestDllBlocklist_MatchByVersion.rc"
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_MatchByVersion.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_DifferentProcesses/TestDllBlocklist_MultipleEntries_DifferentProcesses.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_DifferentProcesses/TestDllBlocklist_MultipleEntries_DifferentProcesses.cpp
new file mode 100644
index 0000000000..7bd936296e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_DifferentProcesses/TestDllBlocklist_MultipleEntries_DifferentProcesses.cpp
@@ -0,0 +1,7 @@
+/* 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>
+
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_DifferentProcesses/TestDllBlocklist_MultipleEntries_DifferentProcesses.rc b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_DifferentProcesses/TestDllBlocklist_MultipleEntries_DifferentProcesses.rc
new file mode 100644
index 0000000000..2b5b81406e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_DifferentProcesses/TestDllBlocklist_MultipleEntries_DifferentProcesses.rc
@@ -0,0 +1,42 @@
+/* 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 <winver.h>
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 5,5,5,5
+ PRODUCTVERSION 5,5,5,1
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904e4"
+ BEGIN
+ VALUE "CompanyName", "mozilla.org"
+ VALUE "FileDescription", L"Test DLL"
+ VALUE "FileVersion", "1.0"
+ VALUE "InternalName", "Test DLL"
+ VALUE "OriginalFilename", "TestDllBlocklist_MultipleEntries_DifferentProcesses.dll"
+ VALUE "ProductName", "Test DLL"
+ VALUE "ProductVersion", "1.0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0409, 1252
+ END
+END
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_DifferentProcesses/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_DifferentProcesses/moz.build
new file mode 100644
index 0000000000..cf2f745a28
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_DifferentProcesses/moz.build
@@ -0,0 +1,19 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestDllBlocklist_MultipleEntries_DifferentProcesses")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_MultipleEntries_DifferentProcesses.cpp",
+]
+
+RCFILE = "TestDllBlocklist_MultipleEntries_DifferentProcesses.rc"
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += [
+ "!TestDllBlocklist_MultipleEntries_DifferentProcesses.dll"
+ ]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessBackward/TestDllBlocklist_MultipleEntries_SameProcessBackward.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessBackward/TestDllBlocklist_MultipleEntries_SameProcessBackward.cpp
new file mode 100644
index 0000000000..7bd936296e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessBackward/TestDllBlocklist_MultipleEntries_SameProcessBackward.cpp
@@ -0,0 +1,7 @@
+/* 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>
+
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessBackward/TestDllBlocklist_MultipleEntries_SameProcessBackward.rc b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessBackward/TestDllBlocklist_MultipleEntries_SameProcessBackward.rc
new file mode 100644
index 0000000000..0b2431c343
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessBackward/TestDllBlocklist_MultipleEntries_SameProcessBackward.rc
@@ -0,0 +1,42 @@
+/* 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 <winver.h>
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 5,5,5,5
+ PRODUCTVERSION 5,5,5,1
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904e4"
+ BEGIN
+ VALUE "CompanyName", "mozilla.org"
+ VALUE "FileDescription", L"Test DLL"
+ VALUE "FileVersion", "1.0"
+ VALUE "InternalName", "Test DLL"
+ VALUE "OriginalFilename", "TestDllBlocklist_MultipleEntries_SameProcessBackward.dll"
+ VALUE "ProductName", "Test DLL"
+ VALUE "ProductVersion", "1.0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0409, 1252
+ END
+END
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessBackward/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessBackward/moz.build
new file mode 100644
index 0000000000..21ab229245
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessBackward/moz.build
@@ -0,0 +1,19 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestDllBlocklist_MultipleEntries_SameProcessBackward")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_MultipleEntries_SameProcessBackward.cpp",
+]
+
+RCFILE = "TestDllBlocklist_MultipleEntries_SameProcessBackward.rc"
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += [
+ "!TestDllBlocklist_MultipleEntries_SameProcessBackward.dll"
+ ]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessForward/TestDllBlocklist_MultipleEntries_SameProcessForward.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessForward/TestDllBlocklist_MultipleEntries_SameProcessForward.cpp
new file mode 100644
index 0000000000..7bd936296e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessForward/TestDllBlocklist_MultipleEntries_SameProcessForward.cpp
@@ -0,0 +1,7 @@
+/* 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>
+
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessForward/TestDllBlocklist_MultipleEntries_SameProcessForward.rc b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessForward/TestDllBlocklist_MultipleEntries_SameProcessForward.rc
new file mode 100644
index 0000000000..20d14e24d7
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessForward/TestDllBlocklist_MultipleEntries_SameProcessForward.rc
@@ -0,0 +1,42 @@
+/* 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 <winver.h>
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 5,5,5,5
+ PRODUCTVERSION 5,5,5,1
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904e4"
+ BEGIN
+ VALUE "CompanyName", "mozilla.org"
+ VALUE "FileDescription", L"Test DLL"
+ VALUE "FileVersion", "1.0"
+ VALUE "InternalName", "Test DLL"
+ VALUE "OriginalFilename", "TestDllBlocklist_MultipleEntries_SameProcessForward.dll"
+ VALUE "ProductName", "Test DLL"
+ VALUE "ProductVersion", "1.0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0409, 1252
+ END
+END
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessForward/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessForward/moz.build
new file mode 100644
index 0000000000..1546489efb
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MultipleEntries_SameProcessForward/moz.build
@@ -0,0 +1,19 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestDllBlocklist_MultipleEntries_SameProcessForward")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_MultipleEntries_SameProcessForward.cpp",
+]
+
+RCFILE = "TestDllBlocklist_MultipleEntries_SameProcessForward.rc"
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += [
+ "!TestDllBlocklist_MultipleEntries_SameProcessForward.dll"
+ ]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.cpp
new file mode 100644
index 0000000000..2505b8b700
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.cpp
@@ -0,0 +1,12 @@
+/* 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 "mozilla/Assertions.h"
+
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID) {
+ MOZ_RELEASE_ASSERT(0);
+ return TRUE;
+}
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.rc b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.rc
new file mode 100644
index 0000000000..7c79dac373
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.rc
@@ -0,0 +1,42 @@
+/* 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 <winver.h>
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 5,5,5,5
+ PRODUCTVERSION 5,5,5,1
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904e4"
+ BEGIN
+ VALUE "CompanyName", "mozilla.org"
+ VALUE "FileDescription", L"Test DLL"
+ VALUE "FileVersion", "1.0"
+ VALUE "InternalName", "Test DLL"
+ VALUE "OriginalFilename", "TestDllBlocklist_NoOpEntryPoint.dll"
+ VALUE "ProductName", "Test DLL"
+ VALUE "ProductVersion", "1.0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0409, 1252
+ END
+END
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/moz.build
new file mode 100644
index 0000000000..e9a10a150a
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestDllBlocklist_NoOpEntryPoint")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_NoOpEntryPoint.cpp",
+]
+
+RCFILE = "TestDllBlocklist_NoOpEntryPoint.rc"
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_NoOpEntryPoint.dll"]
+
+OS_LIBS += [
+ "uuid",
+]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification1/TestDllBlocklist_SingleNotification1.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification1/TestDllBlocklist_SingleNotification1.cpp
new file mode 100644
index 0000000000..4f6ce877eb
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification1/TestDllBlocklist_SingleNotification1.cpp
@@ -0,0 +1,7 @@
+/* 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>
+
+BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification1/TestDllBlocklist_SingleNotification1.rc b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification1/TestDllBlocklist_SingleNotification1.rc
new file mode 100644
index 0000000000..90f098b1b9
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification1/TestDllBlocklist_SingleNotification1.rc
@@ -0,0 +1,42 @@
+/* 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 <winver.h>
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 5,5,5,5
+ PRODUCTVERSION 5,5,5,1
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904e4"
+ BEGIN
+ VALUE "CompanyName", "mozilla.org"
+ VALUE "FileDescription", L"Test DLL"
+ VALUE "FileVersion", "1.0"
+ VALUE "InternalName", "Test DLL"
+ VALUE "OriginalFilename", "TestDllBlocklist_SingleNotification1.dll"
+ VALUE "ProductName", "Test DLL"
+ VALUE "ProductVersion", "1.0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0409, 1252
+ END
+END
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification1/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification1/moz.build
new file mode 100644
index 0000000000..79139b2b6e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification1/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestDllBlocklist_SingleNotification1")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_SingleNotification1.cpp",
+]
+
+RCFILE = "TestDllBlocklist_SingleNotification1.rc"
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_SingleNotification1.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification2/TestDllBlocklist_SingleNotification2.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification2/TestDllBlocklist_SingleNotification2.cpp
new file mode 100644
index 0000000000..4f6ce877eb
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification2/TestDllBlocklist_SingleNotification2.cpp
@@ -0,0 +1,7 @@
+/* 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>
+
+BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification2/TestDllBlocklist_SingleNotification2.rc b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification2/TestDllBlocklist_SingleNotification2.rc
new file mode 100644
index 0000000000..0cd44b2f9b
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification2/TestDllBlocklist_SingleNotification2.rc
@@ -0,0 +1,42 @@
+/* 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 <winver.h>
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 5,5,5,5
+ PRODUCTVERSION 5,5,5,1
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904e4"
+ BEGIN
+ VALUE "CompanyName", "mozilla.org"
+ VALUE "FileDescription", L"Test DLL"
+ VALUE "FileVersion", "1.0"
+ VALUE "InternalName", "Test DLL"
+ VALUE "OriginalFilename", "TestDllBlocklist_SingleNotification2.dll"
+ VALUE "ProductName", "Test DLL"
+ VALUE "ProductVersion", "1.0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0409, 1252
+ END
+END
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification2/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification2/moz.build
new file mode 100644
index 0000000000..64d551f480
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SingleNotification2/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestDllBlocklist_SingleNotification2")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_SingleNotification2.cpp",
+]
+
+RCFILE = "TestDllBlocklist_SingleNotification2.rc"
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_SingleNotification2.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/TestDllBlocklist_SocketProcessOnly.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/TestDllBlocklist_SocketProcessOnly.cpp
new file mode 100644
index 0000000000..7bd936296e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/TestDllBlocklist_SocketProcessOnly.cpp
@@ -0,0 +1,7 @@
+/* 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>
+
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/moz.build
new file mode 100644
index 0000000000..dc93544e1b
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestDllBlocklist_SocketProcessOnly")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_SocketProcessOnly.cpp",
+]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_SocketProcessOnly.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/TestDllBlocklist_UserBlocked.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/TestDllBlocklist_UserBlocked.cpp
new file mode 100644
index 0000000000..7bd936296e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/TestDllBlocklist_UserBlocked.cpp
@@ -0,0 +1,7 @@
+/* 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>
+
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/moz.build
new file mode 100644
index 0000000000..31996c5cb2
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestDllBlocklist_UserBlocked")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_UserBlocked.cpp",
+]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_UserBlocked.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/TestDllBlocklist_UtilityProcessOnly.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/TestDllBlocklist_UtilityProcessOnly.cpp
new file mode 100644
index 0000000000..7bd936296e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/TestDllBlocklist_UtilityProcessOnly.cpp
@@ -0,0 +1,7 @@
+/* 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>
+
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/moz.build
new file mode 100644
index 0000000000..913d0f155c
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestDllBlocklist_UtilityProcessOnly")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_UtilityProcessOnly.cpp",
+]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_UtilityProcessOnly.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules.cpp b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules.cpp
new file mode 100644
index 0000000000..ba4d1a8df8
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules.cpp
@@ -0,0 +1,462 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#include "gtest/gtest.h"
+
+#include "js/RegExp.h"
+#include "mozilla/BinarySearch.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/UntrustedModulesProcessor.h"
+#include "mozilla/WinDllServices.h"
+#include "nsContentUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "TelemetryFixture.h"
+#include "UntrustedModulesBackupService.h"
+#include "UntrustedModulesDataSerializer.h"
+
+using namespace mozilla;
+
+class ModuleLoadCounter final {
+ nsTHashMap<nsStringCaseInsensitiveHashKey, int> mCounters;
+
+ public:
+ template <size_t N>
+ ModuleLoadCounter(const nsString (&aNames)[N], const int (&aCounts)[N])
+ : mCounters(N) {
+ for (size_t i = 0; i < N; ++i) {
+ mCounters.InsertOrUpdate(aNames[i], aCounts[i]);
+ }
+ }
+
+ template <size_t N>
+ bool Remains(const nsString (&aNames)[N], const int (&aCounts)[N]) {
+ EXPECT_EQ(mCounters.Count(), N);
+ if (mCounters.Count() != N) {
+ return false;
+ }
+
+ bool result = true;
+ for (size_t i = 0; i < N; ++i) {
+ auto entry = mCounters.Lookup(aNames[i]);
+ if (!entry) {
+ wprintf(L"%s is not registered.\n",
+ static_cast<const wchar_t*>(aNames[i].get()));
+ result = false;
+ } else if (*entry != aCounts[i]) {
+ // We can return false, but let's print out all unmet modules
+ // which may be helpful to investigate test failures.
+ wprintf(L"%s:%4d\n", static_cast<const wchar_t*>(aNames[i].get()),
+ *entry);
+ result = false;
+ }
+ }
+ return result;
+ }
+
+ bool IsDone() const {
+ bool allZero = true;
+ for (const auto& data : mCounters.Values()) {
+ if (data < 0) {
+ // If any counter is negative, we know the test fails.
+ // No need to continue.
+ return true;
+ }
+ if (data > 0) {
+ allZero = false;
+ }
+ }
+ // If all counters are zero, the test finished nicely. Otherwise, those
+ // counters are expected to be decremented later. Let's continue.
+ return allZero;
+ }
+
+ void Decrement(const nsString& aName) {
+ if (auto entry = mCounters.Lookup(aName)) {
+ --(*entry);
+ }
+ }
+};
+
+class UntrustedModulesCollector {
+ static constexpr int kMaximumAttempts = 500;
+ Vector<UntrustedModulesData> mData;
+ ModuleLoadCounter* mChecker = nullptr;
+ Maybe<nsresult> mRv;
+ int mAttempts = 0;
+
+ void PollUntrustedModulesData() {
+ RefPtr<DllServices> dllSvc(DllServices::Get());
+ dllSvc->GetUntrustedModulesData()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [this](Maybe<UntrustedModulesData>&& aResult) {
+ // Some of expected loaded modules are still missing after
+ // kMaximumAttempts queries were submitted.
+ // Giving up here to avoid an infinite loop.
+ if (++mAttempts > kMaximumAttempts) {
+ mRv = Some(NS_ERROR_ABORT);
+ return;
+ }
+
+ if (aResult.isSome()) {
+ wprintf(L"Received data. (attempts=%d)\n", mAttempts);
+ for (auto item : aResult.ref().mEvents) {
+ mChecker->Decrement(item->mEvent.mRequestedDllName);
+ }
+ EXPECT_TRUE(mData.emplaceBack(std::move(aResult.ref())));
+ }
+
+ if (mChecker->IsDone()) {
+ mRv = Some(NS_OK);
+ return;
+ }
+
+ PollUntrustedModulesData();
+ },
+ [this](nsresult aReason) {
+ wprintf(L"GetUntrustedModulesData() failed - %08x\n", aReason);
+ EXPECT_TRUE(false);
+ mRv = Some(aReason);
+ });
+ }
+
+ public:
+ Vector<UntrustedModulesData>& Data() { return mData; }
+
+ nsresult Collect(ModuleLoadCounter& aChecker) {
+ mRv = Nothing();
+ mChecker = &aChecker;
+ mAttempts = 0;
+ mData.clear();
+
+ PollUntrustedModulesData();
+
+ EXPECT_TRUE(SpinEventLoopUntil("xre:UntrustedModulesCollector"_ns,
+ [this]() { return mRv.isSome(); }));
+
+ mChecker = nullptr;
+ return *mRv;
+ }
+};
+
+class UntrustedModulesFixture : public TelemetryTestFixture {
+ static constexpr int kLoadCountBeforeDllServices = 5;
+ static constexpr int kLoadCountAfterDllServices = 5;
+ static constexpr uint32_t kMaxModulesArrayLen = 10;
+
+ // One of the important test scenarios is to load modules before DllServices
+ // is initialized and to make sure those loading events are forwarded when
+ // DllServices is initialized.
+ // However, GTest instantiates a Fixture class every testcase and there is
+ // no way to re-enable DllServices and UntrustedModulesProcessor once it's
+ // disabled, which means no matter how many testcases we have, only the
+ // first testcase exercises that scenario. That's why we implement that
+ // test scenario in InitialModuleLoadOnce as a static member and runs it
+ // in the first testcase to be executed.
+ static INIT_ONCE sInitLoadOnce;
+ static UntrustedModulesCollector sInitLoadDataCollector;
+
+ static nsString PrependWorkingDir(const nsAString& aLeaf) {
+ nsCOMPtr<nsIFile> file;
+ EXPECT_TRUE(NS_SUCCEEDED(NS_GetSpecialDirectory(NS_OS_CURRENT_WORKING_DIR,
+ getter_AddRefs(file))));
+ EXPECT_NS_SUCCEEDED(file->Append(aLeaf));
+ bool exists;
+ EXPECT_TRUE(NS_SUCCEEDED(file->Exists(&exists)) && exists);
+ nsString fullPath;
+ EXPECT_NS_SUCCEEDED(file->GetPath(fullPath));
+ return fullPath;
+ }
+
+ static BOOL CALLBACK InitialModuleLoadOnce(PINIT_ONCE, void*, void**);
+
+ protected:
+ static constexpr int kInitLoadCount =
+ kLoadCountBeforeDllServices + kLoadCountAfterDllServices;
+ static const nsString kTestModules[];
+
+ static void ValidateUntrustedModules(const UntrustedModulesData& aData,
+ bool aIsTruncatedData = false);
+
+ static void LoadAndFree(const nsAString& aLeaf) {
+ nsModuleHandle dll(::LoadLibraryW(PrependWorkingDir(aLeaf).get()));
+ EXPECT_TRUE(!!dll);
+ }
+
+ virtual void SetUp() override {
+ TelemetryTestFixture::SetUp();
+ ::InitOnceExecuteOnce(&sInitLoadOnce, InitialModuleLoadOnce, nullptr,
+ nullptr);
+ }
+
+ static const Vector<UntrustedModulesData>& GetInitLoadData() {
+ return sInitLoadDataCollector.Data();
+ }
+
+ // This method is useful if we want a new instance of UntrustedModulesData
+ // which is not copyable.
+ static UntrustedModulesData CollectSingleData() {
+ // If we call LoadAndFree more than once, those loading events are
+ // likely to be merged into an instance of UntrustedModulesData,
+ // meaning the length of the collector's vector is at least one but
+ // the exact number is unknown.
+ LoadAndFree(kTestModules[0]);
+
+ UntrustedModulesCollector collector;
+ ModuleLoadCounter waitForOne({kTestModules[0]}, {1});
+ EXPECT_NS_SUCCEEDED(collector.Collect(waitForOne));
+ EXPECT_TRUE(waitForOne.Remains({kTestModules[0]}, {0}));
+ EXPECT_EQ(collector.Data().length(), 1U);
+
+ // Cannot "return collector.Data()[0]" as copy ctor is deleted.
+ return UntrustedModulesData(std::move(collector.Data()[0]));
+ }
+
+ template <typename DataFetcherT>
+ void ValidateJSValue(const char16_t* aPattern, size_t aPatternLength,
+ DataFetcherT&& aDataFetcher) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+ mozilla::Telemetry::UntrustedModulesDataSerializer serializer(
+ cx.GetJSContext(), kMaxModulesArrayLen);
+ EXPECT_TRUE(!!serializer);
+ aDataFetcher(serializer);
+
+ JS::Rooted<JS::Value> jsval(cx.GetJSContext());
+ serializer.GetObject(&jsval);
+
+ nsAutoString json;
+ EXPECT_TRUE(nsContentUtils::StringifyJSON(
+ cx.GetJSContext(), jsval, json, dom::UndefinedIsNullStringLiteral));
+
+ JS::Rooted<JSObject*> re(
+ cx.GetJSContext(),
+ JS::NewUCRegExpObject(cx.GetJSContext(), aPattern, aPatternLength,
+ JS::RegExpFlag::Global));
+ EXPECT_TRUE(!!re);
+
+ JS::Rooted<JS::Value> matchResult(cx.GetJSContext(), JS::NullValue());
+ size_t idx = 0;
+ EXPECT_TRUE(JS::ExecuteRegExpNoStatics(cx.GetJSContext(), re, json.get(),
+ json.Length(), &idx, true,
+ &matchResult));
+ // On match, with aOnlyMatch = true, ExecuteRegExpNoStatics returns boolean
+ // true. If no match, ExecuteRegExpNoStatics returns Null.
+ EXPECT_TRUE(matchResult.isBoolean() && matchResult.toBoolean());
+ if (!matchResult.isBoolean() || !matchResult.toBoolean()) {
+ // If match failed, print out the actual JSON kindly.
+ wprintf(L"JSON: %s\n", static_cast<const wchar_t*>(json.get()));
+ wprintf(L"RE: %s\n", aPattern);
+ }
+ }
+};
+
+const nsString UntrustedModulesFixture::kTestModules[] = {
+ // Sorted for binary-search
+ u"TestUntrustedModules_Dll1.dll"_ns,
+ u"TestUntrustedModules_Dll2.dll"_ns,
+};
+
+INIT_ONCE UntrustedModulesFixture::sInitLoadOnce = INIT_ONCE_STATIC_INIT;
+UntrustedModulesCollector UntrustedModulesFixture::sInitLoadDataCollector;
+
+void UntrustedModulesFixture::ValidateUntrustedModules(
+ const UntrustedModulesData& aData, bool aIsTruncatedData) {
+ // This defines a list of modules which are listed on our blocklist and
+ // thus its loading status is not expected to be Status::Loaded.
+ // Although the UntrustedModulesFixture test does not touch any of them,
+ // the current process might have run a test like TestDllBlocklist where
+ // we try to load and block them.
+ const struct {
+ const wchar_t* mName;
+ ModuleLoadInfo::Status mStatus;
+ } kKnownModules[] = {
+ // Sorted by mName for binary-search
+ {L"TestDllBlocklist_MatchByName.dll", ModuleLoadInfo::Status::Blocked},
+ {L"TestDllBlocklist_MatchByVersion.dll", ModuleLoadInfo::Status::Blocked},
+ {L"TestDllBlocklist_NoOpEntryPoint.dll",
+ ModuleLoadInfo::Status::Redirected},
+#if !defined(MOZ_ASAN)
+ // With ASAN, the test uses mozglue's blocklist where
+ // the user blocklist is not used. So only check for this
+ // DLL in the non-ASAN case.
+ {L"TestDllBlocklist_UserBlocked.dll", ModuleLoadInfo::Status::Blocked},
+#endif // !defined(MOZ_ASAN)
+ };
+
+ EXPECT_EQ(aData.mProcessType, GeckoProcessType_Default);
+ EXPECT_EQ(aData.mPid, ::GetCurrentProcessId());
+
+ nsTHashtable<nsPtrHashKey<void>> moduleSet;
+ for (const RefPtr<ModuleRecord>& module : aData.mModules.Values()) {
+ moduleSet.PutEntry(module);
+ }
+
+ size_t numBlockedEvents = 0;
+ for (auto item : aData.mEvents) {
+ const auto& evt = item->mEvent;
+ const nsDependentSubstring leafName =
+ nt::GetLeafName(evt.mModule->mResolvedNtName);
+ const nsAutoString leafNameStr(leafName.Data(), leafName.Length());
+ const ModuleLoadInfo::Status loadStatus =
+ static_cast<ModuleLoadInfo::Status>(evt.mLoadStatus);
+ if (loadStatus == ModuleLoadInfo::Status::Blocked) {
+ ++numBlockedEvents;
+ }
+
+ size_t match;
+ if (BinarySearchIf(
+ kKnownModules, 0, ArrayLength(kKnownModules),
+ [&leafNameStr](const auto& aVal) {
+ return _wcsicmp(leafNameStr.get(), aVal.mName);
+ },
+ &match)) {
+ EXPECT_EQ(loadStatus, kKnownModules[match].mStatus);
+ } else {
+ EXPECT_EQ(evt.mLoadStatus, 0U);
+ }
+
+ if (BinarySearchIf(
+ kTestModules, 0, ArrayLength(kTestModules),
+ [&leafNameStr](const auto& aVal) {
+ return _wcsicmp(leafNameStr.get(), aVal.get());
+ },
+ &match)) {
+ // We know the test modules are loaded in the main thread,
+ // but we don't know about other modules.
+ EXPECT_EQ(evt.mThreadId, ::GetCurrentThreadId());
+ }
+
+ // Make sure mModule is pointing to an entry of mModules.
+ EXPECT_TRUE(moduleSet.Contains(evt.mModule));
+ EXPECT_FALSE(evt.mIsDependent);
+ }
+
+ // No check for the mXULLoadDurationMS field because the field has a value
+ // in CCov build GTest, but it is empty in non-CCov build (bug 1681936).
+ EXPECT_EQ(aData.mNumEvents, aData.mEvents.length());
+ EXPECT_GT(aData.mNumEvents, 0U);
+ if (aIsTruncatedData) {
+ EXPECT_EQ(aData.mStacks.GetModuleCount(), 0U);
+ EXPECT_LE(aData.mNumEvents, UntrustedModulesData::kMaxEvents);
+ } else if (numBlockedEvents == aData.mNumEvents) {
+ // If all loading events were blocked or aData is truncated,
+ // the stacks are empty.
+ EXPECT_EQ(aData.mStacks.GetModuleCount(), 0U);
+ } else {
+ EXPECT_GT(aData.mStacks.GetModuleCount(), 0U);
+ }
+ EXPECT_EQ(aData.mSanitizationFailures, 0U);
+ EXPECT_EQ(aData.mTrustTestFailures, 0U);
+}
+
+BOOL CALLBACK UntrustedModulesFixture::InitialModuleLoadOnce(PINIT_ONCE, void*,
+ void**) {
+ for (int i = 0; i < kLoadCountBeforeDllServices; ++i) {
+ for (const auto& mod : kTestModules) {
+ LoadAndFree(mod);
+ }
+ }
+
+ RefPtr<DllServices> dllSvc(DllServices::Get());
+ dllSvc->StartUntrustedModulesProcessor(true);
+
+ for (int i = 0; i < kLoadCountAfterDllServices; ++i) {
+ for (const auto& mod : kTestModules) {
+ LoadAndFree(mod);
+ }
+ }
+
+ ModuleLoadCounter waitForTwo(kTestModules, {kInitLoadCount, kInitLoadCount});
+ EXPECT_EQ(sInitLoadDataCollector.Collect(waitForTwo), NS_OK);
+ EXPECT_TRUE(waitForTwo.Remains(kTestModules, {0, 0}));
+
+ for (const auto& event : GetInitLoadData()) {
+ ValidateUntrustedModules(event);
+ }
+
+ // Data was removed when retrieved. No data is retrieved again.
+ UntrustedModulesCollector collector;
+ ModuleLoadCounter waitOnceForEach(kTestModules, {1, 1});
+ EXPECT_EQ(collector.Collect(waitOnceForEach), NS_ERROR_ABORT);
+ EXPECT_TRUE(waitOnceForEach.Remains(kTestModules, {1, 1}));
+
+ return TRUE;
+}
+
+#define PROCESS_OBJ(TYPE, PID) \
+ u"\"" TYPE u"\\." PID u"\":{" \
+ u"\"processType\":\"" TYPE u"\",\"elapsed\":\\d+\\.\\d+," \
+ u"\"sanitizationFailures\":0,\"trustTestFailures\":0," \
+ u"\"events\":\\[{" \
+ u"\"processUptimeMS\":\\d+,\"loadDurationMS\":\\d+\\.\\d+," \
+ u"\"threadID\":\\d+,\"threadName\":\"Main Thread\"," \
+ u"\"baseAddress\":\"0x[0-9a-f]+\",\"moduleIndex\":0," \
+ u"\"isDependent\":false,\"loadStatus\":0}\\]," \
+ u"\"combinedStacks\":{" \
+ u"\"memoryMap\":\\[\\[\"\\w+\\.\\w+\",\"[0-9A-Z]+\"\\]" \
+ u"(,\\[\"\\w+\\.\\w+\",\"[0-9A-Z]+\\\"\\])*\\]," \
+ u"\"stacks\":\\[\\[\\[(-1|\\d+),\\d+\\]" \
+ u"(,\\[(-1|\\d+),\\d+\\])*\\]\\]}}"
+
+TEST_F(UntrustedModulesFixture, Serialize) {
+ // clang-format off
+ const char16_t kPattern[] = u"{\"structVersion\":1,"
+ u"\"modules\":\\[{"
+ u"\"resolvedDllName\":\"TestUntrustedModules_Dll1\\.dll\","
+ u"\"fileVersion\":\"1\\.2\\.3\\.4\","
+ // It would be nice to hard-code this, but this might change with
+ // compiler versions, etc.
+ u"\"debugID\":\"[0-9A-F]{33}\","
+ u"\"companyName\":\"Mozilla Corporation\",\"trustFlags\":0}\\],"
+ u"\"blockedModules\":\\[.*?\\]," // allow for the case where there are some blocked modules
+ u"\"processes\":{"
+ PROCESS_OBJ(u"browser", u"0xabc") u","
+ PROCESS_OBJ(u"browser", u"0x4") u","
+ PROCESS_OBJ(u"rdd", u"0x4")
+ u"}}";
+ // clang-format on
+
+ UntrustedModulesBackupData backup1, backup2;
+ {
+ UntrustedModulesData data1 = CollectSingleData();
+ UntrustedModulesData data2 = CollectSingleData();
+ UntrustedModulesData data3 = CollectSingleData();
+
+ data1.mPid = 0xabc;
+ data2.mPid = 0x4;
+ data2.mProcessType = GeckoProcessType_RDD;
+ data3.mPid = 0x4;
+
+ backup1.Add(std::move(data1));
+ backup2.Add(std::move(data2));
+ backup1.Add(std::move(data3));
+ }
+
+ ValidateJSValue(kPattern, ArrayLength(kPattern) - 1,
+ [&backup1, &backup2](
+ Telemetry::UntrustedModulesDataSerializer& aSerializer) {
+ EXPECT_NS_SUCCEEDED(aSerializer.Add(backup1));
+ EXPECT_NS_SUCCEEDED(aSerializer.Add(backup2));
+ });
+}
+
+TEST_F(UntrustedModulesFixture, Backup) {
+ RefPtr<UntrustedModulesBackupService> backupSvc(
+ UntrustedModulesBackupService::Get());
+ for (int i = 0; i < 100; ++i) {
+ backupSvc->Backup(CollectSingleData());
+ }
+
+ backupSvc->SettleAllStagingData();
+ EXPECT_TRUE(backupSvc->Staging().IsEmpty());
+
+ for (const auto& entry : backupSvc->Settled()) {
+ const RefPtr<UntrustedModulesDataContainer>& container = entry.GetData();
+ EXPECT_TRUE(!!container);
+ const UntrustedModulesData& data = container->mData;
+ EXPECT_EQ(entry.GetKey(), ProcessHashKey(data.mProcessType, data.mPid));
+ ValidateUntrustedModules(data, /*aIsTruncatedData*/ true);
+ }
+}
diff --git a/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.cpp b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.cpp
new file mode 100644
index 0000000000..4f6ce877eb
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.cpp
@@ -0,0 +1,7 @@
+/* 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>
+
+BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.rc b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.rc
new file mode 100644
index 0000000000..2358b88b93
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.rc
@@ -0,0 +1,38 @@
+/* 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 <winver.h>
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,2,3,4 // This field will be collected
+ PRODUCTVERSION 5,6,7,8
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904e4"
+ BEGIN
+ VALUE "CompanyName", "Mozilla Corporation"
+ VALUE "OriginalFilename", "TestUntrustedModules_Dll1.dll"
+ VALUE "ProductName", "Test DLL"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0409, 1252
+ END
+END
diff --git a/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/moz.build b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/moz.build
new file mode 100644
index 0000000000..57fc59ca8a
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestUntrustedModules_Dll1")
+
+UNIFIED_SOURCES = [
+ "TestUntrustedModules_Dll1.cpp",
+]
+
+RCFILE = "TestUntrustedModules_Dll1.rc"
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestUntrustedModules_Dll1.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll2/TestUntrustedModules_Dll2.cpp b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll2/TestUntrustedModules_Dll2.cpp
new file mode 100644
index 0000000000..4f6ce877eb
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll2/TestUntrustedModules_Dll2.cpp
@@ -0,0 +1,7 @@
+/* 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>
+
+BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll2/moz.build b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll2/moz.build
new file mode 100644
index 0000000000..fcefe41329
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll2/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestUntrustedModules_Dll2")
+
+UNIFIED_SOURCES = [
+ "TestUntrustedModules_Dll2.cpp",
+]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestUntrustedModules_Dll2.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/moz.build b/toolkit/xre/dllservices/tests/gtest/moz.build
new file mode 100644
index 0000000000..92f341841a
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/moz.build
@@ -0,0 +1,39 @@
+# 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/.
+
+Library("dllservicestest")
+
+UNIFIED_SOURCES += [
+ "TestDLLBlocklist.cpp",
+ "TestUntrustedModules.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/toolkit/components/telemetry/other",
+ "/toolkit/components/telemetry/tests/gtest",
+]
+
+TEST_DIRS += [
+ "rust",
+ "TestDllBlocklist_AllowByVersion",
+ "TestDllBlocklist_GMPluginProcessOnly",
+ "TestDllBlocklist_GPUProcessOnly",
+ "TestDllBlocklist_MatchByName",
+ "TestDllBlocklist_MatchByVersion",
+ "TestDllBlocklist_MultipleEntries_DifferentProcesses",
+ "TestDllBlocklist_MultipleEntries_SameProcessBackward",
+ "TestDllBlocklist_MultipleEntries_SameProcessForward",
+ "TestDllBlocklist_NoOpEntryPoint",
+ "TestDllBlocklist_SingleNotification1",
+ "TestDllBlocklist_SingleNotification2",
+ "TestDllBlocklist_SocketProcessOnly",
+ "TestDllBlocklist_UserBlocked",
+ "TestDllBlocklist_UtilityProcessOnly",
+ "TestUntrustedModules_Dll1",
+ "TestUntrustedModules_Dll2",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/toolkit/xre/dllservices/tests/gtest/rust/Cargo.toml b/toolkit/xre/dllservices/tests/gtest/rust/Cargo.toml
new file mode 100644
index 0000000000..a342fb8bf2
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/rust/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "dllservices-gtest"
+version = "0.1.0"
+authors = ["nobody@mozilla.com"]
+license = "MPL-2.0"
+description = "Tests for dllservices"
+
+[dependencies]
+uuid = { version = "1.0", features = ["v4"] }
+
+[lib]
+path = "test.rs"
diff --git a/toolkit/xre/dllservices/tests/gtest/rust/TestBCryptFallback.cpp b/toolkit/xre/dllservices/tests/gtest/rust/TestBCryptFallback.cpp
new file mode 100644
index 0000000000..ca4dbde3ab
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/rust/TestBCryptFallback.cpp
@@ -0,0 +1,113 @@
+/* -*- 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 <ntstatus.h>
+
+#include <bcrypt.h>
+#pragma comment(lib, "bcrypt.lib")
+
+#include "gtest/gtest.h"
+
+#include "nsWindowsDllInterceptor.h"
+
+#define RtlGenRandom SystemFunction036
+extern "C" BOOLEAN NTAPI RtlGenRandom(PVOID aRandomBuffer,
+ ULONG aRandomBufferLength);
+
+static mozilla::WindowsDllInterceptor BCryptIntercept;
+static mozilla::WindowsDllInterceptor::FuncHookType<
+ decltype(&::BCryptGenRandom)>
+ stub_BCryptGenRandom;
+
+static mozilla::WindowsDllInterceptor AdvApiIntercept;
+static mozilla::WindowsDllInterceptor::FuncHookType<decltype(&RtlGenRandom)>
+ stub_RtlGenRandom;
+
+volatile bool gAreHooksActive = false;
+volatile bool gHasPanicked = false;
+volatile bool gHasReachedBCryptGenRandom = false;
+volatile bool gHasReachedRtlGenRandom = false;
+
+NTSTATUS WINAPI patched_BCryptGenRandom(BCRYPT_ALG_HANDLE aAlgorithm,
+ PUCHAR aBuffer, ULONG aSize,
+ ULONG aFlags) {
+ if (gAreHooksActive) {
+ gHasReachedBCryptGenRandom = true;
+ // Force BCryptGenRandom failures when the hook is active.
+ return STATUS_UNSUCCESSFUL;
+ }
+ return stub_BCryptGenRandom(aAlgorithm, aBuffer, aSize, aFlags);
+}
+
+BOOLEAN NTAPI patched_RtlGenRandom(PVOID aRandomBuffer,
+ ULONG aRandomBufferLength) {
+ if (gAreHooksActive) {
+ gHasReachedRtlGenRandom = true;
+ }
+ return stub_RtlGenRandom(aRandomBuffer, aRandomBufferLength);
+}
+
+bool InitInterception() {
+ static bool sSuccess = []() {
+ BCryptIntercept.Init(L"bcrypt.dll");
+ AdvApiIntercept.Init(L"advapi32.dll");
+ return stub_BCryptGenRandom.SetDetour(BCryptIntercept, "BCryptGenRandom",
+ patched_BCryptGenRandom) &&
+ stub_RtlGenRandom.SetDetour(AdvApiIntercept, "SystemFunction036",
+ patched_RtlGenRandom);
+ }();
+ gAreHooksActive = true;
+ return sSuccess;
+}
+
+void ExitInterception() { gAreHooksActive = false; }
+
+DWORD WINAPI TestIsFallbackTriggeredThreadProc(LPVOID aParameter) {
+ auto testedFunction = reinterpret_cast<void (*)()>(aParameter);
+ EXPECT_TRUE(InitInterception());
+ MOZ_SEH_TRY { testedFunction(); }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ // Catch a potential Rust panic
+ gHasPanicked = true;
+ }
+ ExitInterception();
+ return 0;
+}
+
+// This function hooks BCryptGenRandom to make it fail, and hooks RtlGenRandom
+// to allow us to ensure that it gets visited as a fallback for
+// BCryptGenRandom.
+void TestIsFallbackTriggered(void (*aTestedFunction)()) {
+ gHasPanicked = false;
+ gHasReachedBCryptGenRandom = false;
+ gHasReachedRtlGenRandom = false;
+
+ // The HashMap test must run on a new thread, because some random bytes have
+ // already been collected but not used on the current thread by previous
+ // calls to HashMap::new in various locations of the code base. These bytes
+ // would be recycled instead of calling into BCryptGenRandom and RtlGenRandom
+ // if running the HashMap test on the current thread.
+ auto thread =
+ ::CreateThread(nullptr, 0, TestIsFallbackTriggeredThreadProc,
+ reinterpret_cast<void*>(aTestedFunction), 0, nullptr);
+ EXPECT_TRUE(bool(thread));
+ EXPECT_EQ(::WaitForSingleObject(thread, 5000),
+ static_cast<DWORD>(WAIT_OBJECT_0));
+
+ EXPECT_FALSE(gHasPanicked);
+ EXPECT_TRUE(gHasReachedBCryptGenRandom);
+ EXPECT_TRUE(gHasReachedRtlGenRandom);
+}
+
+extern "C" void Rust_TriggerGenRandomFromHashMap();
+extern "C" void Rust_TriggerGenRandomFromUuid();
+
+TEST(TestBCryptFallback, HashMapTriggersFallback)
+{ TestIsFallbackTriggered(Rust_TriggerGenRandomFromHashMap); }
+
+TEST(TestBCryptFallback, UuidTriggersFallback)
+{ TestIsFallbackTriggered(Rust_TriggerGenRandomFromUuid); }
diff --git a/toolkit/xre/dllservices/tests/gtest/rust/moz.build b/toolkit/xre/dllservices/tests/gtest/rust/moz.build
new file mode 100644
index 0000000000..eea8b9e978
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/rust/moz.build
@@ -0,0 +1,11 @@
+# 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/.
+
+Library("dllservicestest")
+
+UNIFIED_SOURCES += [
+ "TestBCryptFallback.cpp",
+]
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/toolkit/xre/dllservices/tests/gtest/rust/test.rs b/toolkit/xre/dllservices/tests/gtest/rust/test.rs
new file mode 100644
index 0000000000..51c2a6e156
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/rust/test.rs
@@ -0,0 +1,19 @@
+/* -*- Mode: rust; rust-indent-offset: 2 -*- */
+/* 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/. */
+
+use std::collections::HashMap;
+
+extern crate uuid;
+use uuid::Uuid;
+
+#[no_mangle]
+pub extern "C" fn Rust_TriggerGenRandomFromHashMap() -> () {
+ let _: HashMap<u32, u32> = HashMap::new();
+}
+
+#[no_mangle]
+pub extern "C" fn Rust_TriggerGenRandomFromUuid() -> () {
+ let _: Uuid = Uuid::new_v4();
+}
diff --git a/toolkit/xre/dllservices/tests/moz.build b/toolkit/xre/dllservices/tests/moz.build
new file mode 100644
index 0000000000..957243a476
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/moz.build
@@ -0,0 +1,47 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+GeckoCppUnitTests(
+ [
+ "TestDllBlocklistAssumptions",
+ "TestDllInterceptor",
+ "TestIATPatcher",
+ "TestMMPolicy",
+ ],
+ linkage=None,
+)
+
+if CONFIG["CPU_ARCH"] in ("x86", "x86_64"):
+ # Cross-process interceptors not yet supported on aarch64
+ GeckoCppUnitTests(
+ [
+ "TestDllInterceptorCrossProcess",
+ ],
+ linkage=None,
+ )
+
+OS_LIBS += [
+ "advapi32",
+ "ntdll",
+ "ole32",
+ "shlwapi",
+ "user32",
+ "uuid",
+]
+
+DELAYLOAD_DLLS += [
+ "shlwapi.dll",
+]
+
+if CONFIG["CC_TYPE"] in ("gcc", "clang"):
+ # This allows us to use wmain as the entry point on mingw
+ LDFLAGS += [
+ "-municode",
+ ]
+
+TEST_DIRS += [
+ "gtest",
+]