author | Carl Corcoran <ccorcoran@mozilla.com> |
Thu, 14 Jun 2018 00:15:26 -0700 | |
changeset 483394 | 130e3b2dd716ae4608587cb6755cda069a405259 |
parent 483393 | 5c13bf70a3e1bac455d93c136d8079ac3be022e9 |
child 483395 | 40d1931d184b4e4bf7eb35c32220442f9ae14f28 |
push id | 1815 |
push user | ffxbld-merge |
push date | Mon, 15 Oct 2018 10:40:45 +0000 |
treeherder | mozilla-release@18d4c09e9378 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | aklotz |
bugs | 1443411 |
milestone | 63.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/mozglue/build/WindowsDllBlocklist.cpp +++ b/mozglue/build/WindowsDllBlocklist.cpp @@ -369,16 +369,55 @@ static wchar_t* lastslash(wchar_t* s, in for (wchar_t* c = s + len - 1; c >= s; --c) { if (*c == L'\\' || *c == L'/') { return c; } } return nullptr; } + +#ifdef ENABLE_TESTS +DllLoadHookType gDllLoadHook = nullptr; + +void +DllBlocklist_SetDllLoadHook(DllLoadHookType aHook) +{ + gDllLoadHook = aHook; +} + +void +CallDllLoadHook(bool aDllLoaded, NTSTATUS aStatus, HANDLE aDllBase, PUNICODE_STRING aDllName) +{ + if (gDllLoadHook) { + gDllLoadHook(aDllLoaded, aStatus, aDllBase, aDllName); + } +} + +CreateThreadHookType gCreateThreadHook = nullptr; + +void +DllBlocklist_SetCreateThreadHook(CreateThreadHookType aHook) +{ + gCreateThreadHook = aHook; +} + +void +CallCreateThreadHook(bool aWasAllowed, void* aStartAddress) +{ + if (gCreateThreadHook) { + gCreateThreadHook(aWasAllowed, aStartAddress); + } +} + +#else // ENABLE_TESTS +#define CallDllLoadHook(...) +#define CallCreateThreadHook(...) +#endif // ENABLE_TESTS + static NTSTATUS NTAPI patched_LdrLoadDll (PWCHAR filePath, PULONG flags, PUNICODE_STRING moduleFileName, PHANDLE handle) { // We have UCS2 (UTF16?), we want ASCII, but we also just want the filename portion #define DLLNAME_MAX 128 char dllName[DLLNAME_MAX+1]; wchar_t *dll_part; char *dot; @@ -448,26 +487,28 @@ patched_LdrLoadDll (PWCHAR filePath, PUL if (!(sInitFlags & eDllBlocklistInitFlagWasBootstrapped)) { // Block a suspicious binary that uses various 12-digit hex strings // e.g. MovieMode.48CA2AEFA22D.dll (bug 973138) dot = strchr(dllName, '.'); if (dot && (strchr(dot+1, '.') == dot+13)) { char * end = nullptr; _strtoui64(dot+1, &end, 16); if (end == dot+13) { + CallDllLoadHook(false, STATUS_DLL_NOT_FOUND, 0, moduleFileName); return STATUS_DLL_NOT_FOUND; } } // Block binaries where the filename is at least 16 hex digits if (dot && ((dot - dllName) >= 16)) { char * current = dllName; while (current < dot && isxdigit(*current)) { current++; } if (current == dot) { + CallDllLoadHook(false, STATUS_DLL_NOT_FOUND, 0, moduleFileName); return STATUS_DLL_NOT_FOUND; } } // then compare to everything on the blocklist DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY(info); while (info->name) { if (strcmp(info->name, dllName) == 0) @@ -505,16 +546,17 @@ patched_LdrLoadDll (PWCHAR filePath, PUL if (sentinel.BailOut()) { goto continue_loading; } full_fname = getFullPath(filePath, fname); if (!full_fname) { // uh, we couldn't find the DLL at all, so... printf_stderr("LdrLoadDll: Blocking load of '%s' (SearchPathW didn't find it?)\n", dllName); + CallDllLoadHook(false, STATUS_DLL_NOT_FOUND, 0, moduleFileName); return STATUS_DLL_NOT_FOUND; } if (info->flags & DllBlockInfo::USE_TIMESTAMP) { fVersion = GetTimestamp(full_fname.get()); if (fVersion > info->maxVersion) { load_ok = true; } @@ -543,16 +585,17 @@ patched_LdrLoadDll (PWCHAR filePath, PUL } } } } if (!load_ok) { printf_stderr("LdrLoadDll: Blocking load of '%s' -- see http://www.mozilla.com/en-US/blocklist/\n", dllName); DllBlockSet::Add(info->name, fVersion); + CallDllLoadHook(false, STATUS_DLL_NOT_FOUND, 0, moduleFileName); return STATUS_DLL_NOT_FOUND; } } } continue_loading: #ifdef DEBUG_very_verbose printf_stderr("LdrLoadDll: continuing load... ('%S')\n", moduleFileName->Buffer); @@ -565,17 +608,19 @@ continue_loading: __LINE__); #ifdef _M_AMD64 // Prevent the stack walker from suspending this thread when LdrLoadDll // holds the RtlLookupFunctionEntry lock. AutoSuppressStackWalking suppress; #endif - return stub_LdrLoadDll(filePath, flags, moduleFileName, handle); + NTSTATUS ret = stub_LdrLoadDll(filePath, flags, moduleFileName, handle); + CallDllLoadHook(true, ret, handle ? *handle : 0, moduleFileName); + return ret; } #if defined(NIGHTLY_BUILD) // Map of specific thread proc addresses we should block. In particular, // LoadLibrary* APIs which indicate DLL injection static mozilla::Vector<void*, 4>* gStartAddressesToBlock; #endif @@ -611,17 +656,20 @@ NopThreadProc(void* /* aThreadParam */) return 0; } static MOZ_NORETURN void __fastcall patched_BaseThreadInitThunk(BOOL aIsInitialThread, void* aStartAddress, void* aThreadParam) { if (ShouldBlockThread(aStartAddress)) { + CallCreateThreadHook(false, aStartAddress); aStartAddress = (void*)NopThreadProc; + } else { + CallCreateThreadHook(true, aStartAddress); } stub_BaseThreadInitThunk(aIsInitialThread, aStartAddress, aThreadParam); } static WindowsDllInterceptor NtDllIntercept; static WindowsDllInterceptor Kernel32Intercept;
--- a/mozglue/build/WindowsDllBlocklist.h +++ b/mozglue/build/WindowsDllBlocklist.h @@ -4,32 +4,43 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_windowsdllblocklist_h #define mozilla_windowsdllblocklist_h #if (defined(_MSC_VER) || defined(__MINGW32__)) && (defined(_M_IX86) || defined(_M_X64)) #include <windows.h> +#ifdef ENABLE_TESTS +#include <winternl.h> +#endif // ENABLE_TESTS #include "mozilla/Attributes.h" #include "mozilla/Types.h" #define HAS_DLL_BLOCKLIST enum DllBlocklistInitFlags { eDllBlocklistInitFlagDefault = 0, eDllBlocklistInitFlagIsChildProcess = 1, eDllBlocklistInitFlagWasBootstrapped = 2 }; MFBT_API void DllBlocklist_Initialize(uint32_t aInitFlags = eDllBlocklistInitFlagDefault); MFBT_API void DllBlocklist_WriteNotes(HANDLE file); MFBT_API bool DllBlocklist_CheckStatus(); +#ifdef ENABLE_TESTS +typedef void (*DllLoadHookType)(bool aDllLoaded, NTSTATUS aNtStatus, + HANDLE aDllBase, PUNICODE_STRING aDllName); +MFBT_API void DllBlocklist_SetDllLoadHook(DllLoadHookType aHook); +typedef void (*CreateThreadHookType)(bool aWasAllowed, void *aStartAddress); +MFBT_API void DllBlocklist_SetCreateThreadHook(CreateThreadHookType aHook); +#endif // ENABLE_TESTS + // Forward declaration namespace mozilla { namespace glue { namespace detail { class DllServicesBase; } // namespace detail } // namespace glue } // namespace mozilla
new file mode 100644 --- /dev/null +++ b/mozglue/tests/gtest/Injector/Injector.cpp @@ -0,0 +1,50 @@ +/* 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 <stdio.h> +#include <stdlib.h> + +int +main(int argc, char** argv) +{ + if (argc < 4) { + fprintf(stderr, + "Not enough command line arguments.\n" + "Command line syntax:\n" + "Injector.exe [pid] [startAddr] [threadParam]\n"); + return 1; + } + + DWORD pid = strtoul(argv[1], 0, 0); +#ifdef HAVE_64BIT_BUILD + void* startAddr = (void*)strtoull(argv[2], 0, 0); + void* threadParam = (void*)strtoull(argv[3], 0, 0); +#else + void* startAddr = (void*)strtoul(argv[2], 0, 0); + void* threadParam = (void*)strtoul(argv[3], 0, 0); +#endif + HANDLE targetProc = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | + PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, + FALSE, + pid); + if (targetProc == nullptr) { + fprintf(stderr, "Error %lu opening target process, PID %lu \n", GetLastError(), pid); + return 1; + } + + HANDLE hThread = CreateRemoteThread(targetProc, nullptr, 0, + (LPTHREAD_START_ROUTINE)startAddr, + threadParam, 0, nullptr); + if (hThread == nullptr) { + fprintf(stderr, "Error %lu in CreateRemoteThread\n", GetLastError()); + CloseHandle(targetProc); + return 1; + } + + CloseHandle(hThread); + CloseHandle(targetProc); + + return 0; +}
new file mode 100644 --- /dev/null +++ b/mozglue/tests/gtest/Injector/moz.build @@ -0,0 +1,9 @@ +# 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 + +SimplePrograms(['Injector']) + +TEST_HARNESS_FILES.gtest += ['!Injector.exe']
new file mode 100644 --- /dev/null +++ b/mozglue/tests/gtest/InjectorDLL/InjectorDLL.cpp @@ -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/. */ + +#include <Windows.h> + +BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID) +{ + return TRUE; +} +
new file mode 100644 --- /dev/null +++ b/mozglue/tests/gtest/InjectorDLL/moz.build @@ -0,0 +1,14 @@ +# -*- 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('InjectorDLL') + +UNIFIED_SOURCES = [ + 'InjectorDLL.cpp', +] + +TEST_HARNESS_FILES.gtest += ['!InjectorDLL.dll']
new file mode 100644 --- /dev/null +++ b/mozglue/tests/gtest/TestDLLEject.cpp @@ -0,0 +1,277 @@ +/* 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 "gtest/gtest.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "nsUnicharUtils.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/WindowsDllBlocklist.h" + +static HANDLE sThreadWasBlocked = 0; +static HANDLE sThreadWasAllowed = 0; +static HANDLE sDllWasLoaded = 0; +static uintptr_t sStartAddress = 0; + +static const int sTimeoutMS = 10000; + +#define DLL_LEAF_NAME (u"InjectorDLL.dll") + +static nsString +makeString(PUNICODE_STRING aOther) +{ + size_t numChars = aOther->Length / sizeof(WCHAR); + return nsString((const char16_t *)aOther->Buffer, numChars); +} + +static void +DllLoadHook(bool aDllLoaded, NTSTATUS aStatus, HANDLE aDllBase, + PUNICODE_STRING aDllName) +{ + nsString str = makeString(aDllName); + + nsString dllName = nsString(DLL_LEAF_NAME); + if (StringEndsWith(str, dllName, nsCaseInsensitiveStringComparator())) { + if (aDllLoaded) { + SetEvent(sDllWasLoaded); + } + } +} + +static void +CreateThreadHook(bool aWasAllowed, void* aStartAddress) +{ + if (sStartAddress == (uintptr_t)aStartAddress) { + if (!aWasAllowed) { + SetEvent(sThreadWasBlocked); + } else { + SetEvent(sThreadWasAllowed); + } + } +} + +/** + * This function tests that we correctly block DLLs injected into this process + * via an injection technique which calls CreateRemoteThread with LoadLibrary*() + * as the thread start address, and the path to the DLL as the thread param. + * + * We prevent this technique by blocking threads with a start address in any + * LoadLibrary*() APIs. + * + * This function launches Injector.exe which simulates a 3rd-party application + * executing this technique. + * + * @param aGetArgsProc A callable procedure that specifies the thread start + * address and thread param passed as arguments to + * Injector.exe, which are in turn passed as arguments to + * CreateRemoteThread. This procedure is defined as such: + * + * void (*aGetArgsProc)(const nsString& aDllPath, + * const nsCString& aDllPathC, + * uintptr_t& startAddress, + * uintptr_t& threadParam); + * + * aDllPath is a WCHAR-friendly path to InjectorDLL.dll. + * Its memory will persist during the injection attempt. + * + * aDllPathC is the equivalent char-friendly path. + * + * startAddress and threadParam are passed into + * CreateRemoteThread as arguments. + */ +template<typename TgetArgsProc> +static void +DoTest_CreateRemoteThread_LoadLibrary(TgetArgsProc aGetArgsProc) +{ + sThreadWasBlocked = CreateEvent(NULL, FALSE, FALSE, NULL); + if (!sThreadWasBlocked) { + EXPECT_TRUE(!"Unable to create sThreadWasBlocked event"); + ASSERT_EQ(GetLastError(), ERROR_SUCCESS); + } + + sThreadWasAllowed = CreateEvent(NULL, FALSE, FALSE, NULL); + if (!sThreadWasAllowed) { + EXPECT_TRUE(!"Unable to create sThreadWasAllowed event"); + ASSERT_EQ(GetLastError(), ERROR_SUCCESS); + } + + sDllWasLoaded = CreateEvent(NULL, FALSE, FALSE, NULL); + if (!sDllWasLoaded) { + EXPECT_TRUE(!"Unable to create sDllWasLoaded event"); + ASSERT_EQ(GetLastError(), ERROR_SUCCESS); + } + + auto closeEvents = mozilla::MakeScopeExit([&](){ + CloseHandle(sThreadWasAllowed); + CloseHandle(sThreadWasBlocked); + CloseHandle(sDllWasLoaded); + }); + + // Hook into our DLL and thread blocking routines during this test. + DllBlocklist_SetDllLoadHook(DllLoadHook); + DllBlocklist_SetCreateThreadHook(CreateThreadHook); + auto undoHooks = mozilla::MakeScopeExit([&](){ + DllBlocklist_SetDllLoadHook(nullptr); + DllBlocklist_SetCreateThreadHook(nullptr); + }); + + // Launch Injector.exe. + STARTUPINFOW si = { 0 }; + si.cb = sizeof(si); + ::GetStartupInfoW(&si); + PROCESS_INFORMATION pi = { 0 }; + + nsString path(u"Injector.exe"); + nsString dllPath(DLL_LEAF_NAME); + nsCString dllPathC = NS_ConvertUTF16toUTF8(dllPath); + + uintptr_t threadParam; + aGetArgsProc(dllPath, dllPathC, sStartAddress, threadParam); + + path.AppendPrintf(" %lu 0x%p 0x%p", GetCurrentProcessId(), sStartAddress, + threadParam); + if (::CreateProcessW(NULL, path.get(), 0, 0, FALSE, 0, NULL, NULL, + &si, &pi) == FALSE) { + EXPECT_TRUE(!"Error in CreateProcessW() launching Injector.exe"); + ASSERT_EQ(GetLastError(), ERROR_SUCCESS); + return; + } + + // Ensure Injector.exe doesn't stay running after this test finishes. + auto cleanup = mozilla::MakeScopeExit([&](){ + CloseHandle(pi.hThread); + EXPECT_TRUE("Shutting down."); + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hProcess); + }); + + // Wait for information to come in and complete the test. + HANDLE handles[] = { + sThreadWasBlocked, + sThreadWasAllowed, + sDllWasLoaded, + pi.hProcess + }; + int handleCount = mozilla::ArrayLength(handles); + bool keepGoing = true; // Set to false to signal that the test is over. + + while(keepGoing) { + switch(WaitForMultipleObjectsEx(handleCount, handles, + FALSE, sTimeoutMS, FALSE)) { + case WAIT_OBJECT_0: { // sThreadWasBlocked + EXPECT_TRUE("Thread was blocked successfully."); + // No need to continue testing; blocking was successful. + keepGoing = false; + break; + } + case WAIT_OBJECT_0 + 1: { // sThreadWasAllowed + EXPECT_TRUE(!"Thread was allowed but should have been blocked."); + // No need to continue testing; blocking failed. + keepGoing = false; + break; + } + case WAIT_OBJECT_0 + 2: { // sDllWasLoaded + EXPECT_TRUE(!"DLL was loaded."); + // No need to continue testing; blocking failed and the DLL was + // consequently loaded. In theory we should never see this fire, because + // the thread being allowed should already trigger a test failure. + keepGoing = false; + break; + } + case WAIT_OBJECT_0 + 3: { // pi.hProcess + // Check to see if we got an error code from Injector.exe, in which case + // fail the test and exit. + DWORD exitCode; + if (!GetExitCodeProcess(pi.hProcess, &exitCode)) { + EXPECT_TRUE(!"Injector.exe exited but we were unable to get the exit code."); + keepGoing = false; + break; + } + EXPECT_EQ(exitCode, 0); + if (exitCode != 0) { + EXPECT_TRUE(!"Injector.exe returned non-zero exit code"); + keepGoing = false; + break; + } + // Process exited successfully. This can be ignored; we expect to get an + // event whether the DLL was loaded or blocked. + EXPECT_TRUE("Process exited as expected."); + handleCount--; + break; + } + case WAIT_TIMEOUT: + default: { + EXPECT_TRUE(!"An error or timeout occurred while waiting for activity " + "from Injector.exe"); + keepGoing = false; + break; + } + } + } + + // Double-check that injectordll is not loaded. + auto hExisting = GetModuleHandleW(dllPath.get()); + EXPECT_TRUE(!hExisting); + + // If the DLL was erroneously loaded, attempt to unload it before exiting. + if (hExisting) { + FreeLibrary(hExisting); + } + + return; +} + +TEST(TestInjectEject, CreateRemoteThread_LoadLibraryA) +{ + DoTest_CreateRemoteThread_LoadLibrary([](const nsString& dllPath, + const nsCString& dllPathC, + uintptr_t& aStartAddress, + uintptr_t& aThreadParam){ + HMODULE hKernel32 = GetModuleHandleW(L"Kernel32"); + aStartAddress = (uintptr_t)GetProcAddress(hKernel32, "LoadLibraryA"); + aThreadParam = (uintptr_t)dllPathC.get(); + }); +} + +TEST(TestInjectEject, CreateRemoteThread_LoadLibraryW) +{ + DoTest_CreateRemoteThread_LoadLibrary([](const nsString& dllPath, + const nsCString& dllPathC, + uintptr_t& aStartAddress, + uintptr_t& aThreadParam){ + HMODULE hKernel32 = GetModuleHandleW(L"Kernel32"); + aStartAddress = (uintptr_t)GetProcAddress(hKernel32, "LoadLibraryW"); + aThreadParam = (uintptr_t)dllPath.get(); + }); +} + +TEST(TestInjectEject, CreateRemoteThread_LoadLibraryExW) +{ + DoTest_CreateRemoteThread_LoadLibrary([](const nsString& dllPath, + const nsCString& dllPathC, + uintptr_t& aStartAddress, + uintptr_t& aThreadParam){ + HMODULE hKernel32 = GetModuleHandleW(L"Kernel32"); + // LoadLibraryEx requires three arguments so this injection method may not + // be viable. It's certainly not an allowable thread start in any case. + aStartAddress = (uintptr_t)GetProcAddress(hKernel32, "LoadLibraryExW"); + aThreadParam = (uintptr_t)dllPath.get(); + }); +} + +TEST(TestInjectEject, CreateRemoteThread_LoadLibraryExA) +{ + DoTest_CreateRemoteThread_LoadLibrary([](const nsString& dllPath, + const nsCString& dllPathC, + uintptr_t& aStartAddress, + uintptr_t& aThreadParam){ + HMODULE hKernel32 = GetModuleHandleW(L"Kernel32"); + aStartAddress = (uintptr_t)GetProcAddress(hKernel32, "LoadLibraryExA"); + aThreadParam = (uintptr_t)dllPathC.get(); + }); +}
new file mode 100644 --- /dev/null +++ b/mozglue/tests/gtest/moz.build @@ -0,0 +1,14 @@ +# 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/. + +UNIFIED_SOURCES += [ + 'TestDLLEject.cpp' +] + +FINAL_LIBRARY = 'xul-gtest' + +TEST_DIRS += [ + 'Injector', + 'InjectorDLL', +]