diff options
Diffstat (limited to 'toolkit/xre/dllservices/tests')
58 files changed, 4717 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..c9958df14f --- /dev/null +++ b/toolkit/xre/dllservices/tests/AssemblyPayloads.h @@ -0,0 +1,265 @@ +/* -*- 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 "mozilla/Attributes.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) MOZ_NAKED void MovPushRet() { + asm volatile( + "mov %0, %%rax;" + "push %%rax;" + "ret;" + : + : "i"(JumpDestination)); +} + +__declspec(dllexport) MOZ_NAKED void MovRaxJump() { + asm volatile( + "mov %0, %%rax;" + "jmpq *%%rax;" + : + : "i"(JumpDestination)); +} + +__declspec(dllexport) MOZ_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) MOZ_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) MOZ_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) MOZ_NAKED void IndirectCall() { + asm volatile( + "call *(%rip);" // Indirect call to 0x90909090`90909090 + "nop;nop;nop;nop;nop;nop;nop;nop;" + "ret;"); +} + +__declspec(dllexport) MOZ_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. +MOZ_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. +MOZ_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) MOZ_NAKED void PushRet() { + asm volatile( + "push %0;" + "ret;" + : + : "i"(JumpDestination)); +} + +__declspec(dllexport) MOZ_NAKED void MovEaxJump() { + asm volatile( + "mov %0, %%eax;" + "jmp *%%eax;" + : + : "i"(JumpDestination)); +} + +__declspec(dllexport) MOZ_NAKED void Opcode83() { + asm volatile( + "xor $0x42, %eax;" + "cmpl $1, 0xc(%ebp);"); +} + +__declspec(dllexport) MOZ_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) MOZ_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) MOZ_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) MOZ_NAKED void UnsupportedOp() { + asm volatile( + "ud2;" + "nop;nop;nop;nop;nop;nop;nop;nop;" + "nop;nop;nop;nop;nop;nop;nop;nop;"); +} + +// bug 1816936 +// Make sure no instruction ends at 5 (for x86) or 13 (for x64) bytes +__declspec(dllexport) MOZ_NAKED void SpareBytesAfterDetour() { + asm volatile( + "incl %eax;" // 2 bytes on x64, 1 byte on x86 + "mov $0x01234567, %eax;" // 5 bytes + "mov $0xfedcba98, %eax;" // 5 bytes + "mov $0x01234567, %eax;" // 5 bytes + "mov $0xfedcba98, %eax;"); // 5 bytes +} + +// bug 1816936 +// Make sure no instruction ends at 10 (for x64) bytes +// This is slightly different than SpareBytesAfterDetour so the compiler doesn't +// combine them, which would make the test that detours this one behave +// unexpectedly since it is already detoured. +__declspec(dllexport) MOZ_NAKED void SpareBytesAfterDetourFor10BytePatch() { + asm volatile( + "incl %eax;" // 2 bytes + "mov $0x01234567, %ecx;" // 5 bytes + "mov $0xfedcba98, %ebx;" // 5 bytes + "mov $0x01234567, %eax;" // 5 bytes + "mov $0xfedcba98, %edx;"); // 5 bytes +} +# 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..166b9012c2 --- /dev/null +++ b/toolkit/xre/dllservices/tests/TestDllInterceptor.cpp @@ -0,0 +1,1584 @@ +/* -*- 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/WindowsProcessMitigations.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); + +#define RtlGenRandom SystemFunction036 +extern "C" BOOLEAN NTAPI RtlGenRandom(PVOID aRandomBuffer, + ULONG aRandomBufferLength); + +extern "C" uintptr_t WINAPI TF_Notify(UINT uMsg, WPARAM wParam, LPARAM lParam); + +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) { + return ResolveRedirectedAddress(aFunc).GetAddress(); + } +}; + +#endif // _M_X64 + +void PrintFunctionBytes(FARPROC aFuncAddr, uint32_t aNumBytesToDump) { + printf("\tFirst %u bytes of function:\n\t", aNumBytesToDump); + auto code = reinterpret_cast<const uint8_t*>(aFuncAddr); + for (uint32_t i = 0; i < aNumBytesToDump; ++i) { + char suffix = (i < (aNumBytesToDump - 1)) ? ' ' : '\n'; + printf("%02hhX%c", code[i], suffix); + } + + fflush(stdout); +} + +// 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(); + PrintFunctionBytes(funcAddr, kNumBytesToDump); + } + 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() { + 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; + bool mSkipExec; + explicit TestCase(const char* aFunctionName, uintptr_t aExpectedStub, + bool aSkipExec = false) + : mFunctionName(aFunctionName), + mExpectedStub(aExpectedStub), + mPatchedOnce(false), + mSkipExec(aSkipExec) {} +} 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, + mozilla::IsUserShadowStackEnabled()), + 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, + mozilla::IsUserShadowStackEnabled()), + 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; + } + + if (testCase.mSkipExec) { + printf( + "TEST-SKIPPED | WindowsDllInterceptor | " + "Will not attempt to execute patched %s.\n", + testCase.mFunctionName); + } else { + 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{}; + // Because the current function could be located on the same memory page as + // DetouredCallJumper, we must preserve the permission to execute the page + // while we adjust the detoured call jumper (hence PAGE_EXECUTE_READWRITE). + if (!VirtualProtect(reinterpret_cast<void*>(DetouredCallJumper), sizeof bytes, + PAGE_EXECUTE_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) + +#ifndef MOZ_CODE_COVERAGE +# if defined(_M_X64) || defined(_M_IX86) +bool TestSpareBytesAfterDetour() { + WindowsDllInterceptor interceptor; + interceptor.Init("TestDllInterceptor.exe"); + InterceptorFunction& interceptorFunc = InterceptorFunction::Create(); + auto orig_func( + mozilla::MakeUnique<WindowsDllInterceptor::FuncHookType<void (*)()>>()); + + bool successful = orig_func->Set( + interceptor, "SpareBytesAfterDetour", + reinterpret_cast<void (*)()>(interceptorFunc.GetFunction())); + if (!successful) { + printf( + "TEST-FAILED | WindowsDllInterceptor | " + "Failed to detour SpareBytesAfterDetour.\n"); + return false; + } + FARPROC funcAddr = + ::GetProcAddress(GetModuleHandleW(nullptr), "SpareBytesAfterDetour"); + if (!funcAddr) { + printf( + "TEST-FAILED | WindowsDllInterceptor | " + "Failed to GetProcAddress() for SpareBytesAfterDetour.\n"); + return false; + } + uint8_t* funcBytes = reinterpret_cast<uint8_t*>(funcAddr); +# if defined(_M_X64) + // patch is 13 bytes + // the next instruction ends after 17 bytes + if (*(funcBytes + 13) != 0x90 || *(funcBytes + 14) != 0x90 || + *(funcBytes + 15) != 0x90 || *(funcBytes + 16) != 0x90) { + printf( + "TEST-FAILED | WindowsDllInterceptor | " + "SpareBytesAfterDetour doesn't have nop's after the patch.\n"); + PrintFunctionBytes(funcAddr, 17); + return false; + } + printf( + "TEST-PASS | WindowsDllInterceptor | " + "SpareBytesAfterDetour has correct nop bytes after the patch.\n"); +# elif defined(_M_IX86) + // patch is 5 bytes + // the next instruction ends after 6 bytes + if (*(funcBytes + 5) != 0x90) { + printf( + "TEST-FAILED | WindowsDllInterceptor | " + "SpareBytesAfterDetour doesn't have nop's after the patch.\n"); + PrintFunctionBytes(funcAddr, 6); + return false; + } + printf( + "TEST-PASS | WindowsDllInterceptor | " + "SpareBytesAfterDetour has correct nop bytes after the patch.\n"); +# endif + + return true; +} +# endif // defined(_M_X64) || defined(_M_IX86) + +# if defined(_M_X64) +bool TestSpareBytesAfterDetourFor10BytePatch() { + ShortInterceptor interceptor; + interceptor.TestOnlyDetourInit( + L"TestDllInterceptor.exe", + mozilla::interceptor::DetourFlags::eTestOnlyForceShortPatch); + InterceptorFunction& interceptorFunc = InterceptorFunction::Create(); + + auto orig_func( + mozilla::MakeUnique<ShortInterceptor::FuncHookType<void (*)()>>()); + bool successful = orig_func->SetDetour( + interceptor, "SpareBytesAfterDetourFor10BytePatch", + reinterpret_cast<void (*)()>(interceptorFunc.GetFunction())); + if (!successful) { + printf( + "TEST-FAILED | WindowsDllInterceptor | " + "Failed to detour SpareBytesAfterDetourFor10BytePatch.\n"); + return false; + } + FARPROC funcAddr = ::GetProcAddress(GetModuleHandleW(nullptr), + "SpareBytesAfterDetourFor10BytePatch"); + if (!funcAddr) { + printf( + "TEST-FAILED | WindowsDllInterceptor | " + "Failed to GetProcAddress() for " + "SpareBytesAfterDetourFor10BytePatch.\n"); + return false; + } + uint8_t* funcBytes = reinterpret_cast<uint8_t*>(funcAddr); + // patch is 10 bytes + // the next instruction ends after 12 bytes + if (*(funcBytes + 10) != 0x90 || *(funcBytes + 11) != 0x90) { + printf( + "TEST-FAILED | WindowsDllInterceptor | " + "SpareBytesAfterDetourFor10BytePatch doesn't have nop's after the " + "patch.\n"); + PrintFunctionBytes(funcAddr, 12); + return false; + } + printf( + "TEST-PASS | WindowsDllInterceptor | " + "SpareBytesAfterDetourFor10BytePatch has correct nop bytes after the " + "patch.\n"); + return true; +} +# endif +#endif // MOZ_CODE_COVERAGE + +bool TestDynamicCodePolicy() { + 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) && + TEST_HOOK_SKIP_EXEC("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) + 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("msctf.dll", TF_Notify, 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) +// 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) || defined(_M_IX86) + TestSpareBytesAfterDetour() && +# if defined(_M_X64) + TestSpareBytesAfterDetourFor10BytePatch() && +# endif // defined(_M_X64) +# endif // MOZ_CODE_COVERAGE +#endif // defined(_M_X64) || defined(_M_IX86) + // 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/TestDLLServices.cpp b/toolkit/xre/dllservices/tests/gtest/TestDLLServices.cpp new file mode 100644 index 0000000000..0ac2db7cbb --- /dev/null +++ b/toolkit/xre/dllservices/tests/gtest/TestDLLServices.cpp @@ -0,0 +1,83 @@ +/* -*- 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 "gtest/gtest.h" + +#include "mozilla/glue/WindowsDllServices.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/WinDllServices.h" +#include "nsDirectoryServiceDefs.h" + +using namespace mozilla; + +static nsString GetFullDllPath(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; +} + +struct BinaryOrgNameInfo { + wchar_t* dllPath; + nsString orgName; + bool* hasNestedMicrosoftCertificate; +}; + +static unsigned int __stdcall GetBinaryOrgName(void* binaryOrgNameInfo) { + BinaryOrgNameInfo* info = + reinterpret_cast<BinaryOrgNameInfo*>(binaryOrgNameInfo); + RefPtr<DllServices> dllSvc(DllServices::Get()); + auto signedBy(dllSvc->GetBinaryOrgName( + info->dllPath, info->hasNestedMicrosoftCertificate, + AuthenticodeFlags::SkipTrustVerification)); + info->orgName.Assign(nsString(signedBy.get())); + return 0; +} + +// See bug 1817026 +TEST(TestDllServices, GetNestedMicrosoftCertificate) +{ + constexpr auto kLeafName = u"msvcp140.dll"_ns; + nsString dllPath = GetFullDllPath(kLeafName); + bool hasNestedMicrosoftCertificate; + BinaryOrgNameInfo info; + info.dllPath = dllPath.get(); + info.hasNestedMicrosoftCertificate = &hasNestedMicrosoftCertificate; + + // Have to call GetBinaryOrgNames() off the main thread + nsAutoHandle threadW(reinterpret_cast<HANDLE>( + _beginthreadex(nullptr, 0, GetBinaryOrgName, &info, 0, nullptr))); + ::WaitForSingleObject(threadW, INFINITE); + EXPECT_EQ(info.orgName, + u"Microsoft Windows Software Compatibility Publisher"_ns); + EXPECT_TRUE(*info.hasNestedMicrosoftCertificate); +} + +TEST(TestDllServices, DoNotGetNestedMicrosoftCertificate) +{ + constexpr auto kLeafName = u"msvcp140.dll"_ns; + nsString dllPath = GetFullDllPath(kLeafName); + BinaryOrgNameInfo info; + info.dllPath = dllPath.get(); + // Mostly just to make sure we don't crash + info.hasNestedMicrosoftCertificate = nullptr; + + // Have to call GetMainBinaryOrgName() off the main thread + nsAutoHandle threadW(reinterpret_cast<HANDLE>( + _beginthreadex(nullptr, 0, GetBinaryOrgName, &info, 0, nullptr))); + ::WaitForSingleObject(threadW, INFINITE); + EXPECT_EQ(info.orgName, + u"Microsoft Windows Software Compatibility Publisher"_ns); +} 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..70ebb7c7d2 --- /dev/null +++ b/toolkit/xre/dllservices/tests/gtest/moz.build @@ -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/. + +Library("dllservicestest") + +UNIFIED_SOURCES += [ + "TestDLLBlocklist.cpp", + "TestDLLServices.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", +] + +TEST_HARNESS_FILES.gtest += ["msvcp140.dll"] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" diff --git a/toolkit/xre/dllservices/tests/gtest/msvcp140.dll b/toolkit/xre/dllservices/tests/gtest/msvcp140.dll Binary files differnew file mode 100644 index 0000000000..01d749d215 --- /dev/null +++ b/toolkit/xre/dllservices/tests/gtest/msvcp140.dll 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..8655f27b15 --- /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..b7225f1166 --- /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["TARGET_CPU"] 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", +] |