зеркало из https://github.com/mozilla/gecko-dev.git
Backed out 8 changesets (bug 1542830) for causing Nightly bustages. a=backout
Backed out changeset 6fcb417f7ff4 (bug 1542830) Backed out changeset 1aa253e6604a (bug 1542830) Backed out changeset 73ec288886cd (bug 1542830) Backed out changeset 84b903e60dc9 (bug 1542830) Backed out changeset 8e2da9ff5f5a (bug 1542830) Backed out changeset c4e547a6a039 (bug 1542830) Backed out changeset 919f1af7c135 (bug 1542830) Backed out changeset da7e775c4051 (bug 1542830) --HG-- rename : browser/app/winlauncher/freestanding/DllBlocklist.cpp => browser/app/winlauncher/DllBlocklistWin.cpp rename : browser/app/winlauncher/freestanding/DllBlocklist.h => browser/app/winlauncher/DllBlocklistWin.h rename : mozglue/dllservices/Authenticode.cpp => mozglue/build/Authenticode.cpp rename : mozglue/dllservices/Authenticode.h => mozglue/build/Authenticode.h rename : mozglue/misc/WinUtils.h => mozglue/build/MozglueUtils.h rename : mozglue/dllservices/WindowsDllBlocklist.cpp => mozglue/build/WindowsDllBlocklist.cpp rename : mozglue/dllservices/WindowsDllBlocklist.h => mozglue/build/WindowsDllBlocklist.h rename : mozglue/dllservices/WindowsDllBlocklistCommon.h => mozglue/build/WindowsDllBlocklistCommon.h rename : mozglue/dllservices/WindowsDllBlocklistDefs.in => mozglue/build/WindowsDllBlocklistDefs.in rename : mozglue/dllservices/WindowsDllServices.h => mozglue/build/WindowsDllServices.h rename : mozglue/dllservices/gen_dll_blocklist_defs.py => mozglue/build/gen_dll_blocklist_defs.py rename : toolkit/components/telemetry/docs/data/third-party-modules-ping.rst => toolkit/components/telemetry/docs/data/untrusted-modules-ping.rst rename : toolkit/components/telemetry/tests/unit/test_ThirdPartyModulesPing.js => toolkit/components/telemetry/tests/unit/test_UntrustedModulesPing.js rename : toolkit/xre/UntrustedModulesProcessor.cpp => toolkit/xre/ModuleEvaluator_windows.cpp rename : toolkit/xre/UntrustedModulesProcessor.h => toolkit/xre/ModuleEvaluator_windows.h rename : toolkit/xre/ModuleVersionInfo.cpp => toolkit/xre/ModuleVersionInfo_windows.cpp rename : toolkit/xre/ModuleVersionInfo.h => toolkit/xre/ModuleVersionInfo_windows.h
This commit is contained in:
Родитель
78b937649f
Коммит
125c934a04
|
@ -86,15 +86,6 @@ if CONFIG['OS_ARCH'] == 'WINNT':
|
|||
'version.dll',
|
||||
]
|
||||
|
||||
if CONFIG['CC_TYPE'] == 'clang-cl':
|
||||
libpath_flag = '-LIBPATH:'
|
||||
else:
|
||||
libpath_flag = '-L'
|
||||
|
||||
WIN32_EXE_LDFLAGS += [
|
||||
libpath_flag + OBJDIR + '/winlauncher/freestanding',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_SANDBOX'] and CONFIG['OS_ARCH'] == 'Darwin':
|
||||
USE_LIBS += [
|
||||
'mozsandbox',
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
|
||||
#ifdef XP_WIN
|
||||
# include "LauncherProcessWin.h"
|
||||
# include "mozilla/WindowsDllBlocklist.h"
|
||||
|
||||
# define XRE_WANT_ENVIRON
|
||||
# define strcasecmp _stricmp
|
||||
|
@ -39,6 +38,7 @@
|
|||
|
||||
#include "mozilla/Sprintf.h"
|
||||
#include "mozilla/StartupTimeline.h"
|
||||
#include "mozilla/WindowsDllBlocklist.h"
|
||||
#include "BaseProfiler.h"
|
||||
|
||||
#ifdef LIBFUZZER
|
||||
|
|
|
@ -1,113 +0,0 @@
|
|||
/* -*- 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 "nsWindowsDllInterceptor.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/BinarySearch.h"
|
||||
#include "mozilla/ImportDir.h"
|
||||
#include "mozilla/NativeNt.h"
|
||||
#include "mozilla/Types.h"
|
||||
#include "mozilla/WindowsDllBlocklist.h"
|
||||
#include "mozilla/WinHeaderOnlyUtils.h"
|
||||
|
||||
#include "DllBlocklistInit.h"
|
||||
#include "freestanding/DllBlocklist.h"
|
||||
|
||||
extern uint32_t gBlocklistInitFlags;
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
LauncherVoidResult InitializeDllBlocklistOOP(const wchar_t* aFullImagePath,
|
||||
HANDLE aChildProcess) {
|
||||
CrossProcessDllInterceptor intcpt(aChildProcess);
|
||||
intcpt.Init(L"ntdll.dll");
|
||||
|
||||
bool ok = freestanding::stub_NtMapViewOfSection.SetDetour(
|
||||
aChildProcess, intcpt, "NtMapViewOfSection",
|
||||
&freestanding::patched_NtMapViewOfSection);
|
||||
if (!ok) {
|
||||
return LAUNCHER_ERROR_GENERIC();
|
||||
}
|
||||
|
||||
ok = freestanding::stub_LdrLoadDll.SetDetour(
|
||||
aChildProcess, intcpt, "LdrLoadDll", &freestanding::patched_LdrLoadDll);
|
||||
if (!ok) {
|
||||
return LAUNCHER_ERROR_GENERIC();
|
||||
}
|
||||
|
||||
// Because aChildProcess has just been created in a suspended state, its
|
||||
// dynamic linker has not yet been initialized, thus its executable has
|
||||
// not yet been linked with ntdll.dll. If the blocklist hook intercepts a
|
||||
// library load prior to the link, the hook will be unable to invoke any
|
||||
// ntdll.dll functions.
|
||||
//
|
||||
// We know that the executable for our *current* process's binary is already
|
||||
// linked into ntdll, so we obtain the IAT from our own executable and graft
|
||||
// it onto the child process's IAT, thus enabling the child process's hook to
|
||||
// safely make its ntdll calls.
|
||||
|
||||
HMODULE ourModule;
|
||||
#if defined(_MSC_VER)
|
||||
ourModule = reinterpret_cast<HMODULE>(&__ImageBase);
|
||||
#else
|
||||
ourModule = ::GetModuleHandleW(nullptr);
|
||||
#endif // defined(_MSC_VER)
|
||||
|
||||
mozilla::nt::PEHeaders ourExeImage(ourModule);
|
||||
if (!ourExeImage) {
|
||||
return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
|
||||
}
|
||||
|
||||
// As part of our mitigation of binary tampering, copy our import directory
|
||||
// from the original in our executable file.
|
||||
LauncherVoidResult importDirRestored = RestoreImportDirectory(
|
||||
aFullImagePath, ourExeImage, aChildProcess, ourModule);
|
||||
if (importDirRestored.isErr()) {
|
||||
return importDirRestored;
|
||||
}
|
||||
|
||||
Maybe<Span<IMAGE_THUNK_DATA>> ntdllThunks =
|
||||
ourExeImage.GetIATThunksForModule("ntdll.dll");
|
||||
if (!ntdllThunks) {
|
||||
return LAUNCHER_ERROR_FROM_WIN32(ERROR_INVALID_DATA);
|
||||
}
|
||||
|
||||
SIZE_T bytesWritten;
|
||||
|
||||
{ // Scope for prot
|
||||
PIMAGE_THUNK_DATA firstIatThunk = ntdllThunks.value().data();
|
||||
SIZE_T iatLength = ntdllThunks.value().LengthBytes();
|
||||
|
||||
AutoVirtualProtect prot(firstIatThunk, iatLength, PAGE_READWRITE,
|
||||
aChildProcess);
|
||||
if (!prot) {
|
||||
return LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(prot.GetError());
|
||||
}
|
||||
|
||||
ok = !!::WriteProcessMemory(aChildProcess, firstIatThunk, firstIatThunk,
|
||||
iatLength, &bytesWritten);
|
||||
if (!ok || bytesWritten != iatLength) {
|
||||
return LAUNCHER_ERROR_FROM_LAST();
|
||||
}
|
||||
}
|
||||
|
||||
// Tell the mozglue blocklist that we have bootstrapped
|
||||
uint32_t newFlags = eDllBlocklistInitFlagWasBootstrapped;
|
||||
ok = !!::WriteProcessMemory(aChildProcess, &gBlocklistInitFlags, &newFlags,
|
||||
sizeof(newFlags), &bytesWritten);
|
||||
if (!ok || bytesWritten != sizeof(newFlags)) {
|
||||
return LAUNCHER_ERROR_FROM_LAST();
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -4,15 +4,15 @@
|
|||
* 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 "nsWindowsDllInterceptor.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/BinarySearch.h"
|
||||
#include "mozilla/ImportDir.h"
|
||||
#include "mozilla/NativeNt.h"
|
||||
#include "mozilla/Types.h"
|
||||
|
||||
#include "DllBlocklist.h"
|
||||
#include "LoaderPrivateAPI.h"
|
||||
#include "ModuleLoadFrame.h"
|
||||
#include "mozilla/WindowsDllBlocklist.h"
|
||||
#include "mozilla/WinHeaderOnlyUtils.h"
|
||||
|
||||
// clang-format off
|
||||
#define MOZ_LITERAL_UNICODE_STRING(s) \
|
||||
|
@ -38,6 +38,8 @@ DLL_BLOCKLIST_DEFINITIONS_BEGIN
|
|||
DLL_BLOCKLIST_DEFINITIONS_END
|
||||
#endif
|
||||
|
||||
extern uint32_t gBlocklistInitFlags;
|
||||
|
||||
static const HANDLE kCurrentProcess = reinterpret_cast<HANDLE>(-1);
|
||||
|
||||
class MOZ_STATIC_CLASS MOZ_TRIVIAL_CTOR_DTOR NativeNtBlockSet final {
|
||||
|
@ -54,7 +56,7 @@ class MOZ_STATIC_CLASS MOZ_TRIVIAL_CTOR_DTOR NativeNtBlockSet final {
|
|||
|
||||
public:
|
||||
// Constructor and destructor MUST be trivial
|
||||
constexpr NativeNtBlockSet() : mFirstEntry(nullptr) {}
|
||||
NativeNtBlockSet() = default;
|
||||
~NativeNtBlockSet() = default;
|
||||
|
||||
void Add(const UNICODE_STRING& aName, uint64_t aVersion);
|
||||
|
@ -67,23 +69,36 @@ class MOZ_STATIC_CLASS MOZ_TRIVIAL_CTOR_DTOR NativeNtBlockSet final {
|
|||
|
||||
private:
|
||||
NativeNtBlockSetEntry* mFirstEntry;
|
||||
mozilla::nt::SRWLock mLock;
|
||||
// SRWLOCK_INIT == 0, so this is okay to use without any additional work as
|
||||
// long as NativeNtBlockSet is instantiated statically
|
||||
SRWLOCK mLock;
|
||||
};
|
||||
|
||||
NativeNtBlockSet::NativeNtBlockSetEntry* NativeNtBlockSet::NewEntry(
|
||||
const UNICODE_STRING& aName, uint64_t aVersion,
|
||||
NativeNtBlockSet::NativeNtBlockSetEntry* aNextEntry) {
|
||||
return mozilla::freestanding::RtlNew<NativeNtBlockSetEntry>(aName, aVersion,
|
||||
aNextEntry);
|
||||
HANDLE processHeap = mozilla::nt::RtlGetProcessHeap();
|
||||
if (!processHeap) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PVOID memory =
|
||||
::RtlAllocateHeap(processHeap, 0, sizeof(NativeNtBlockSetEntry));
|
||||
if (!memory) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new (memory) NativeNtBlockSetEntry(aName, aVersion, aNextEntry);
|
||||
}
|
||||
|
||||
void NativeNtBlockSet::Add(const UNICODE_STRING& aName, uint64_t aVersion) {
|
||||
mozilla::nt::AutoExclusiveLock lock(mLock);
|
||||
::RtlAcquireSRWLockExclusive(&mLock);
|
||||
|
||||
for (NativeNtBlockSetEntry* entry = mFirstEntry; entry;
|
||||
entry = entry->mNext) {
|
||||
if (::RtlEqualUnicodeString(&entry->mName, &aName, TRUE) &&
|
||||
aVersion == entry->mVersion) {
|
||||
::RtlReleaseSRWLockExclusive(&mLock);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -91,6 +106,8 @@ void NativeNtBlockSet::Add(const UNICODE_STRING& aName, uint64_t aVersion) {
|
|||
// Not present, add it
|
||||
NativeNtBlockSetEntry* newEntry = NewEntry(aName, aVersion, mFirstEntry);
|
||||
mFirstEntry = newEntry;
|
||||
|
||||
::RtlReleaseSRWLockExclusive(&mLock);
|
||||
}
|
||||
|
||||
void NativeNtBlockSet::Write(HANDLE aFile) {
|
||||
|
@ -148,15 +165,8 @@ extern "C" void MOZ_EXPORT NativeNtBlockSet_Write(HANDLE aHandle) {
|
|||
gBlockSet.Write(aHandle);
|
||||
}
|
||||
|
||||
enum class BlockAction {
|
||||
Allow,
|
||||
SubstituteLSP,
|
||||
Error,
|
||||
Deny,
|
||||
};
|
||||
|
||||
static BlockAction CheckBlockInfo(const DllBlockInfo* aInfo, void* aBaseAddress,
|
||||
uint64_t& aVersion) {
|
||||
static bool CheckBlockInfo(const DllBlockInfo* aInfo, void* aBaseAddress,
|
||||
uint64_t& aVersion) {
|
||||
aVersion = DllBlockInfo::ALL_VERSIONS;
|
||||
|
||||
if (aInfo->mFlags &
|
||||
|
@ -164,59 +174,52 @@ static BlockAction CheckBlockInfo(const DllBlockInfo* aInfo, void* aBaseAddress,
|
|||
RTL_OSVERSIONINFOW osv = {sizeof(osv)};
|
||||
NTSTATUS ntStatus = ::RtlGetVersion(&osv);
|
||||
if (!NT_SUCCESS(ntStatus)) {
|
||||
return BlockAction::Error;
|
||||
// huh?
|
||||
return false;
|
||||
}
|
||||
|
||||
if (osv.dwMajorVersion < 8) {
|
||||
return BlockAction::Allow;
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((aInfo->mFlags & DllBlockInfo::BLOCK_WIN8_ONLY) &&
|
||||
(osv.dwMajorVersion > 8 ||
|
||||
(osv.dwMajorVersion == 8 && osv.dwMinorVersion > 0))) {
|
||||
return BlockAction::Allow;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// We're not bootstrapping child processes at this time, so this case is
|
||||
// always true.
|
||||
if (aInfo->mFlags & DllBlockInfo::CHILD_PROCESSES_ONLY) {
|
||||
return BlockAction::Allow;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (aInfo->mMaxVersion == DllBlockInfo::ALL_VERSIONS) {
|
||||
return BlockAction::Deny;
|
||||
return false;
|
||||
}
|
||||
|
||||
mozilla::nt::PEHeaders headers(aBaseAddress);
|
||||
if (!headers) {
|
||||
return BlockAction::Error;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aInfo->mFlags & DllBlockInfo::USE_TIMESTAMP) {
|
||||
DWORD timestamp;
|
||||
if (!headers.GetTimeStamp(timestamp)) {
|
||||
return BlockAction::Error;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (timestamp > aInfo->mMaxVersion) {
|
||||
return BlockAction::Allow;
|
||||
}
|
||||
|
||||
return BlockAction::Deny;
|
||||
return timestamp > aInfo->mMaxVersion;
|
||||
}
|
||||
|
||||
// Else we try to get the file version information. Note that we don't have
|
||||
// access to GetFileVersionInfo* APIs.
|
||||
if (!headers.GetVersionInfo(aVersion)) {
|
||||
return BlockAction::Error;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aInfo->IsVersionBlocked(aVersion)) {
|
||||
return BlockAction::Deny;
|
||||
}
|
||||
|
||||
return BlockAction::Allow;
|
||||
return !aInfo->IsVersionBlocked(aVersion);
|
||||
}
|
||||
|
||||
struct DllBlockInfoComparator {
|
||||
|
@ -231,11 +234,10 @@ struct DllBlockInfoComparator {
|
|||
PCUNICODE_STRING mTarget;
|
||||
};
|
||||
|
||||
static BlockAction IsDllAllowed(const UNICODE_STRING& aLeafName,
|
||||
void* aBaseAddress) {
|
||||
static bool IsDllAllowed(const UNICODE_STRING& aLeafName, void* aBaseAddress) {
|
||||
if (mozilla::nt::Contains12DigitHexString(aLeafName) ||
|
||||
mozilla::nt::IsFileNameAtLeast16HexDigits(aLeafName)) {
|
||||
return BlockAction::Deny;
|
||||
return false;
|
||||
}
|
||||
|
||||
DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY(info);
|
||||
|
@ -245,39 +247,26 @@ static BlockAction IsDllAllowed(const UNICODE_STRING& aLeafName,
|
|||
|
||||
size_t match;
|
||||
if (!BinarySearchIf(info, 0, infoNumEntries, comp, &match)) {
|
||||
return BlockAction::Allow;
|
||||
return true;
|
||||
}
|
||||
|
||||
const DllBlockInfo& entry = info[match];
|
||||
|
||||
uint64_t version;
|
||||
BlockAction checkResult = CheckBlockInfo(&entry, aBaseAddress, version);
|
||||
if (checkResult != BlockAction::Allow) {
|
||||
if (!CheckBlockInfo(&entry, aBaseAddress, version)) {
|
||||
gBlockSet.Add(entry.mName, version);
|
||||
return false;
|
||||
}
|
||||
|
||||
return checkResult;
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
namespace freestanding {
|
||||
|
||||
CrossProcessDllInterceptor::FuncHookType<LdrLoadDllPtr> stub_LdrLoadDll;
|
||||
|
||||
NTSTATUS NTAPI patched_LdrLoadDll(PWCHAR aDllPath, PULONG aFlags,
|
||||
PUNICODE_STRING aDllName,
|
||||
PHANDLE aOutHandle) {
|
||||
ModuleLoadFrame frame(aDllName);
|
||||
|
||||
NTSTATUS ntStatus = stub_LdrLoadDll(aDllPath, aFlags, aDllName, aOutHandle);
|
||||
|
||||
return frame.SetLoadStatus(ntStatus, aOutHandle);
|
||||
}
|
||||
|
||||
CrossProcessDllInterceptor::FuncHookType<NtMapViewOfSectionPtr>
|
||||
typedef decltype(&NtMapViewOfSection) NtMapViewOfSection_func;
|
||||
static mozilla::CrossProcessDllInterceptor::FuncHookType<
|
||||
NtMapViewOfSection_func>
|
||||
stub_NtMapViewOfSection;
|
||||
|
||||
NTSTATUS NTAPI patched_NtMapViewOfSection(
|
||||
static 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,
|
||||
|
@ -311,35 +300,109 @@ NTSTATUS NTAPI patched_NtMapViewOfSection(
|
|||
}
|
||||
|
||||
// Get the section name
|
||||
nt::AllocatedUnicodeString sectionFileName(
|
||||
gLoaderPrivateAPI.GetSectionName(*aBaseAddress));
|
||||
if (sectionFileName.IsEmpty()) {
|
||||
mozilla::nt::MemorySectionNameBuf buf;
|
||||
|
||||
ntStatus = ::NtQueryVirtualMemory(aProcess, *aBaseAddress, MemorySectionName,
|
||||
&buf, sizeof(buf), nullptr);
|
||||
if (!NT_SUCCESS(ntStatus)) {
|
||||
::NtUnmapViewOfSection(aProcess, *aBaseAddress);
|
||||
return STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
// Find the leaf name
|
||||
UNICODE_STRING leaf;
|
||||
nt::GetLeafName(&leaf, sectionFileName);
|
||||
mozilla::nt::GetLeafName(&leaf, &buf.mSectionFileName);
|
||||
|
||||
// Check blocklist
|
||||
BlockAction blockAction = IsDllAllowed(leaf, *aBaseAddress);
|
||||
|
||||
if (blockAction == BlockAction::Allow) {
|
||||
ModuleLoadFrame::NotifySectionMap(std::move(sectionFileName), *aBaseAddress,
|
||||
stubStatus);
|
||||
if (IsDllAllowed(leaf, *aBaseAddress)) {
|
||||
return stubStatus;
|
||||
}
|
||||
|
||||
if (blockAction == BlockAction::SubstituteLSP) {
|
||||
// Notify patched_LdrLoadDll that it will be necessary to perform a
|
||||
// substitution before returning.
|
||||
ModuleLoadFrame::NotifyLSPSubstitutionRequired(&leaf);
|
||||
}
|
||||
|
||||
::NtUnmapViewOfSection(aProcess, *aBaseAddress);
|
||||
return STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
} // namespace freestanding
|
||||
#if defined(_MSC_VER)
|
||||
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
LauncherVoidResult InitializeDllBlocklistOOP(const wchar_t* aFullImagePath,
|
||||
HANDLE aChildProcess) {
|
||||
mozilla::CrossProcessDllInterceptor intcpt(aChildProcess);
|
||||
intcpt.Init(L"ntdll.dll");
|
||||
bool ok = stub_NtMapViewOfSection.SetDetour(
|
||||
aChildProcess, intcpt, "NtMapViewOfSection", &patched_NtMapViewOfSection);
|
||||
if (!ok) {
|
||||
return LAUNCHER_ERROR_GENERIC();
|
||||
}
|
||||
|
||||
// Because aChildProcess has just been created in a suspended state, its
|
||||
// dynamic linker has not yet been initialized, thus its executable has
|
||||
// not yet been linked with ntdll.dll. If the blocklist hook intercepts a
|
||||
// library load prior to the link, the hook will be unable to invoke any
|
||||
// ntdll.dll functions.
|
||||
//
|
||||
// We know that the executable for our *current* process's binary is already
|
||||
// linked into ntdll, so we obtain the IAT from our own executable and graft
|
||||
// it onto the child process's IAT, thus enabling the child process's hook to
|
||||
// safely make its ntdll calls.
|
||||
|
||||
HMODULE ourModule;
|
||||
#if defined(_MSC_VER)
|
||||
ourModule = reinterpret_cast<HMODULE>(&__ImageBase);
|
||||
#else
|
||||
ourModule = ::GetModuleHandleW(nullptr);
|
||||
#endif // defined(_MSC_VER)
|
||||
|
||||
mozilla::nt::PEHeaders ourExeImage(ourModule);
|
||||
if (!ourExeImage) {
|
||||
return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
|
||||
}
|
||||
|
||||
// As part of our mitigation of binary tampering, copy our import directory
|
||||
// from the original in our executable file.
|
||||
LauncherVoidResult importDirRestored = RestoreImportDirectory(
|
||||
aFullImagePath, ourExeImage, aChildProcess, ourModule);
|
||||
if (importDirRestored.isErr()) {
|
||||
return importDirRestored;
|
||||
}
|
||||
|
||||
Maybe<Span<IMAGE_THUNK_DATA>> ntdllThunks =
|
||||
ourExeImage.GetIATThunksForModule("ntdll.dll");
|
||||
if (!ntdllThunks) {
|
||||
return LAUNCHER_ERROR_FROM_WIN32(ERROR_INVALID_DATA);
|
||||
}
|
||||
|
||||
SIZE_T bytesWritten;
|
||||
|
||||
{ // Scope for prot
|
||||
PIMAGE_THUNK_DATA firstIatThunk = ntdllThunks.value().data();
|
||||
SIZE_T iatLength = ntdllThunks.value().LengthBytes();
|
||||
|
||||
AutoVirtualProtect prot(firstIatThunk, iatLength, PAGE_READWRITE,
|
||||
aChildProcess);
|
||||
if (!prot) {
|
||||
return LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(prot.GetError());
|
||||
}
|
||||
|
||||
ok = !!::WriteProcessMemory(aChildProcess, firstIatThunk, firstIatThunk,
|
||||
iatLength, &bytesWritten);
|
||||
if (!ok || bytesWritten != iatLength) {
|
||||
return LAUNCHER_ERROR_FROM_LAST();
|
||||
}
|
||||
}
|
||||
|
||||
// Tell the mozglue blocklist that we have bootstrapped
|
||||
uint32_t newFlags = eDllBlocklistInitFlagWasBootstrapped;
|
||||
ok = !!::WriteProcessMemory(aChildProcess, &gBlocklistInitFlags, &newFlags,
|
||||
sizeof(newFlags), &bytesWritten);
|
||||
if (!ok || bytesWritten != sizeof(newFlags)) {
|
||||
return LAUNCHER_ERROR_FROM_LAST();
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -4,8 +4,8 @@
|
|||
* 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/. */
|
||||
|
||||
#ifndef mozilla_DllBlocklistInit_h
|
||||
#define mozilla_DllBlocklistInit_h
|
||||
#ifndef mozilla_DllBlocklistWin_h
|
||||
#define mozilla_DllBlocklistWin_h
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
|
@ -18,4 +18,4 @@ LauncherVoidResult InitializeDllBlocklistOOP(const wchar_t* aFullImagePath,
|
|||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_DllBlocklistInit_h
|
||||
#endif // mozilla_DllBlocklistWin_h
|
|
@ -24,7 +24,7 @@
|
|||
#include <windows.h>
|
||||
#include <processthreadsapi.h>
|
||||
|
||||
#include "DllBlocklistInit.h"
|
||||
#include "DllBlocklistWin.h"
|
||||
#include "ErrorHandler.h"
|
||||
#include "LaunchUnelevated.h"
|
||||
#include "ProcThreadAttributes.h"
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
/* -*- 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/LoaderAPIInterfaces.h"
|
||||
|
||||
#include "freestanding/LoaderPrivateAPI.h"
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# include <intrin.h>
|
||||
# pragma intrinsic(_ReturnAddress)
|
||||
# define RETURN_ADDRESS() _ReturnAddress()
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
# define RETURN_ADDRESS() \
|
||||
__builtin_extract_return_addr(__builtin_return_address(0))
|
||||
#endif
|
||||
|
||||
static bool CheckForMozglue(void* aReturnAddress) {
|
||||
HMODULE callingModule;
|
||||
if (!::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
|
||||
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
||||
reinterpret_cast<LPCWSTR>(aReturnAddress),
|
||||
&callingModule)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return callingModule && callingModule == ::GetModuleHandleW(L"mozglue.dll");
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
extern "C" MOZ_EXPORT nt::LoaderAPI* GetNtLoaderAPI(
|
||||
nt::LoaderObserver* aNewObserver) {
|
||||
const bool isCallerMozglue = CheckForMozglue(RETURN_ADDRESS());
|
||||
MOZ_ASSERT(isCallerMozglue);
|
||||
if (!isCallerMozglue) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
freestanding::EnsureInitialized();
|
||||
freestanding::LoaderPrivateAPI& api = freestanding::gLoaderPrivateAPI;
|
||||
api.SetObserver(aNewObserver);
|
||||
|
||||
return &api;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -1,23 +0,0 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_NtLoaderAPI_h
|
||||
#define mozilla_NtLoaderAPI_h
|
||||
|
||||
#include "mozilla/LoaderAPIInterfaces.h"
|
||||
|
||||
#if !defined(IMPL_MFBT)
|
||||
# error "This should only be included from mozglue!"
|
||||
#endif // !defined(IMPL_MFBT)
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
extern "C" MOZ_IMPORT_API nt::LoaderAPI* GetNtLoaderAPI(
|
||||
nt::LoaderObserver* aNewObserver);
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_NtLoaderAPI_h
|
|
@ -1,38 +0,0 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_freestanding_DllBlocklist_h
|
||||
#define mozilla_freestanding_DllBlocklist_h
|
||||
|
||||
#include "mozilla/NativeNt.h"
|
||||
#include "nsWindowsDllInterceptor.h"
|
||||
#include "mozilla/WinHeaderOnlyUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace freestanding {
|
||||
|
||||
NTSTATUS NTAPI patched_LdrLoadDll(PWCHAR aDllPath, PULONG aFlags,
|
||||
PUNICODE_STRING aDllName, PHANDLE aOutHandle);
|
||||
|
||||
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);
|
||||
|
||||
using LdrLoadDllPtr = decltype(&::LdrLoadDll);
|
||||
|
||||
extern CrossProcessDllInterceptor::FuncHookType<LdrLoadDllPtr> stub_LdrLoadDll;
|
||||
|
||||
using NtMapViewOfSectionPtr = decltype(&::NtMapViewOfSection);
|
||||
|
||||
extern CrossProcessDllInterceptor::FuncHookType<NtMapViewOfSectionPtr>
|
||||
stub_NtMapViewOfSection;
|
||||
|
||||
} // namespace freestanding
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_freestanding_DllBlocklist_h
|
|
@ -1,59 +0,0 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_freestanding_Freestanding_h
|
||||
#define mozilla_freestanding_Freestanding_h
|
||||
|
||||
/**
|
||||
* This header is automatically included in all source code residing in the
|
||||
* /browser/app/winlauncher/freestanding directory.
|
||||
*/
|
||||
|
||||
#if defined(__STDC_HOSTED__) && __STDC_HOSTED__ == 1
|
||||
# error "This header should only be included by freestanding code"
|
||||
#endif // defined(__STDC_HOSTED__) && __STDC_HOSTED__ == 1
|
||||
|
||||
#include "mozilla/NativeNt.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace freestanding {
|
||||
|
||||
/**
|
||||
* Since this library is the only part of firefox.exe that needs special
|
||||
* treatment with respect to the heap, we implement |RtlNew| and |RtlDelete|
|
||||
* to be used instead of |new| and |delete| for any heap allocations inside
|
||||
* the freestanding library.
|
||||
*/
|
||||
template <typename T, typename... Args>
|
||||
inline static T* RtlNew(Args&&... aArgs) {
|
||||
void* ptr = ::RtlAllocateHeap(nt::RtlGetProcessHeap(), 0, sizeof(T));
|
||||
if (!ptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new (ptr) T(std::forward<Args>(aArgs)...);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline static void RtlDelete(T* aPtr) {
|
||||
if (!aPtr) {
|
||||
return;
|
||||
}
|
||||
|
||||
aPtr->~T();
|
||||
::RtlFreeHeap(nt::RtlGetProcessHeap(), 0, aPtr);
|
||||
}
|
||||
|
||||
} // namespace freestanding
|
||||
} // namespace mozilla
|
||||
|
||||
// Initialization code for all statically-allocated data in freestanding is
|
||||
// placed into a separate section. This allows us to initialize any
|
||||
// freestanding statics without needing to initialize everything else in this
|
||||
// binary.
|
||||
#pragma init_seg(".freestd$g")
|
||||
|
||||
#endif // mozilla_freestanding_Freestanding_h
|
|
@ -1,100 +0,0 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_LoaderAPIInternal_h
|
||||
#define mozilla_LoaderAPIInternal_h
|
||||
|
||||
#include "mozilla/ModuleLoadInfo.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace nt {
|
||||
|
||||
class NS_NO_VTABLE LoaderObserver {
|
||||
public:
|
||||
/**
|
||||
* Notification that a DLL load has begun.
|
||||
*
|
||||
* @param aContext Outparam that allows this observer to store any context
|
||||
* information pertaining to the current load.
|
||||
* @param aRequestedDllName The DLL name requested by whatever invoked the
|
||||
* loader. This name may not match the effective
|
||||
* name of the DLL once the loader has completed
|
||||
* its path search.
|
||||
*/
|
||||
virtual void OnBeginDllLoad(void** aContext,
|
||||
PCUNICODE_STRING aRequestedDllName) = 0;
|
||||
|
||||
/**
|
||||
* Query the observer to determine whether the DLL named |aLSPLeafName| needs
|
||||
* to be substituted with another module, and substitute the module handle
|
||||
* when necessary.
|
||||
*
|
||||
* @return true when substitution occurs, otherwise false
|
||||
*/
|
||||
virtual bool SubstituteForLSP(PCUNICODE_STRING aLSPLeafName,
|
||||
PHANDLE aOutHandle) = 0;
|
||||
|
||||
/**
|
||||
* Notification that a DLL load has ended.
|
||||
*
|
||||
* @param aContext The context that was set by the corresponding call to
|
||||
* OnBeginDllLoad
|
||||
* @param aNtStatus The NTSTATUS returned by LdrLoadDll
|
||||
* @param aModuleLoadInfo Telemetry information that was gathered about the
|
||||
* load.
|
||||
*/
|
||||
virtual void OnEndDllLoad(void* aContext, NTSTATUS aNtStatus,
|
||||
ModuleLoadInfo&& aModuleLoadInfo) = 0;
|
||||
|
||||
/**
|
||||
* Called to inform the observer that it is no longer active and, if
|
||||
* necessary, call aNext->OnForward() with any accumulated telemetry
|
||||
* information.
|
||||
*/
|
||||
virtual void Forward(LoaderObserver* aNext) = 0;
|
||||
|
||||
/**
|
||||
* Receives a vector of module load telemetry from a previous LoaderObserver.
|
||||
*/
|
||||
virtual void OnForward(ModuleLoadInfoVec&& aInfo) = 0;
|
||||
};
|
||||
|
||||
class NS_NO_VTABLE LoaderAPI {
|
||||
public:
|
||||
/**
|
||||
* Construct a new ModuleLoadInfo structure and notify the LoaderObserver
|
||||
* that a library load is beginning.
|
||||
*/
|
||||
virtual ModuleLoadInfo ConstructAndNotifyBeginDllLoad(
|
||||
void** aContext, PCUNICODE_STRING aRequestedDllName) = 0;
|
||||
|
||||
/**
|
||||
* Query to determine whether the DLL named |aLSPLeafName| needs to be
|
||||
* substituted with another module, and substitute the module handle when
|
||||
* necessary.
|
||||
*
|
||||
* @return true when substitution occurs, otherwise false
|
||||
*/
|
||||
virtual bool SubstituteForLSP(PCUNICODE_STRING aLSPLeafName,
|
||||
PHANDLE aOutHandle) = 0;
|
||||
|
||||
/**
|
||||
* Notification that a DLL load has ended.
|
||||
*/
|
||||
virtual void NotifyEndDllLoad(void* aContext, NTSTATUS aLoadNtStatus,
|
||||
ModuleLoadInfo&& aModuleLoadInfo) = 0;
|
||||
|
||||
/**
|
||||
* Given the address of a mapped section, obtain the name of the file that is
|
||||
* backing it.
|
||||
*/
|
||||
virtual AllocatedUnicodeString GetSectionName(void* aSectionAddr) = 0;
|
||||
};
|
||||
|
||||
} // namespace nt
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_LoaderAPIInternal_h
|
|
@ -1,257 +0,0 @@
|
|||
/* -*- 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 "LoaderPrivateAPI.h"
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Types.h"
|
||||
#include "mozilla/Unused.h"
|
||||
|
||||
using GlobalInitializerFn = void(__cdecl*)(void);
|
||||
|
||||
// Allocation of static initialization section for the freestanding library
|
||||
#pragma section(".freestd$a", read)
|
||||
__declspec(allocate(".freestd$a")) static const GlobalInitializerFn
|
||||
FreeStdStart = reinterpret_cast<GlobalInitializerFn>(0);
|
||||
|
||||
#pragma section(".freestd$z", read)
|
||||
__declspec(allocate(".freestd$z")) static const GlobalInitializerFn FreeStdEnd =
|
||||
reinterpret_cast<GlobalInitializerFn>(0);
|
||||
|
||||
namespace mozilla {
|
||||
namespace freestanding {
|
||||
|
||||
static RTL_RUN_ONCE gRunOnce = RTL_RUN_ONCE_INIT;
|
||||
|
||||
// The contract for this callback is identical to the InitOnceCallback from
|
||||
// Win32 land; we're just using ntdll-layer types instead.
|
||||
static ULONG NTAPI DoOneTimeInit(PRTL_RUN_ONCE aRunOnce, PVOID aParameter,
|
||||
PVOID* aContext) {
|
||||
// Invoke every static initializer in the .freestd section
|
||||
const GlobalInitializerFn* cur = &FreeStdStart + 1;
|
||||
while (cur < &FreeStdEnd) {
|
||||
if (*cur) {
|
||||
(*cur)();
|
||||
}
|
||||
|
||||
++cur;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* This observer is only used until the mozglue observer connects itself.
|
||||
* All we do here is accumulate the module loads into a vector.
|
||||
* As soon as mozglue connects, we call |Forward| on mozglue's LoaderObserver
|
||||
* to pass our vector on for further processing. This object then becomes
|
||||
* defunct.
|
||||
*/
|
||||
class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS DefaultLoaderObserver final
|
||||
: public nt::LoaderObserver {
|
||||
public:
|
||||
constexpr DefaultLoaderObserver() : mModuleLoads(nullptr) {}
|
||||
|
||||
void OnBeginDllLoad(void** aContext,
|
||||
PCUNICODE_STRING aRequestedDllName) final {}
|
||||
bool SubstituteForLSP(PCUNICODE_STRING aLSPLeafName,
|
||||
PHANDLE aOutHandle) final {
|
||||
return false;
|
||||
}
|
||||
void OnEndDllLoad(void* aContext, NTSTATUS aNtStatus,
|
||||
ModuleLoadInfo&& aModuleLoadInfo) final;
|
||||
void Forward(nt::LoaderObserver* aNext) final;
|
||||
void OnForward(ModuleLoadInfoVec&& aInfo) final {
|
||||
MOZ_ASSERT_UNREACHABLE("Not valid in freestanding::DefaultLoaderObserver");
|
||||
}
|
||||
|
||||
private:
|
||||
mozilla::nt::SRWLock mLock;
|
||||
ModuleLoadInfoVec* mModuleLoads;
|
||||
};
|
||||
|
||||
class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS LoaderPrivateAPIImp final
|
||||
: public LoaderPrivateAPI {
|
||||
public:
|
||||
// LoaderAPI
|
||||
ModuleLoadInfo ConstructAndNotifyBeginDllLoad(
|
||||
void** aContext, PCUNICODE_STRING aRequestedDllName) final;
|
||||
bool SubstituteForLSP(PCUNICODE_STRING aLSPLeafName,
|
||||
PHANDLE aOutHandle) final;
|
||||
void NotifyEndDllLoad(void* aContext, NTSTATUS aLoadNtStatus,
|
||||
ModuleLoadInfo&& aModuleLoadInfo) final;
|
||||
nt::AllocatedUnicodeString GetSectionName(void* aSectionAddr) final;
|
||||
|
||||
// LoaderPrivateAPI
|
||||
void NotifyBeginDllLoad(void** aContext,
|
||||
PCUNICODE_STRING aRequestedDllName) final;
|
||||
void NotifyBeginDllLoad(ModuleLoadInfo& aModuleLoadInfo, void** aContext,
|
||||
PCUNICODE_STRING aRequestedDllName) final;
|
||||
void SetObserver(nt::LoaderObserver* aNewObserver) final;
|
||||
bool IsDefaultObserver() const final;
|
||||
};
|
||||
|
||||
static void Init() {
|
||||
DebugOnly<NTSTATUS> ntStatus =
|
||||
::RtlRunOnceExecuteOnce(&gRunOnce, &DoOneTimeInit, nullptr, nullptr);
|
||||
MOZ_ASSERT(NT_SUCCESS(ntStatus));
|
||||
}
|
||||
|
||||
} // namespace freestanding
|
||||
} // namespace mozilla
|
||||
|
||||
static mozilla::freestanding::DefaultLoaderObserver gDefaultObserver;
|
||||
static mozilla::freestanding::LoaderPrivateAPIImp gPrivateAPI;
|
||||
|
||||
static mozilla::nt::SRWLock gLoaderObserverLock;
|
||||
static mozilla::nt::LoaderObserver* gLoaderObserver = &gDefaultObserver;
|
||||
|
||||
namespace mozilla {
|
||||
namespace freestanding {
|
||||
|
||||
LoaderPrivateAPI& gLoaderPrivateAPI = gPrivateAPI;
|
||||
|
||||
void DefaultLoaderObserver::OnEndDllLoad(void* aContext, NTSTATUS aNtStatus,
|
||||
ModuleLoadInfo&& aModuleLoadInfo) {
|
||||
// If the DLL load failed, or if the DLL was loaded by a previous request
|
||||
// and thus was not mapped by this request, we do not save the ModuleLoadInfo.
|
||||
if (!NT_SUCCESS(aNtStatus) || !aModuleLoadInfo.WasMapped()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nt::AutoExclusiveLock lock(mLock);
|
||||
if (!mModuleLoads) {
|
||||
mModuleLoads = RtlNew<ModuleLoadInfoVec>();
|
||||
if (!mModuleLoads) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Unused << mModuleLoads->emplaceBack(
|
||||
std::forward<ModuleLoadInfo>(aModuleLoadInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass mModuleLoads's data off to |aNext| for further processing.
|
||||
*/
|
||||
void DefaultLoaderObserver::Forward(nt::LoaderObserver* aNext) {
|
||||
MOZ_ASSERT(aNext);
|
||||
if (!aNext) {
|
||||
return;
|
||||
}
|
||||
|
||||
ModuleLoadInfoVec* moduleLoads = nullptr;
|
||||
|
||||
{ // Scope for lock
|
||||
nt::AutoExclusiveLock lock(mLock);
|
||||
moduleLoads = mModuleLoads;
|
||||
mModuleLoads = nullptr;
|
||||
}
|
||||
|
||||
if (!moduleLoads) {
|
||||
return;
|
||||
}
|
||||
|
||||
aNext->OnForward(std::move(*moduleLoads));
|
||||
RtlDelete(moduleLoads);
|
||||
}
|
||||
|
||||
ModuleLoadInfo LoaderPrivateAPIImp::ConstructAndNotifyBeginDllLoad(
|
||||
void** aContext, PCUNICODE_STRING aRequestedDllName) {
|
||||
ModuleLoadInfo loadInfo(aRequestedDllName);
|
||||
|
||||
NotifyBeginDllLoad(loadInfo, aContext, aRequestedDllName);
|
||||
|
||||
return loadInfo;
|
||||
}
|
||||
|
||||
bool LoaderPrivateAPIImp::SubstituteForLSP(PCUNICODE_STRING aLSPLeafName,
|
||||
PHANDLE aOutHandle) {
|
||||
nt::AutoSharedLock lock(gLoaderObserverLock);
|
||||
return gLoaderObserver->SubstituteForLSP(aLSPLeafName, aOutHandle);
|
||||
}
|
||||
|
||||
void LoaderPrivateAPIImp::NotifyEndDllLoad(void* aContext,
|
||||
NTSTATUS aLoadNtStatus,
|
||||
ModuleLoadInfo&& aModuleLoadInfo) {
|
||||
aModuleLoadInfo.SetEndLoadTimeStamp();
|
||||
|
||||
if (NT_SUCCESS(aLoadNtStatus)) {
|
||||
aModuleLoadInfo.CaptureBacktrace();
|
||||
}
|
||||
|
||||
nt::AutoSharedLock lock(gLoaderObserverLock);
|
||||
|
||||
// We need to notify the observer that the DLL load has ended even when
|
||||
// |aLoadNtStatus| indicates a failure. This is to ensure that any resources
|
||||
// acquired by the observer during OnBeginDllLoad are cleaned up.
|
||||
gLoaderObserver->OnEndDllLoad(aContext, aLoadNtStatus,
|
||||
std::move(aModuleLoadInfo));
|
||||
}
|
||||
|
||||
nt::AllocatedUnicodeString LoaderPrivateAPIImp::GetSectionName(
|
||||
void* aSectionAddr) {
|
||||
const HANDLE kCurrentProcess = reinterpret_cast<HANDLE>(-1);
|
||||
|
||||
nt::MemorySectionNameBuf buf;
|
||||
NTSTATUS ntStatus =
|
||||
::NtQueryVirtualMemory(kCurrentProcess, aSectionAddr, MemorySectionName,
|
||||
&buf, sizeof(buf), nullptr);
|
||||
if (!NT_SUCCESS(ntStatus)) {
|
||||
return nt::AllocatedUnicodeString();
|
||||
}
|
||||
|
||||
return nt::AllocatedUnicodeString(&buf.mSectionFileName);
|
||||
}
|
||||
|
||||
void LoaderPrivateAPIImp::NotifyBeginDllLoad(
|
||||
void** aContext, PCUNICODE_STRING aRequestedDllName) {
|
||||
nt::AutoSharedLock lock(gLoaderObserverLock);
|
||||
gLoaderObserver->OnBeginDllLoad(aContext, aRequestedDllName);
|
||||
}
|
||||
|
||||
void LoaderPrivateAPIImp::NotifyBeginDllLoad(
|
||||
ModuleLoadInfo& aModuleLoadInfo, void** aContext,
|
||||
PCUNICODE_STRING aRequestedDllName) {
|
||||
NotifyBeginDllLoad(aContext, aRequestedDllName);
|
||||
aModuleLoadInfo.SetBeginLoadTimeStamp();
|
||||
}
|
||||
|
||||
void LoaderPrivateAPIImp::SetObserver(nt::LoaderObserver* aNewObserver) {
|
||||
nt::LoaderObserver* prevLoaderObserver = nullptr;
|
||||
|
||||
nt::AutoExclusiveLock lock(gLoaderObserverLock);
|
||||
|
||||
MOZ_ASSERT(aNewObserver);
|
||||
if (!aNewObserver) {
|
||||
// This is unlikely, but we always want a valid observer, so use the
|
||||
// gDefaultObserver if necessary.
|
||||
gLoaderObserver = &gDefaultObserver;
|
||||
return;
|
||||
}
|
||||
|
||||
prevLoaderObserver = gLoaderObserver;
|
||||
gLoaderObserver = aNewObserver;
|
||||
|
||||
MOZ_ASSERT(prevLoaderObserver);
|
||||
if (!prevLoaderObserver) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Now that we have a new observer, the previous observer must forward its
|
||||
// data on to the new observer for processing.
|
||||
prevLoaderObserver->Forward(aNewObserver);
|
||||
}
|
||||
|
||||
bool LoaderPrivateAPIImp::IsDefaultObserver() const {
|
||||
nt::AutoSharedLock lock(gLoaderObserverLock);
|
||||
return gLoaderObserver == &gDefaultObserver;
|
||||
}
|
||||
|
||||
void EnsureInitialized() { Init(); }
|
||||
|
||||
} // namespace freestanding
|
||||
} // namespace mozilla
|
|
@ -1,56 +0,0 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_freestanding_LoaderPrivateAPI_h
|
||||
#define mozilla_freestanding_LoaderPrivateAPI_h
|
||||
|
||||
#include "LoaderAPIInterfaces.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace freestanding {
|
||||
|
||||
/**
|
||||
* This part of the API is available only to the launcher process.
|
||||
*/
|
||||
class NS_NO_VTABLE LoaderPrivateAPI : public nt::LoaderAPI {
|
||||
public:
|
||||
/**
|
||||
* Notify the nt::LoaderObserver that a module load is beginning
|
||||
*/
|
||||
virtual void NotifyBeginDllLoad(void** aContext,
|
||||
PCUNICODE_STRING aRequestedDllName) = 0;
|
||||
/**
|
||||
* Notify the nt::LoaderObserver that a module load is beginning and set the
|
||||
* begin load timestamp on |aModuleLoadInfo|.
|
||||
*/
|
||||
virtual void NotifyBeginDllLoad(ModuleLoadInfo& aModuleLoadInfo,
|
||||
void** aContext,
|
||||
PCUNICODE_STRING aRequestedDllName) = 0;
|
||||
|
||||
/**
|
||||
* Set a new nt::LoaderObserver to be used by the launcher process. NB: This
|
||||
* should only happen while the current process is still single-threaded!
|
||||
*/
|
||||
virtual void SetObserver(nt::LoaderObserver* aNewObserver) = 0;
|
||||
|
||||
/**
|
||||
* Returns true if the current nt::LoaderObserver is the launcher process's
|
||||
* built-in observer.
|
||||
*/
|
||||
virtual bool IsDefaultObserver() const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Ensures that any statics in the freestanding library are initialized.
|
||||
*/
|
||||
void EnsureInitialized();
|
||||
|
||||
extern LoaderPrivateAPI& gLoaderPrivateAPI;
|
||||
|
||||
} // namespace freestanding
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_freestanding_LoaderPrivateAPI_h
|
|
@ -1,129 +0,0 @@
|
|||
/* -*- 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 "ModuleLoadFrame.h"
|
||||
|
||||
#include "LoaderPrivateAPI.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace freestanding {
|
||||
|
||||
ModuleLoadFrame::ModuleLoadFrame(PCUNICODE_STRING aRequestedDllName)
|
||||
: mPrev(sTopFrame.get()),
|
||||
mContext(nullptr),
|
||||
mLSPSubstitutionRequired(false),
|
||||
mLoadNtStatus(STATUS_UNSUCCESSFUL),
|
||||
mLoadInfo(aRequestedDllName) {
|
||||
EnsureInitialized();
|
||||
sTopFrame.set(this);
|
||||
|
||||
gLoaderPrivateAPI.NotifyBeginDllLoad(mLoadInfo, &mContext, aRequestedDllName);
|
||||
}
|
||||
|
||||
ModuleLoadFrame::ModuleLoadFrame(nt::AllocatedUnicodeString&& aSectionName,
|
||||
const void* aMapBaseAddr, NTSTATUS aNtStatus)
|
||||
: mPrev(sTopFrame.get()),
|
||||
mContext(nullptr),
|
||||
mLSPSubstitutionRequired(false),
|
||||
mLoadNtStatus(aNtStatus),
|
||||
mLoadInfo(std::move(aSectionName), aMapBaseAddr) {
|
||||
sTopFrame.set(this);
|
||||
|
||||
gLoaderPrivateAPI.NotifyBeginDllLoad(&mContext, mLoadInfo.mSectionName);
|
||||
}
|
||||
|
||||
ModuleLoadFrame::~ModuleLoadFrame() {
|
||||
gLoaderPrivateAPI.NotifyEndDllLoad(mContext, mLoadNtStatus,
|
||||
std::move(mLoadInfo));
|
||||
sTopFrame.set(mPrev);
|
||||
}
|
||||
|
||||
/* static */
|
||||
void ModuleLoadFrame::NotifyLSPSubstitutionRequired(
|
||||
PCUNICODE_STRING aLeafName) {
|
||||
ModuleLoadFrame* topFrame = sTopFrame.get();
|
||||
if (!topFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
topFrame->SetLSPSubstitutionRequired(aLeafName);
|
||||
}
|
||||
|
||||
void ModuleLoadFrame::SetLSPSubstitutionRequired(PCUNICODE_STRING aLeafName) {
|
||||
MOZ_ASSERT(!mLoadInfo.mBaseAddr);
|
||||
if (mLoadInfo.mBaseAddr) {
|
||||
// If mBaseAddr is not null then |this| has already seen a module load. This
|
||||
// should not be the case for a LSP substitution, so we bail.
|
||||
return;
|
||||
}
|
||||
|
||||
// Save aLeafName, as it will be used by SetLoadStatus when invoking
|
||||
// SubstituteForLSP
|
||||
mLoadInfo.mRequestedDllName = aLeafName;
|
||||
mLSPSubstitutionRequired = true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
void ModuleLoadFrame::NotifySectionMap(
|
||||
nt::AllocatedUnicodeString&& aSectionName, const void* aMapBaseAddr,
|
||||
NTSTATUS aMapNtStatus) {
|
||||
ModuleLoadFrame* topFrame = sTopFrame.get();
|
||||
if (!topFrame) {
|
||||
// The only time that this data is useful is during initial mapping of
|
||||
// the executable's dependent DLLs. If mozglue is present then
|
||||
// IsDefaultObserver will return false, indicating that we are beyond
|
||||
// initial process startup.
|
||||
if (gLoaderPrivateAPI.IsDefaultObserver()) {
|
||||
OnBareSectionMap(std::move(aSectionName), aMapBaseAddr, aMapNtStatus);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
topFrame->OnSectionMap(std::move(aSectionName), aMapBaseAddr, aMapNtStatus);
|
||||
}
|
||||
|
||||
void ModuleLoadFrame::OnSectionMap(nt::AllocatedUnicodeString&& aSectionName,
|
||||
const void* aMapBaseAddr,
|
||||
NTSTATUS aMapNtStatus) {
|
||||
if (mLoadInfo.mBaseAddr) {
|
||||
// If mBaseAddr is not null then |this| has already seen a module load. This
|
||||
// means that we are witnessing a bare section map.
|
||||
OnBareSectionMap(std::move(aSectionName), aMapBaseAddr, aMapNtStatus);
|
||||
return;
|
||||
}
|
||||
|
||||
mLoadInfo.mSectionName = std::move(aSectionName);
|
||||
mLoadInfo.mBaseAddr = aMapBaseAddr;
|
||||
}
|
||||
|
||||
/* static */
|
||||
void ModuleLoadFrame::OnBareSectionMap(
|
||||
nt::AllocatedUnicodeString&& aSectionName, const void* aMapBaseAddr,
|
||||
NTSTATUS aMapNtStatus) {
|
||||
// We call the special constructor variant that is used for bare mappings.
|
||||
ModuleLoadFrame frame(std::move(aSectionName), aMapBaseAddr, aMapNtStatus);
|
||||
}
|
||||
|
||||
NTSTATUS ModuleLoadFrame::SetLoadStatus(NTSTATUS aNtStatus,
|
||||
PHANDLE aOutHandle) {
|
||||
mLoadNtStatus = aNtStatus;
|
||||
|
||||
if (!mLSPSubstitutionRequired) {
|
||||
return aNtStatus;
|
||||
}
|
||||
|
||||
if (!gLoaderPrivateAPI.SubstituteForLSP(mLoadInfo.mRequestedDllName,
|
||||
aOutHandle)) {
|
||||
return aNtStatus;
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
MOZ_THREAD_LOCAL(ModuleLoadFrame*) ModuleLoadFrame::sTopFrame;
|
||||
|
||||
} // namespace freestanding
|
||||
} // namespace mozilla
|
|
@ -1,94 +0,0 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_freestanding_ModuleLoadFrame_h
|
||||
#define mozilla_freestanding_ModuleLoadFrame_h
|
||||
|
||||
#include "mozilla/LoaderAPIInterfaces.h"
|
||||
#include "mozilla/NativeNt.h"
|
||||
#include "mozilla/ThreadLocal.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace freestanding {
|
||||
|
||||
// We cannot fall back to the Tls* APIs because kernel32 might not have been
|
||||
// loaded yet.
|
||||
#if defined(__MINGW32__) && !defined(HAVE_THREAD_TLS_KEYWORD)
|
||||
# error "This code requires the compiler to have native TLS support"
|
||||
#endif // defined(__MINGW32__) && !defined(HAVE_THREAD_TLS_KEYWORD)
|
||||
|
||||
/**
|
||||
* This class holds information about a DLL load at a particular frame in the
|
||||
* current thread's stack. Each instance adds itself to a thread-local linked
|
||||
* list of ModuleLoadFrames, enabling us to query information about the
|
||||
* previous module load on the stack.
|
||||
*/
|
||||
class MOZ_RAII ModuleLoadFrame final {
|
||||
public:
|
||||
/**
|
||||
* This constructor is for use by the LdrLoadDll hook.
|
||||
*/
|
||||
explicit ModuleLoadFrame(PCUNICODE_STRING aRequestedDllName);
|
||||
~ModuleLoadFrame();
|
||||
|
||||
static void NotifyLSPSubstitutionRequired(PCUNICODE_STRING aLeafName);
|
||||
|
||||
/**
|
||||
* This static method is called by the NtMapViewOfSection hook.
|
||||
*/
|
||||
static void NotifySectionMap(nt::AllocatedUnicodeString&& aSectionName,
|
||||
const void* aMapBaseAddr, NTSTATUS aMapNtStatus);
|
||||
|
||||
/**
|
||||
* Called by the LdrLoadDll hook to indicate the status of the load and for
|
||||
* us to provide a substitute output handle if necessary.
|
||||
*/
|
||||
NTSTATUS SetLoadStatus(NTSTATUS aNtStatus, PHANDLE aOutHandle);
|
||||
|
||||
ModuleLoadFrame(const ModuleLoadFrame&) = delete;
|
||||
ModuleLoadFrame(ModuleLoadFrame&&) = delete;
|
||||
ModuleLoadFrame& operator=(const ModuleLoadFrame&) = delete;
|
||||
ModuleLoadFrame& operator=(ModuleLoadFrame&&) = delete;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Called by OnBareSectionMap to construct a frame for a bare load.
|
||||
*/
|
||||
ModuleLoadFrame(nt::AllocatedUnicodeString&& aSectionName,
|
||||
const void* aMapBaseAddr, NTSTATUS aNtStatus);
|
||||
|
||||
void SetLSPSubstitutionRequired(PCUNICODE_STRING aLeafName);
|
||||
void OnSectionMap(nt::AllocatedUnicodeString&& aSectionName,
|
||||
const void* aMapBaseAddr, NTSTATUS aMapNtStatus);
|
||||
|
||||
/**
|
||||
* A "bare" section mapping is one that was mapped without the code passing
|
||||
* through a call to ntdll!LdrLoadDll. This method is invoked when we detect
|
||||
* that condition.
|
||||
*/
|
||||
static void OnBareSectionMap(nt::AllocatedUnicodeString&& aSectionName,
|
||||
const void* aMapBaseAddr, NTSTATUS aMapNtStatus);
|
||||
|
||||
private:
|
||||
// Link to the previous frame
|
||||
ModuleLoadFrame* mPrev;
|
||||
// Pointer to context managed by the nt::LoaderObserver implementation
|
||||
void* mContext;
|
||||
// Set to |true| when we need to block a WinSock LSP
|
||||
bool mLSPSubstitutionRequired;
|
||||
// NTSTATUS code from the |LdrLoadDll| call
|
||||
NTSTATUS mLoadNtStatus;
|
||||
// Telemetry information that will be forwarded to the nt::LoaderObserver
|
||||
ModuleLoadInfo mLoadInfo;
|
||||
|
||||
// Head of the linked list
|
||||
static MOZ_THREAD_LOCAL(ModuleLoadFrame*) sTopFrame;
|
||||
};
|
||||
|
||||
} // namespace freestanding
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_freestanding_ModuleLoadFrame_h
|
|
@ -1,152 +0,0 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_ModuleLoadInfo_h
|
||||
#define mozilla_ModuleLoadInfo_h
|
||||
|
||||
#include "mozilla/NativeNt.h"
|
||||
#include "mozilla/Vector.h"
|
||||
#include "mozilla/Unused.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
struct ModuleLoadInfo final {
|
||||
// We do not provide these methods inside Gecko proper.
|
||||
#if !defined(MOZILLA_INTERNAL_API)
|
||||
|
||||
/**
|
||||
* This constructor is for use by the LdrLoadDll hook.
|
||||
*/
|
||||
explicit ModuleLoadInfo(PCUNICODE_STRING aRequestedDllName)
|
||||
: mLoadTimeInfo(),
|
||||
mThreadId(nt::RtlGetCurrentThreadId()),
|
||||
mRequestedDllName(aRequestedDllName),
|
||||
mBaseAddr(nullptr) {
|
||||
# if defined(IMPL_MFBT)
|
||||
::QueryPerformanceCounter(&mBeginTimestamp);
|
||||
# else
|
||||
::RtlQueryPerformanceCounter(&mBeginTimestamp);
|
||||
# endif // defined(IMPL_MFBT)
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructor is used by the NtMapViewOfSection hook IF AND ONLY IF
|
||||
* the LdrLoadDll hook did not already construct a ModuleLoadInfo for the
|
||||
* current DLL load. This may occur while the loader is loading dependencies
|
||||
* of another library.
|
||||
*/
|
||||
ModuleLoadInfo(nt::AllocatedUnicodeString&& aSectionName,
|
||||
const void* aBaseAddr)
|
||||
: mLoadTimeInfo(),
|
||||
mThreadId(nt::RtlGetCurrentThreadId()),
|
||||
mSectionName(std::move(aSectionName)),
|
||||
mBaseAddr(aBaseAddr) {
|
||||
# if defined(IMPL_MFBT)
|
||||
::QueryPerformanceCounter(&mBeginTimestamp);
|
||||
# else
|
||||
::RtlQueryPerformanceCounter(&mBeginTimestamp);
|
||||
# endif // defined(IMPL_MFBT)
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the time that LdrLoadDll began loading this library.
|
||||
*/
|
||||
void SetBeginLoadTimeStamp() {
|
||||
# if defined(IMPL_MFBT)
|
||||
::QueryPerformanceCounter(&mLoadTimeInfo);
|
||||
# else
|
||||
::RtlQueryPerformanceCounter(&mLoadTimeInfo);
|
||||
# endif // defined(IMPL_MFBT)
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the time that LdrLoadDll finished loading this library.
|
||||
*/
|
||||
void SetEndLoadTimeStamp() {
|
||||
LARGE_INTEGER endTimeStamp;
|
||||
# if defined(IMPL_MFBT)
|
||||
::QueryPerformanceCounter(&endTimeStamp);
|
||||
# else
|
||||
::RtlQueryPerformanceCounter(&endTimeStamp);
|
||||
# endif // defined(IMPL_MFBT)
|
||||
|
||||
LONGLONG& timeInfo = mLoadTimeInfo.QuadPart;
|
||||
if (!timeInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
timeInfo = endTimeStamp.QuadPart - timeInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current thread's call stack.
|
||||
*/
|
||||
void CaptureBacktrace() {
|
||||
const DWORD kMaxBacktraceSize = 512;
|
||||
|
||||
if (!mBacktrace.resize(kMaxBacktraceSize)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't use a Win32 variant here because Win32's CaptureStackBackTrace
|
||||
// is just a macro that resolve to this function anyway.
|
||||
WORD numCaptured = ::RtlCaptureStackBackTrace(2, kMaxBacktraceSize,
|
||||
mBacktrace.begin(), nullptr);
|
||||
Unused << mBacktrace.resize(numCaptured);
|
||||
// These backtraces might stick around for a while, so let's trim any
|
||||
// excess memory.
|
||||
mBacktrace.podResizeToFit();
|
||||
}
|
||||
|
||||
#endif // !defined(MOZILLA_INTERNAL_API)
|
||||
|
||||
ModuleLoadInfo(ModuleLoadInfo&&) = default;
|
||||
ModuleLoadInfo& operator=(ModuleLoadInfo&&) = default;
|
||||
|
||||
ModuleLoadInfo() = delete;
|
||||
ModuleLoadInfo(const ModuleLoadInfo&) = delete;
|
||||
ModuleLoadInfo& operator=(const ModuleLoadInfo&) = delete;
|
||||
|
||||
/**
|
||||
* A "bare" module load is one that was mapped without the code passing
|
||||
* through a call to ntdll!LdrLoadDll.
|
||||
*/
|
||||
bool IsBare() const {
|
||||
// SetBeginLoadTimeStamp() and SetEndLoadTimeStamp() are only called by the
|
||||
// LdrLoadDll hook, so when mLoadTimeInfo == 0, we know that we are bare.
|
||||
return !mLoadTimeInfo.QuadPart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true for DLL loads where LdrLoadDll was called but
|
||||
* NtMapViewOfSection was not. This will happen for DLL requests where the DLL
|
||||
* was already mapped into memory by a previous request.
|
||||
*/
|
||||
bool WasMapped() const { return !mSectionName.IsEmpty(); }
|
||||
|
||||
// Timestamp for the creation of this event
|
||||
LARGE_INTEGER mBeginTimestamp;
|
||||
// Duration of the LdrLoadDll call
|
||||
LARGE_INTEGER mLoadTimeInfo;
|
||||
// Thread ID of this DLL load
|
||||
DWORD mThreadId;
|
||||
// The name requested of LdrLoadDll by its caller
|
||||
nt::AllocatedUnicodeString mRequestedDllName;
|
||||
// The name of the DLL that backs section that was mapped by the loader. This
|
||||
// string is the effective name of the DLL that was resolved by the loader's
|
||||
// path search algorithm.
|
||||
nt::AllocatedUnicodeString mSectionName;
|
||||
// The base address of the module's mapped section
|
||||
const void* mBaseAddr;
|
||||
// If the module was successfully loaded, stack trace of the DLL load request
|
||||
Vector<PVOID, 0, nt::RtlAllocPolicy> mBacktrace;
|
||||
};
|
||||
|
||||
using ModuleLoadInfoVec = Vector<ModuleLoadInfo, 0, nt::RtlAllocPolicy>;
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_ModuleLoadInfo_h
|
|
@ -1,30 +0,0 @@
|
|||
# -*- 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/.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
|
||||
def main(output_fd, def_file, llvm_dlltool, *llvm_dlltool_args):
|
||||
# llvm-dlltool can't output to stdout, so we create a temp file, use that
|
||||
# to write out the lib, and then copy it over to output_fd
|
||||
(tmp_fd, tmp_output) = tempfile.mkstemp()
|
||||
os.close(tmp_fd)
|
||||
|
||||
try:
|
||||
cmd = [llvm_dlltool]
|
||||
cmd.extend(llvm_dlltool_args)
|
||||
cmd += ['-d', def_file, '-l', tmp_output]
|
||||
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
with open(tmp_output, 'rb') as tmplib:
|
||||
output_fd.write(tmplib.read())
|
||||
finally:
|
||||
os.remove(tmp_output)
|
|
@ -1,66 +0,0 @@
|
|||
# -*- 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/.
|
||||
|
||||
Library('winlauncher-freestanding')
|
||||
|
||||
FORCE_STATIC_LIB = True
|
||||
|
||||
# Our patched NtMapViewOfSection can be called before the process's import
|
||||
# table is populated. Don't let the compiler insert any instrumentation
|
||||
# that might call an import.
|
||||
NO_PGO = True
|
||||
|
||||
EXPORTS.mozilla += [
|
||||
'LoaderAPIInterfaces.h',
|
||||
'ModuleLoadInfo.h',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'DllBlocklist.cpp',
|
||||
'LoaderPrivateAPI.cpp',
|
||||
'ModuleLoadFrame.cpp',
|
||||
]
|
||||
|
||||
# This library must be compiled in a freestanding environment, as its code must
|
||||
# not assume that it has access to any runtime libraries.
|
||||
if CONFIG['CC_TYPE'] == 'clang-cl':
|
||||
CXXFLAGS += ['-Xclang']
|
||||
|
||||
CXXFLAGS += [
|
||||
'-ffreestanding',
|
||||
]
|
||||
|
||||
# Forcibly include Freestanding.h into all source files in this library.
|
||||
if CONFIG['CC_TYPE'] == 'clang-cl':
|
||||
CXXFLAGS += ['-FI']
|
||||
else:
|
||||
CXXFLAGS += ['-include']
|
||||
|
||||
CXXFLAGS += [ SRCDIR + '/Freestanding.h' ]
|
||||
|
||||
OS_LIBS += [
|
||||
'ntdll',
|
||||
'ntdll_freestanding',
|
||||
]
|
||||
|
||||
if CONFIG['COMPILE_ENVIRONMENT']:
|
||||
ntdll_freestanding_lib = '%sntdll_freestanding.%s' % (CONFIG['LIB_PREFIX'],
|
||||
CONFIG['LIB_SUFFIX'])
|
||||
|
||||
GENERATED_FILES += [
|
||||
ntdll_freestanding_lib,
|
||||
]
|
||||
|
||||
ntdll_freestanding_gen = GENERATED_FILES[ntdll_freestanding_lib]
|
||||
ntdll_freestanding_gen.script = 'gen_ntdll_freestanding_lib.py'
|
||||
ntdll_freestanding_gen.inputs = ['ntdll_freestanding.def']
|
||||
ntdll_freestanding_gen.flags = [CONFIG['LLVM_DLLTOOL']] + \
|
||||
CONFIG['LLVM_DLLTOOL_FLAGS']
|
||||
|
||||
DisableStlWrapping()
|
||||
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('Firefox', 'Launcher Process')
|
|
@ -1,25 +0,0 @@
|
|||
; 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 ntdll
|
||||
|
||||
; When we compile with -freestanding, the compiler still requires implementation
|
||||
; of the four functions listed below.
|
||||
;
|
||||
; We could implement our own naive versions of these functions, but that
|
||||
; solution is less than ideal since the implementations must be extern and are
|
||||
; thus picked up by the entire firefox.exe binary. This denies the rest of
|
||||
; firefox.exe the benefit of optimized implementations. On Windows the
|
||||
; sandbox is linked into firefox.exe, so we cannot just shrug and
|
||||
; assume that a naive implementation will not have any effect on anything.
|
||||
;
|
||||
; There are, however, optimized implementations of these functions that are
|
||||
; exported by ntdll.dll. OTOH, they are not included in the ntdll.lib
|
||||
; import library. This .def file is used to build an import library that "fills
|
||||
; in the blanks" and allows us to link into the ntdll implementations.
|
||||
EXPORTS
|
||||
memcmp
|
||||
memcpy
|
||||
memmove
|
||||
memset
|
|
@ -8,35 +8,51 @@ Library('winlauncher')
|
|||
|
||||
FORCE_STATIC_LIB = True
|
||||
|
||||
EXPORTS.mozilla += [
|
||||
'NtLoaderAPI.h',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'/ipc/mscom/ProcessRuntime.cpp',
|
||||
'/widget/windows/WindowsConsole.cpp',
|
||||
'DllBlocklistInit.cpp',
|
||||
'ErrorHandler.cpp',
|
||||
'LauncherProcessWin.cpp',
|
||||
'LaunchUnelevated.cpp',
|
||||
'NtLoaderAPI.cpp',
|
||||
]
|
||||
|
||||
SOURCES += [
|
||||
'DllBlocklistWin.cpp',
|
||||
]
|
||||
# Our patched NtMapViewOfSection can be called before the process's import
|
||||
# table is populated. Don't let the compiler insert any instrumentation
|
||||
# that might call an import.
|
||||
SOURCES['DllBlocklistWin.cpp'].no_pgo = True
|
||||
|
||||
# DllBlocklistWin.cpp should be compiled in a freestanding environment, as
|
||||
# our patched NtMapViewOfSection must not assume that it has access to any
|
||||
# runtime libraries.
|
||||
if CONFIG['CC_TYPE'] == 'clang-cl':
|
||||
SOURCES['DllBlocklistWin.cpp'].flags += ['-Xclang', '-ffreestanding']
|
||||
else:
|
||||
SOURCES['DllBlocklistWin.cpp'].flags += ['-ffreestanding']
|
||||
|
||||
# Disable -ftrivial-auto-var-init=pattern for DllBlocklistWin.cpp because
|
||||
# the file's interceptions happen so early in the main process that the
|
||||
# loader hasn't yet resolved the import of memset (used by
|
||||
# -ftrivial-auto-var-init) from vcruntime140.dll.
|
||||
if CONFIG['CC_TYPE'] == 'clang-cl':
|
||||
SOURCES['DllBlocklistWin.cpp'].flags += [
|
||||
'-Xclang', '-ftrivial-auto-var-init=uninitialized',
|
||||
]
|
||||
else:
|
||||
SOURCES['DllBlocklistWin.cpp'].flags += [
|
||||
'-ftrivial-auto-var-init=uninitialized',
|
||||
]
|
||||
|
||||
OS_LIBS += [
|
||||
'ntdll',
|
||||
'oleaut32',
|
||||
'ole32',
|
||||
'rpcrt4',
|
||||
'version',
|
||||
]
|
||||
|
||||
DIRS += [
|
||||
'freestanding',
|
||||
]
|
||||
|
||||
USE_LIBS += [
|
||||
'winlauncher-freestanding',
|
||||
]
|
||||
|
||||
TEST_DIRS += [
|
||||
'test',
|
||||
]
|
||||
|
|
|
@ -7,9 +7,7 @@
|
|||
#include "../contentproc/plugin-container.cpp"
|
||||
|
||||
#include "mozilla/Bootstrap.h"
|
||||
#if defined(XP_WIN)
|
||||
# include "mozilla/WindowsDllBlocklist.h"
|
||||
#endif // defined(XP_WIN)
|
||||
#include "mozilla/WindowsDllBlocklist.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/mscom/ProcessRuntimeShared.h"
|
||||
|
||||
#include "mozilla/glue/WinUtils.h"
|
||||
#include "MozglueUtils.h"
|
||||
|
||||
// We allow multiple ProcessRuntime instances to exist simultaneously (even
|
||||
// on separate threads), but only one should be doing the process-wide
|
||||
|
|
|
@ -10,6 +10,10 @@ EXPORTS.mozilla.mscom += [
|
|||
'ProcessRuntimeShared.h',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/mozglue/build',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'ProcessRuntimeShared.cpp',
|
||||
]
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "mozilla/WindowsDllBlocklist.h"
|
||||
#include "mozilla/Bootstrap.h"
|
||||
|
||||
#include "nsXULAppAPI.h"
|
||||
|
@ -15,8 +16,6 @@
|
|||
# include "xpcshellMacUtils.h"
|
||||
#endif
|
||||
#ifdef XP_WIN
|
||||
# include "mozilla/WindowsDllBlocklist.h"
|
||||
|
||||
# include <windows.h>
|
||||
# include <shlobj.h>
|
||||
|
||||
|
|
|
@ -0,0 +1,310 @@
|
|||
/* -*- 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 "UntrustedDllsHandler.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/mozalloc.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "mozilla/ThreadLocal.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "nsWindowsHelpers.h" // For AutoCriticalSection
|
||||
|
||||
namespace mozilla {
|
||||
namespace glue {
|
||||
|
||||
// Copies a null-terminated string. Upon error, returns nullptr.
|
||||
static UniquePtr<wchar_t[]> CopyString(const UniquePtr<wchar_t[]>& aOther) {
|
||||
if (!aOther) {
|
||||
return nullptr;
|
||||
}
|
||||
size_t chars = wcslen(aOther.get());
|
||||
auto ret = MakeUnique<wchar_t[]>(chars + 1);
|
||||
if (wcsncpy_s(ret.get(), chars + 1, aOther.get(), chars)) {
|
||||
return nullptr;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Creates a UniquePtr<wchar_t[]> from a PCUNICODE_STRING string.
|
||||
// Upon error, returns nullptr.
|
||||
static UniquePtr<wchar_t[]> CopyString(PCUNICODE_STRING aOther) {
|
||||
if (!aOther || !aOther->Buffer) {
|
||||
return nullptr;
|
||||
}
|
||||
size_t chars = aOther->Length / sizeof(wchar_t);
|
||||
auto ret = MakeUnique<wchar_t[]>(chars + 1);
|
||||
if (wcsncpy_s(ret.get(), chars + 1, aOther->Buffer, chars)) {
|
||||
return nullptr;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Basic wrapper around ::GetModuleFileNameW.
|
||||
// Returns the full path of the loaded module specified by aModuleBase.
|
||||
// Upon error, returns nullptr.
|
||||
static UniquePtr<wchar_t[]> GetModuleFullPath(uintptr_t aModuleBase) {
|
||||
size_t allocated = MAX_PATH;
|
||||
auto ret = MakeUnique<wchar_t[]>(allocated);
|
||||
size_t len;
|
||||
while (true) {
|
||||
len = (size_t)::GetModuleFileNameW((HMODULE)aModuleBase, ret.get(),
|
||||
allocated);
|
||||
if (!len) {
|
||||
return nullptr;
|
||||
}
|
||||
if (len == allocated && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
|
||||
allocated *= 2;
|
||||
ret = MakeUnique<wchar_t[]>(allocated);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// The buffer may much bigger than needed. Return an efficiently-allocated
|
||||
// buffer.
|
||||
return CopyString(ret);
|
||||
}
|
||||
|
||||
// To track call depth and recursively-loaded modules, we must store this data
|
||||
// in thread local storage.
|
||||
class TLSData {
|
||||
public:
|
||||
Vector<ModuleLoadEvent::ModuleInfo, 0, InfallibleAllocPolicy> mModulesLoaded;
|
||||
int mCallDepth = 0;
|
||||
};
|
||||
|
||||
static MOZ_THREAD_LOCAL(TLSData*) sTlsData;
|
||||
|
||||
// This singleton class does the underlying work for UntrustedDllsHandler
|
||||
class UntrustedDllsHandlerImpl {
|
||||
// Refcounting gives us a way to synchronize call lifetime vs object lifetime.
|
||||
// We don't have access to NS_INLINE_DECL_THREADSAFE_REFCOUNTING from mozglue,
|
||||
// but it's easy to roll our own.
|
||||
Atomic<int32_t> mRefCnt;
|
||||
|
||||
// In order to prevent sInstance from being "woken back up" after it's been
|
||||
// cleared on shutdown, this will let us know if sInstance is empty because
|
||||
// it's not initialized yet, or because it's been cleared on shutdown.
|
||||
static Atomic<bool> sInstanceHasBeenSet;
|
||||
|
||||
// Singleton reference
|
||||
static StaticRefPtr<UntrustedDllsHandlerImpl> sInstance;
|
||||
|
||||
// Holds a list of module load events. This gets emptied upon calling
|
||||
// UntrustedDllsHandler::TakePendingEvents
|
||||
Vector<ModuleLoadEvent, 0, InfallibleAllocPolicy> mModuleLoadEvents;
|
||||
|
||||
// Holds a list of module full paths that we've already handled, so we can
|
||||
// skip duplicates.
|
||||
Vector<mozilla::UniquePtr<wchar_t[]>> mModuleHistory;
|
||||
|
||||
// This lock protects gModuleLoadEvents and gModuleHistory.
|
||||
//
|
||||
// You must only make trivial, loader-lock-friendly calls within the lock. We
|
||||
// cannot risk re-entering the loader at this point.
|
||||
CRITICAL_SECTION mDataLock;
|
||||
|
||||
UntrustedDllsHandlerImpl() { InitializeCriticalSection(&mDataLock); }
|
||||
|
||||
~UntrustedDllsHandlerImpl() {
|
||||
{ // Scope for lock
|
||||
// Ensure pending ops are complete.
|
||||
AutoCriticalSection lock(&mDataLock);
|
||||
}
|
||||
DeleteCriticalSection(&mDataLock);
|
||||
}
|
||||
|
||||
public:
|
||||
static RefPtr<UntrustedDllsHandlerImpl> GetInstance() {
|
||||
if (sInstanceHasBeenSet) {
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
sInstance = new UntrustedDllsHandlerImpl();
|
||||
sInstanceHasBeenSet = true;
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
static void Shutdown() { sInstance = nullptr; }
|
||||
|
||||
int32_t AddRef() { return ++mRefCnt; }
|
||||
|
||||
int32_t Release() {
|
||||
int32_t ret = --mRefCnt;
|
||||
if (!ret) {
|
||||
delete this;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Called after a successful module load at the top level. Now we are safe
|
||||
// to package up the event and save for later processing.
|
||||
void OnAfterTopLevelModuleLoad() {
|
||||
// Hold a reference to ensure we don't get deleted during this call.
|
||||
RefPtr<UntrustedDllsHandlerImpl> refHolder(this);
|
||||
if (!refHolder) {
|
||||
return;
|
||||
}
|
||||
|
||||
ModuleLoadEvent thisEvent;
|
||||
|
||||
TLSData* tlsData = sTlsData.get();
|
||||
if (!tlsData) {
|
||||
return;
|
||||
}
|
||||
|
||||
{ // Scope for lock
|
||||
// Lock around gModuleHistory to prune out modules we've handled before.
|
||||
// Only trivial calls allowed during lock (don't invoke the loader)
|
||||
AutoCriticalSection lock(&mDataLock);
|
||||
|
||||
// There will rarely be more than a couple items in
|
||||
// tlsData->mModulesLoaded, so this is efficient enough.
|
||||
for (auto& module : tlsData->mModulesLoaded) {
|
||||
if (module.mFullPath && wcslen(module.mFullPath.get())) {
|
||||
bool foundInHistory = false;
|
||||
for (auto& h : mModuleHistory) {
|
||||
if (!wcsicmp(h.get(), module.mFullPath.get())) {
|
||||
foundInHistory = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (foundInHistory) {
|
||||
continue;
|
||||
}
|
||||
Unused << mModuleHistory.append(CopyString(module.mFullPath));
|
||||
}
|
||||
Unused << thisEvent.mModules.emplaceBack(std::move(module));
|
||||
}
|
||||
}
|
||||
|
||||
tlsData->mModulesLoaded.clear();
|
||||
|
||||
if (thisEvent.mModules.empty()) {
|
||||
return; // All modules have been filtered out; nothing further to do.
|
||||
}
|
||||
|
||||
thisEvent.mThreadID = GetCurrentThreadId();
|
||||
|
||||
TimeStamp processCreation = TimeStamp::ProcessCreation();
|
||||
TimeDuration td = TimeStamp::Now() - processCreation;
|
||||
thisEvent.mProcessUptimeMS = (uint64_t)td.ToMilliseconds();
|
||||
|
||||
static const uint32_t kMaxFrames = 500;
|
||||
auto frames = MakeUnique<void*[]>(kMaxFrames);
|
||||
|
||||
// Setting FramesToSkip to 1 so the caller will see itself as the top frame.
|
||||
USHORT frameCount =
|
||||
CaptureStackBackTrace(1, kMaxFrames, frames.get(), nullptr);
|
||||
if (thisEvent.mStack.reserve(frameCount)) {
|
||||
for (size_t i = 0; i < frameCount; ++i) {
|
||||
Unused << thisEvent.mStack.append((uintptr_t)frames[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Lock in order to store the new event.
|
||||
// Only trivial calls allowed during lock (don't invoke the loader)
|
||||
AutoCriticalSection lock(&mDataLock);
|
||||
Unused << mModuleLoadEvents.emplaceBack(std::move(thisEvent));
|
||||
}
|
||||
|
||||
// Returns true if aOut is no longer empty.
|
||||
bool TakePendingEvents(
|
||||
Vector<ModuleLoadEvent, 0, InfallibleAllocPolicy>& aOut) {
|
||||
// Hold a reference to ensure we don't get deleted during this call.
|
||||
RefPtr<UntrustedDllsHandlerImpl> refHolder(this);
|
||||
if (!refHolder) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AutoCriticalSection lock(&mDataLock);
|
||||
mModuleLoadEvents.swap(aOut);
|
||||
return !aOut.empty();
|
||||
}
|
||||
};
|
||||
|
||||
Atomic<bool> UntrustedDllsHandlerImpl::sInstanceHasBeenSet;
|
||||
StaticRefPtr<UntrustedDllsHandlerImpl> UntrustedDllsHandlerImpl::sInstance;
|
||||
|
||||
/* static */
|
||||
void UntrustedDllsHandler::Init() { Unused << sTlsData.init(); }
|
||||
|
||||
#ifdef DEBUG
|
||||
/* static */
|
||||
void UntrustedDllsHandler::Shutdown() { UntrustedDllsHandlerImpl::Shutdown(); }
|
||||
#endif // DEBUG
|
||||
|
||||
/* static */
|
||||
void UntrustedDllsHandler::EnterLoaderCall() {
|
||||
if (!sTlsData.initialized()) {
|
||||
return;
|
||||
}
|
||||
if (!sTlsData.get()) {
|
||||
sTlsData.set(new TLSData());
|
||||
}
|
||||
sTlsData.get()->mCallDepth++;
|
||||
}
|
||||
|
||||
/* static */
|
||||
void UntrustedDllsHandler::ExitLoaderCall() {
|
||||
if (!sTlsData.initialized()) {
|
||||
return;
|
||||
}
|
||||
if (!--(sTlsData.get()->mCallDepth)) {
|
||||
delete sTlsData.get();
|
||||
sTlsData.set(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
/* static */
|
||||
void UntrustedDllsHandler::OnAfterModuleLoad(uintptr_t aBaseAddr,
|
||||
PUNICODE_STRING aLdrModuleName,
|
||||
double aLoadDurationMS) {
|
||||
RefPtr<UntrustedDllsHandlerImpl> p(UntrustedDllsHandlerImpl::GetInstance());
|
||||
if (!p) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sTlsData.initialized()) {
|
||||
return;
|
||||
}
|
||||
TLSData* tlsData = sTlsData.get();
|
||||
if (!tlsData) {
|
||||
return;
|
||||
}
|
||||
|
||||
ModuleLoadEvent::ModuleInfo moduleInfo;
|
||||
moduleInfo.mLdrName = CopyString(aLdrModuleName);
|
||||
moduleInfo.mBase = aBaseAddr;
|
||||
moduleInfo.mFullPath = GetModuleFullPath(aBaseAddr);
|
||||
moduleInfo.mLoadDurationMS = aLoadDurationMS;
|
||||
|
||||
Unused << tlsData->mModulesLoaded.emplaceBack(std::move(moduleInfo));
|
||||
|
||||
if (tlsData->mCallDepth > 1) {
|
||||
// Recursive call; bail and wait until top-level call can proceed.
|
||||
return;
|
||||
}
|
||||
|
||||
p->OnAfterTopLevelModuleLoad();
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool UntrustedDllsHandler::TakePendingEvents(
|
||||
Vector<ModuleLoadEvent, 0, InfallibleAllocPolicy>& aOut) {
|
||||
RefPtr<UntrustedDllsHandlerImpl> p(UntrustedDllsHandlerImpl::GetInstance());
|
||||
if (!p) {
|
||||
return false;
|
||||
}
|
||||
return p->TakePendingEvents(aOut);
|
||||
}
|
||||
|
||||
} // namespace glue
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,74 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_glue_UntrustedDllsHandler_h
|
||||
#define mozilla_glue_UntrustedDllsHandler_h
|
||||
|
||||
#include <windows.h>
|
||||
#include <winternl.h> // For PUNICODE_STRING
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/glue/WindowsDllServices.h" // For ModuleLoadEvent
|
||||
#include "mozilla/Vector.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace glue {
|
||||
|
||||
// This class examines successful module loads, converts them to a
|
||||
// glue::ModuleLoadEvent, and stores them for consumption by the caller.
|
||||
// In the process, we capture data about the event that is later used to
|
||||
// evaluate trustworthiness of the module.
|
||||
class UntrustedDllsHandler {
|
||||
public:
|
||||
static void Init();
|
||||
|
||||
#ifdef DEBUG
|
||||
static void Shutdown();
|
||||
#endif // DEBUG
|
||||
|
||||
/**
|
||||
* To prevent issues with re-entrancy, we only capture module load events at
|
||||
* the top-level call. Recursive module loads get bundled with the top-level
|
||||
* module load. In order to do that, we must track when LdrLoadDLL() enters
|
||||
* and exits, to detect recursion.
|
||||
*
|
||||
* EnterLoaderCall() must be called at the top of LdrLoadDLL(), and
|
||||
* ExitLoaderCall() must be called when LdrLoadDLL() exits.
|
||||
*
|
||||
* These methods may be called even outside of Init() / Shutdown().
|
||||
*/
|
||||
static void EnterLoaderCall();
|
||||
static void ExitLoaderCall();
|
||||
|
||||
/**
|
||||
* OnAfterModuleLoad should be called between calls to EnterLoaderCall() and
|
||||
* ExitLoaderCall(). This handles the successful module load and records it
|
||||
* internally if needed. To handle the resulting recorded event, call
|
||||
* TakePendingEvents().
|
||||
*
|
||||
* This method may be called even outside of Init() / Shutdown().
|
||||
*/
|
||||
static void OnAfterModuleLoad(uintptr_t aBaseAddr,
|
||||
PUNICODE_STRING aLdrModuleName,
|
||||
double aLoadDurationMS);
|
||||
|
||||
/**
|
||||
* Call TakePendingEvents to get any events that have been recorded since
|
||||
* the last call to TakePendingEvents().
|
||||
*
|
||||
* This method may be called even outside of Init() / Shutdown().
|
||||
*
|
||||
* @param aOut [out] Receives a list of events.
|
||||
* @return true if aOut now contains elements.
|
||||
*/
|
||||
static bool TakePendingEvents(
|
||||
Vector<ModuleLoadEvent, 0, InfallibleAllocPolicy>& aOut);
|
||||
};
|
||||
|
||||
} // namespace glue
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_glue_UntrustedDllsHandler_h
|
|
@ -14,6 +14,8 @@
|
|||
#include "Authenticode.h"
|
||||
#include "BaseProfiler.h"
|
||||
#include "CrashAnnotations.h"
|
||||
#include "MozglueUtils.h"
|
||||
#include "UntrustedDllsHandler.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsWindowsDllInterceptor.h"
|
||||
#include "mozilla/CmdLineAndEnvUtils.h"
|
||||
|
@ -30,25 +32,15 @@
|
|||
#include "mozilla/AutoProfilerLabel.h"
|
||||
#include "mozilla/glue/Debug.h"
|
||||
#include "mozilla/glue/WindowsDllServices.h"
|
||||
#include "mozilla/glue/WinUtils.h"
|
||||
|
||||
// Start new implementation
|
||||
#include "LoaderObserver.h"
|
||||
#include "ModuleLoadFrame.h"
|
||||
#include "mozilla/glue/WindowsUnicode.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
glue::Win32SRWLock gDllServicesLock;
|
||||
glue::detail::DllServicesBase* gDllServices;
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
using CrashReporter::Annotation;
|
||||
using CrashReporter::AnnotationToString;
|
||||
|
||||
static glue::Win32SRWLock gDllServicesLock;
|
||||
static glue::detail::DllServicesBase* gDllServices;
|
||||
|
||||
#define DLL_BLOCKLIST_ENTRY(name, ...) {name, __VA_ARGS__},
|
||||
#define DLL_BLOCKLIST_STRING_TYPE const char*
|
||||
#include "mozilla/WindowsDllBlocklistLegacyDefs.h"
|
||||
|
@ -61,6 +53,15 @@ static bool sBlocklistInitAttempted;
|
|||
static bool sBlocklistInitFailed;
|
||||
static bool sUser32BeforeBlocklist;
|
||||
|
||||
// This feature is enabled only on NIGHTLY, only for the main process.
|
||||
inline static bool IsUntrustedDllsHandlerEnabled() {
|
||||
#ifdef NIGHTLY_BUILD
|
||||
return !(sInitFlags & eDllBlocklistInitFlagIsChildProcess);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
typedef MOZ_NORETURN_PTR void(__fastcall* BaseThreadInitThunk_func)(
|
||||
BOOL aIsInitialThread, void* aStartAddress, void* aThreadParam);
|
||||
static WindowsDllInterceptor::FuncHookType<BaseThreadInitThunk_func>
|
||||
|
@ -336,6 +337,16 @@ static wchar_t* lastslash(wchar_t* s, int len) {
|
|||
static NTSTATUS NTAPI patched_LdrLoadDll(PWCHAR filePath, PULONG flags,
|
||||
PUNICODE_STRING moduleFileName,
|
||||
PHANDLE handle) {
|
||||
if (IsUntrustedDllsHandlerEnabled()) {
|
||||
glue::UntrustedDllsHandler::EnterLoaderCall();
|
||||
}
|
||||
// Warning: this must be at the top function scope.
|
||||
auto exitLoaderCallScopeExit = MakeScopeExit([]() {
|
||||
if (IsUntrustedDllsHandlerEnabled()) {
|
||||
glue::UntrustedDllsHandler::ExitLoaderCall();
|
||||
}
|
||||
});
|
||||
|
||||
// We have UCS2 (UTF16?), we want ASCII, but we also just want the filename
|
||||
// portion
|
||||
#define DLLNAME_MAX 128
|
||||
|
@ -505,33 +516,63 @@ continue_loading:
|
|||
moduleFileName->Buffer);
|
||||
#endif
|
||||
|
||||
glue::ModuleLoadFrame loadFrame(moduleFileName);
|
||||
// A few DLLs such as xul.dll and nss3.dll get loaded before mozglue's
|
||||
// AutoProfilerLabel is initialized, and this is a no-op in those cases. But
|
||||
// the vast majority of DLLs do get labelled here.
|
||||
AutoProfilerLabel label("WindowsDllBlocklist::patched_LdrLoadDll", dllName);
|
||||
|
||||
#ifdef _M_AMD64
|
||||
// Prevent the stack walker from suspending this thread when LdrLoadDll
|
||||
// holds the RtlLookupFunctionEntry lock.
|
||||
AutoSuppressStackWalking suppress;
|
||||
#endif
|
||||
|
||||
NTSTATUS ret;
|
||||
HANDLE myHandle;
|
||||
|
||||
ret = stub_LdrLoadDll(filePath, flags, moduleFileName, &myHandle);
|
||||
if (IsUntrustedDllsHandlerEnabled()) {
|
||||
TimeStamp loadStart = TimeStamp::Now();
|
||||
ret = stub_LdrLoadDll(filePath, flags, moduleFileName, &myHandle);
|
||||
TimeStamp loadEnd = TimeStamp::Now();
|
||||
|
||||
if (NT_SUCCESS(ret)) {
|
||||
double loadDurationMS = (loadEnd - loadStart).ToMilliseconds();
|
||||
// Win32 HMODULEs use the bottom two bits as flags. Ensure those bits are
|
||||
// cleared so we're left with the base address value.
|
||||
glue::UntrustedDllsHandler::OnAfterModuleLoad(
|
||||
(uintptr_t)myHandle & ~(uintptr_t)3, moduleFileName, loadDurationMS);
|
||||
glue::AutoSharedLock lock(gDllServicesLock);
|
||||
if (gDllServices) {
|
||||
Vector<glue::ModuleLoadEvent, 0, InfallibleAllocPolicy> events;
|
||||
if (glue::UntrustedDllsHandler::TakePendingEvents(events)) {
|
||||
gDllServices->NotifyUntrustedModuleLoads(events);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ret = stub_LdrLoadDll(filePath, flags, moduleFileName, &myHandle);
|
||||
}
|
||||
|
||||
if (handle) {
|
||||
*handle = myHandle;
|
||||
}
|
||||
|
||||
loadFrame.SetLoadStatus(ret, myHandle);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if defined(NIGHTLY_BUILD)
|
||||
// Map of specific thread proc addresses we should block. In particular,
|
||||
// LoadLibrary* APIs which indicate DLL injection
|
||||
static void* gStartAddressesToBlock[4];
|
||||
static mozilla::Vector<void*, 4>* gStartAddressesToBlock;
|
||||
#endif
|
||||
|
||||
static bool ShouldBlockThread(void* aStartAddress) {
|
||||
// Allows crashfirefox.exe to continue to work. Also if your threadproc is
|
||||
// null, this crash is intentional.
|
||||
if (aStartAddress == nullptr) return false;
|
||||
if (aStartAddress == 0) return false;
|
||||
|
||||
#if defined(NIGHTLY_BUILD)
|
||||
for (auto p : gStartAddressesToBlock) {
|
||||
for (auto p : *gStartAddressesToBlock) {
|
||||
if (p == aStartAddress) {
|
||||
return true;
|
||||
}
|
||||
|
@ -567,32 +608,56 @@ static WindowsDllInterceptor Kernel32Intercept;
|
|||
|
||||
static void GetNativeNtBlockSetWriter();
|
||||
|
||||
static glue::LoaderObserver gMozglueLoaderObserver;
|
||||
|
||||
MFBT_API void DllBlocklist_Initialize(uint32_t aInitFlags) {
|
||||
if (sBlocklistInitAttempted) {
|
||||
return;
|
||||
}
|
||||
sBlocklistInitAttempted = true;
|
||||
|
||||
sInitFlags = aInitFlags;
|
||||
|
||||
glue::ModuleLoadFrame::StaticInit(&gMozglueLoaderObserver);
|
||||
|
||||
#ifdef _M_AMD64
|
||||
if (!IsWin8OrLater()) {
|
||||
Kernel32Intercept.Init("kernel32.dll");
|
||||
|
||||
// The crash that this hook works around is only seen on Win7.
|
||||
stub_RtlInstallFunctionTableCallback.Set(
|
||||
Kernel32Intercept, "RtlInstallFunctionTableCallback",
|
||||
&patched_RtlInstallFunctionTableCallback);
|
||||
if (sInitFlags & eDllBlocklistInitFlagWasBootstrapped) {
|
||||
GetNativeNtBlockSetWriter();
|
||||
}
|
||||
|
||||
sBlocklistInitAttempted = true;
|
||||
#if defined(NIGHTLY_BUILD)
|
||||
gStartAddressesToBlock = new mozilla::Vector<void*, 4>;
|
||||
#endif
|
||||
|
||||
if (aInitFlags & eDllBlocklistInitFlagWasBootstrapped) {
|
||||
GetNativeNtBlockSetWriter();
|
||||
return;
|
||||
if (IsUntrustedDllsHandlerEnabled()) {
|
||||
#ifdef ENABLE_TESTS
|
||||
// Check whether we are running as an xpcshell test.
|
||||
if (mozilla::EnvHasValue("XPCSHELL_TEST_PROFILE_DIR")) {
|
||||
// For xpcshell tests, load this untrusted DLL early enough that the
|
||||
// untrusted module evaluator counts it as a startup module.
|
||||
// It is located in the current directory; the full path must be specified
|
||||
// or LoadLibrary() fails during xpcshell tests with ERROR_MOD_NOT_FOUND.
|
||||
|
||||
// This buffer will hold current directory + dll name
|
||||
wchar_t dllFullPath[MAX_PATH] = {};
|
||||
static const wchar_t kTestDllName[] = L"\\untrusted-startup-test-dll.dll";
|
||||
|
||||
// The amount of the buffer available to store the current directory,
|
||||
// leaving room for the dll name.
|
||||
static const DWORD kBufferDirLen =
|
||||
ArrayLength(dllFullPath) - ArrayLength(kTestDllName);
|
||||
|
||||
DWORD ret = ::GetCurrentDirectoryW(kBufferDirLen, dllFullPath);
|
||||
if ((ret > kBufferDirLen) || !ret) {
|
||||
// Buffer too small or the call failed
|
||||
printf_stderr("Unable to load %S; GetCurrentDirectoryW failed: %lu",
|
||||
kTestDllName, GetLastError());
|
||||
} else {
|
||||
wcscat_s(dllFullPath, kTestDllName);
|
||||
HMODULE hTestDll = ::LoadLibraryW(dllFullPath);
|
||||
if (!hTestDll) {
|
||||
printf_stderr("Unable to load %S; LoadLibraryW failed: %lu",
|
||||
kTestDllName, GetLastError());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
glue::UntrustedDllsHandler::Init();
|
||||
}
|
||||
|
||||
// There are a couple of exceptional cases where we skip user32.dll check.
|
||||
|
@ -647,6 +712,15 @@ MFBT_API void DllBlocklist_Initialize(uint32_t aInitFlags) {
|
|||
|
||||
Kernel32Intercept.Init("kernel32.dll");
|
||||
|
||||
#ifdef _M_AMD64
|
||||
if (!IsWin8OrLater()) {
|
||||
// The crash that this hook works around is only seen on Win7.
|
||||
stub_RtlInstallFunctionTableCallback.Set(
|
||||
Kernel32Intercept, "RtlInstallFunctionTableCallback",
|
||||
&patched_RtlInstallFunctionTableCallback);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Bug 1361410: WRusr.dll will overwrite our hook and cause a crash.
|
||||
// Workaround: If we detect WRusr.dll, don't hook.
|
||||
if (!GetModuleHandleW(L"WRusr.dll")) {
|
||||
|
@ -666,22 +740,34 @@ MFBT_API void DllBlocklist_Initialize(uint32_t aInitFlags) {
|
|||
void* pProc;
|
||||
|
||||
pProc = (void*)GetProcAddress(hKernel, "LoadLibraryA");
|
||||
gStartAddressesToBlock[0] = pProc;
|
||||
if (pProc) {
|
||||
Unused << gStartAddressesToBlock->append(pProc);
|
||||
}
|
||||
|
||||
pProc = (void*)GetProcAddress(hKernel, "LoadLibraryW");
|
||||
gStartAddressesToBlock[1] = pProc;
|
||||
if (pProc) {
|
||||
Unused << gStartAddressesToBlock->append(pProc);
|
||||
}
|
||||
|
||||
pProc = (void*)GetProcAddress(hKernel, "LoadLibraryExA");
|
||||
gStartAddressesToBlock[2] = pProc;
|
||||
if (pProc) {
|
||||
Unused << gStartAddressesToBlock->append(pProc);
|
||||
}
|
||||
|
||||
pProc = (void*)GetProcAddress(hKernel, "LoadLibraryExW");
|
||||
gStartAddressesToBlock[3] = pProc;
|
||||
if (pProc) {
|
||||
Unused << gStartAddressesToBlock->append(pProc);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
MFBT_API void DllBlocklist_Shutdown() {}
|
||||
MFBT_API void DllBlocklist_Shutdown() {
|
||||
if (IsUntrustedDllsHandlerEnabled()) {
|
||||
glue::UntrustedDllsHandler::Shutdown();
|
||||
}
|
||||
}
|
||||
#endif // DEBUG
|
||||
|
||||
static void WriteAnnotation(HANDLE aFile, Annotation aAnnotation,
|
||||
|
@ -733,6 +819,63 @@ MFBT_API bool DllBlocklist_CheckStatus() {
|
|||
// This section is for DLL Services
|
||||
// ============================================================================
|
||||
|
||||
// These types are documented on MSDN but not provided in any SDK headers
|
||||
|
||||
enum DllNotificationReason {
|
||||
LDR_DLL_NOTIFICATION_REASON_LOADED = 1,
|
||||
LDR_DLL_NOTIFICATION_REASON_UNLOADED = 2
|
||||
};
|
||||
|
||||
typedef struct _LDR_DLL_LOADED_NOTIFICATION_DATA {
|
||||
ULONG Flags; // Reserved.
|
||||
PCUNICODE_STRING FullDllName; // The full path name of the DLL module.
|
||||
PCUNICODE_STRING BaseDllName; // The base file name of the DLL module.
|
||||
PVOID DllBase; // A pointer to the base address for the DLL in memory.
|
||||
ULONG SizeOfImage; // The size of the DLL image, in bytes.
|
||||
} LDR_DLL_LOADED_NOTIFICATION_DATA, *PLDR_DLL_LOADED_NOTIFICATION_DATA;
|
||||
|
||||
typedef struct _LDR_DLL_UNLOADED_NOTIFICATION_DATA {
|
||||
ULONG Flags; // Reserved.
|
||||
PCUNICODE_STRING FullDllName; // The full path name of the DLL module.
|
||||
PCUNICODE_STRING BaseDllName; // The base file name of the DLL module.
|
||||
PVOID DllBase; // A pointer to the base address for the DLL in memory.
|
||||
ULONG SizeOfImage; // The size of the DLL image, in bytes.
|
||||
} LDR_DLL_UNLOADED_NOTIFICATION_DATA, *PLDR_DLL_UNLOADED_NOTIFICATION_DATA;
|
||||
|
||||
typedef union _LDR_DLL_NOTIFICATION_DATA {
|
||||
LDR_DLL_LOADED_NOTIFICATION_DATA Loaded;
|
||||
LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded;
|
||||
} LDR_DLL_NOTIFICATION_DATA, *PLDR_DLL_NOTIFICATION_DATA;
|
||||
|
||||
typedef const LDR_DLL_NOTIFICATION_DATA* PCLDR_DLL_NOTIFICATION_DATA;
|
||||
|
||||
typedef VOID(CALLBACK* PLDR_DLL_NOTIFICATION_FUNCTION)(
|
||||
ULONG aReason, PCLDR_DLL_NOTIFICATION_DATA aNotificationData,
|
||||
PVOID aContext);
|
||||
|
||||
NTSTATUS NTAPI LdrRegisterDllNotification(
|
||||
ULONG aFlags, PLDR_DLL_NOTIFICATION_FUNCTION aCallback, PVOID aContext,
|
||||
PVOID* aCookie);
|
||||
|
||||
static PVOID gNotificationCookie;
|
||||
|
||||
static VOID CALLBACK DllLoadNotification(
|
||||
ULONG aReason, PCLDR_DLL_NOTIFICATION_DATA aNotificationData,
|
||||
PVOID aContext) {
|
||||
if (aReason != LDR_DLL_NOTIFICATION_REASON_LOADED) {
|
||||
// We don't care about unloads
|
||||
return;
|
||||
}
|
||||
|
||||
glue::AutoSharedLock lock(gDllServicesLock);
|
||||
if (!gDllServices) {
|
||||
return;
|
||||
}
|
||||
|
||||
PCUNICODE_STRING fullDllName = aNotificationData->Loaded.FullDllName;
|
||||
gDllServices->DispatchDllLoadNotification(fullDllName);
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
Authenticode* GetAuthenticode();
|
||||
} // namespace mozilla
|
||||
|
@ -742,10 +885,29 @@ MFBT_API void DllBlocklist_SetFullDllServices(
|
|||
glue::AutoExclusiveLock lock(gDllServicesLock);
|
||||
if (aSvc) {
|
||||
aSvc->SetAuthenticodeImpl(GetAuthenticode());
|
||||
gMozglueLoaderObserver.Forward(aSvc);
|
||||
|
||||
if (!gNotificationCookie) {
|
||||
auto pLdrRegisterDllNotification =
|
||||
reinterpret_cast<decltype(&::LdrRegisterDllNotification)>(
|
||||
::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"),
|
||||
"LdrRegisterDllNotification"));
|
||||
|
||||
MOZ_DIAGNOSTIC_ASSERT(pLdrRegisterDllNotification);
|
||||
|
||||
mozilla::DebugOnly<NTSTATUS> ntStatus = pLdrRegisterDllNotification(
|
||||
0, &DllLoadNotification, nullptr, &gNotificationCookie);
|
||||
MOZ_ASSERT(NT_SUCCESS(ntStatus));
|
||||
}
|
||||
}
|
||||
|
||||
gDllServices = aSvc;
|
||||
|
||||
if (IsUntrustedDllsHandlerEnabled() && gDllServices) {
|
||||
Vector<glue::ModuleLoadEvent, 0, InfallibleAllocPolicy> events;
|
||||
if (glue::UntrustedDllsHandler::TakePendingEvents(events)) {
|
||||
gDllServices->NotifyUntrustedModuleLoads(events);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MFBT_API void DllBlocklist_SetBasicDllServices(
|
||||
|
@ -755,5 +917,4 @@ MFBT_API void DllBlocklist_SetBasicDllServices(
|
|||
}
|
||||
|
||||
aSvc->SetAuthenticodeImpl(GetAuthenticode());
|
||||
gMozglueLoaderObserver.Clear();
|
||||
}
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Authenticode.h"
|
||||
#include "mozilla/LoaderAPIInterfaces.h"
|
||||
#include "mozilla/mozalloc.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Vector.h"
|
||||
|
@ -22,7 +21,6 @@
|
|||
# include "nsISupportsImpl.h"
|
||||
# include "nsString.h"
|
||||
# include "nsThreadUtils.h"
|
||||
# include "prthread.h"
|
||||
|
||||
#endif // defined(MOZILLA_INTERNAL_API)
|
||||
|
||||
|
@ -31,6 +29,44 @@
|
|||
|
||||
namespace mozilla {
|
||||
namespace glue {
|
||||
|
||||
// Holds data about a top-level DLL load event, for the purposes of later
|
||||
// evaluating the DLLs for trustworthiness. DLLs are loaded recursively,
|
||||
// so we hold the top-level DLL and all child DLLs in mModules.
|
||||
class ModuleLoadEvent {
|
||||
public:
|
||||
class ModuleInfo {
|
||||
public:
|
||||
ModuleInfo() = default;
|
||||
~ModuleInfo() = default;
|
||||
ModuleInfo(const ModuleInfo& aOther) = delete;
|
||||
ModuleInfo(ModuleInfo&& aOther) = default;
|
||||
|
||||
ModuleInfo operator=(const ModuleInfo& aOther) = delete;
|
||||
ModuleInfo operator=(ModuleInfo&& aOther) = delete;
|
||||
|
||||
uintptr_t mBase;
|
||||
UniquePtr<wchar_t[]> mLdrName;
|
||||
UniquePtr<wchar_t[]> mFullPath;
|
||||
double mLoadDurationMS;
|
||||
};
|
||||
|
||||
ModuleLoadEvent() = default;
|
||||
~ModuleLoadEvent() = default;
|
||||
ModuleLoadEvent(const ModuleLoadEvent& aOther) = delete;
|
||||
ModuleLoadEvent(ModuleLoadEvent&& aOther) = default;
|
||||
|
||||
ModuleLoadEvent& operator=(const ModuleLoadEvent& aOther) = delete;
|
||||
ModuleLoadEvent& operator=(ModuleLoadEvent&& aOther) = delete;
|
||||
|
||||
DWORD mThreadID;
|
||||
uint64_t mProcessUptimeMS;
|
||||
Vector<ModuleInfo, 0, InfallibleAllocPolicy> mModules;
|
||||
|
||||
// Stores instruction pointers, top-to-bottom.
|
||||
Vector<uintptr_t, 0, InfallibleAllocPolicy> mStack;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
class DllServicesBase : public Authenticode {
|
||||
|
@ -41,23 +77,24 @@ class DllServicesBase : public Authenticode {
|
|||
* this function should be used for is dispatching the event to our
|
||||
* event loop so that it may be handled in a safe context.
|
||||
*/
|
||||
virtual void DispatchDllLoadNotification(ModuleLoadInfo&& aModLoadInfo) = 0;
|
||||
virtual void DispatchDllLoadNotification(PCUNICODE_STRING aDllName) = 0;
|
||||
|
||||
/**
|
||||
* This function accepts module load events to be processed later for
|
||||
* the untrusted modules telemetry ping.
|
||||
*
|
||||
* WARNING: This method is run from within the Windows loader and should
|
||||
* only perform trivial, loader-friendly operations.
|
||||
* only perform trivial, loader-friendly, operations.
|
||||
*/
|
||||
virtual void DispatchModuleLoadBacklogNotification(
|
||||
ModuleLoadInfoVec&& aEvents) = 0;
|
||||
virtual void NotifyUntrustedModuleLoads(
|
||||
const Vector<glue::ModuleLoadEvent, 0, InfallibleAllocPolicy>&
|
||||
aEvents) = 0;
|
||||
|
||||
void SetAuthenticodeImpl(Authenticode* aAuthenticode) {
|
||||
mAuthenticode = aAuthenticode;
|
||||
}
|
||||
|
||||
// In debug builds we override GetBinaryOrgName to add a Gecko-specific
|
||||
// In debug builds, we override GetBinaryOrgName to add a Gecko-specific
|
||||
// assertion. OTOH, we normally do not want people overriding this function,
|
||||
// so we'll make it final in the release case, thus covering all bases.
|
||||
#if defined(DEBUG)
|
||||
|
@ -96,49 +133,15 @@ class DllServicesBase : public Authenticode {
|
|||
|
||||
#if defined(MOZILLA_INTERNAL_API)
|
||||
|
||||
struct EnhancedModuleLoadInfo final {
|
||||
explicit EnhancedModuleLoadInfo(ModuleLoadInfo&& aModLoadInfo)
|
||||
: mNtLoadInfo(std::move(aModLoadInfo)) {
|
||||
// Only populate mThreadName when we're on the same thread as the event
|
||||
if (mNtLoadInfo.mThreadId == ::GetCurrentThreadId()) {
|
||||
mThreadName = PR_GetThreadName(PR_GetCurrentThread());
|
||||
}
|
||||
MOZ_ASSERT(!mNtLoadInfo.mSectionName.IsEmpty());
|
||||
}
|
||||
|
||||
EnhancedModuleLoadInfo(EnhancedModuleLoadInfo&&) = default;
|
||||
EnhancedModuleLoadInfo& operator=(EnhancedModuleLoadInfo&&) = default;
|
||||
|
||||
EnhancedModuleLoadInfo(const EnhancedModuleLoadInfo&) = delete;
|
||||
EnhancedModuleLoadInfo& operator=(const EnhancedModuleLoadInfo&) = delete;
|
||||
|
||||
nsDependentString GetSectionName() const {
|
||||
return mNtLoadInfo.mSectionName.AsString();
|
||||
}
|
||||
|
||||
using BacktraceType = decltype(ModuleLoadInfo::mBacktrace);
|
||||
|
||||
ModuleLoadInfo mNtLoadInfo;
|
||||
nsCString mThreadName;
|
||||
};
|
||||
|
||||
class DllServices : public detail::DllServicesBase {
|
||||
public:
|
||||
void DispatchDllLoadNotification(ModuleLoadInfo&& aModLoadInfo) final {
|
||||
nsCOMPtr<nsIRunnable> runnable(
|
||||
NewRunnableMethod<StoreCopyPassByRRef<EnhancedModuleLoadInfo>>(
|
||||
"DllServices::NotifyDllLoad", this, &DllServices::NotifyDllLoad,
|
||||
std::move(aModLoadInfo)));
|
||||
void DispatchDllLoadNotification(PCUNICODE_STRING aDllName) final {
|
||||
nsDependentSubstring strDllName(aDllName->Buffer,
|
||||
aDllName->Length / sizeof(wchar_t));
|
||||
|
||||
SystemGroup::Dispatch(TaskCategory::Other, runnable.forget());
|
||||
}
|
||||
|
||||
void DispatchModuleLoadBacklogNotification(
|
||||
ModuleLoadInfoVec&& aEvents) final {
|
||||
nsCOMPtr<nsIRunnable> runnable(
|
||||
NewRunnableMethod<StoreCopyPassByRRef<ModuleLoadInfoVec>>(
|
||||
"DllServices::NotifyModuleLoadBacklog", this,
|
||||
&DllServices::NotifyModuleLoadBacklog, std::move(aEvents)));
|
||||
nsCOMPtr<nsIRunnable> runnable(NewRunnableMethod<bool, nsString>(
|
||||
"DllServices::NotifyDllLoad", this, &DllServices::NotifyDllLoad,
|
||||
NS_IsMainThread(), strDllName));
|
||||
|
||||
SystemGroup::Dispatch(TaskCategory::Other, runnable.forget());
|
||||
}
|
||||
|
@ -158,8 +161,8 @@ class DllServices : public detail::DllServicesBase {
|
|||
DllServices() = default;
|
||||
~DllServices() = default;
|
||||
|
||||
virtual void NotifyDllLoad(EnhancedModuleLoadInfo&& aModLoadInfo) = 0;
|
||||
virtual void NotifyModuleLoadBacklog(ModuleLoadInfoVec&& aEvents) = 0;
|
||||
virtual void NotifyDllLoad(const bool aIsMainThread,
|
||||
const nsString& aDllName) = 0;
|
||||
};
|
||||
|
||||
#else
|
||||
|
@ -171,11 +174,12 @@ class BasicDllServices final : public detail::DllServicesBase {
|
|||
~BasicDllServices() = default;
|
||||
|
||||
// Not useful in this class, so provide a default implementation
|
||||
virtual void DispatchDllLoadNotification(
|
||||
ModuleLoadInfo&& aModLoadInfo) override {}
|
||||
virtual void DispatchDllLoadNotification(PCUNICODE_STRING aDllName) override {
|
||||
}
|
||||
|
||||
virtual void DispatchModuleLoadBacklogNotification(
|
||||
ModuleLoadInfoVec&& aEvents) override {}
|
||||
virtual void NotifyUntrustedModuleLoads(
|
||||
const Vector<glue::ModuleLoadEvent, 0, InfallibleAllocPolicy>& aEvents)
|
||||
override {}
|
||||
};
|
||||
|
||||
#endif // defined(MOZILLA_INTERNAL_API)
|
|
@ -58,11 +58,48 @@ if CONFIG['MOZ_WIDGET_TOOLKIT']:
|
|||
'/memory/build',
|
||||
]
|
||||
|
||||
SOURCES += [
|
||||
'Authenticode.cpp',
|
||||
'UntrustedDllsHandler.cpp',
|
||||
'WindowsDllBlocklist.cpp',
|
||||
]
|
||||
|
||||
OS_LIBS += [
|
||||
'crypt32',
|
||||
'version',
|
||||
'wintrust',
|
||||
]
|
||||
DELAYLOAD_DLLS += [
|
||||
'crypt32.dll',
|
||||
'wintrust.dll',
|
||||
]
|
||||
EXPORTS.mozilla += [
|
||||
'Authenticode.h',
|
||||
'WindowsDllBlocklistCommon.h',
|
||||
]
|
||||
EXPORTS.mozilla.glue += [
|
||||
'WindowsDllServices.h',
|
||||
]
|
||||
|
||||
# Generate DLL Blocklists
|
||||
blocklist_header_types = ['A11y', 'Launcher', 'Legacy', 'Test']
|
||||
blocklist_file_leaf_tpl = 'WindowsDllBlocklist{0}Defs.h'
|
||||
blocklist_files = tuple([blocklist_file_leaf_tpl.format(type)
|
||||
for type in blocklist_header_types])
|
||||
GENERATED_FILES += [
|
||||
blocklist_files
|
||||
]
|
||||
blocklist_defs = GENERATED_FILES[blocklist_files]
|
||||
blocklist_defs.script = 'gen_dll_blocklist_defs.py:gen_blocklists'
|
||||
blocklist_defs.inputs = ['WindowsDllBlocklistDefs.in']
|
||||
EXPORTS.mozilla += ['!' + hdr for hdr in blocklist_files]
|
||||
|
||||
EXPORTS.mozilla += [
|
||||
'arm.h',
|
||||
'mips.h',
|
||||
'ppc.h',
|
||||
'SSE.h',
|
||||
'WindowsDllBlocklist.h',
|
||||
]
|
||||
|
||||
if CONFIG['CPU_ARCH'].startswith('x86'):
|
||||
|
|
|
@ -1,150 +0,0 @@
|
|||
/* -*- 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 "LoaderObserver.h"
|
||||
|
||||
#include "mozilla/AutoProfilerLabel.h"
|
||||
#include "mozilla/glue/WindowsUnicode.h"
|
||||
#include "mozilla/StackWalk_windows.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct LoadContext {
|
||||
LoadContext(mozilla::ProfilerLabel&& aLabel,
|
||||
mozilla::UniquePtr<char[]>&& aDynamicStringStorage)
|
||||
: mProfilerLabel(std::move(aLabel)),
|
||||
mDynamicStringStorage(std::move(aDynamicStringStorage)) {}
|
||||
mozilla::ProfilerLabel mProfilerLabel;
|
||||
mozilla::UniquePtr<char[]> mDynamicStringStorage;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
extern glue::Win32SRWLock gDllServicesLock;
|
||||
extern glue::detail::DllServicesBase* gDllServices;
|
||||
|
||||
namespace glue {
|
||||
|
||||
void LoaderObserver::OnBeginDllLoad(void** aContext,
|
||||
PCUNICODE_STRING aRequestedDllName) {
|
||||
MOZ_ASSERT(aContext);
|
||||
if (IsProfilerPresent()) {
|
||||
UniquePtr<char[]> utf8RequestedDllName(WideToUTF8(aRequestedDllName));
|
||||
const char* dynamicString = utf8RequestedDllName.get();
|
||||
*aContext = new LoadContext(
|
||||
ProfilerLabelBegin("mozilla::glue::LoaderObserver::OnBeginDllLoad",
|
||||
dynamicString, &aContext),
|
||||
std::move(utf8RequestedDllName));
|
||||
}
|
||||
|
||||
#ifdef _M_AMD64
|
||||
// Prevent the stack walker from suspending this thread when LdrLoadDll
|
||||
// holds the RtlLookupFunctionEntry lock.
|
||||
SuppressStackWalking();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool LoaderObserver::SubstituteForLSP(PCUNICODE_STRING aLSPLeafName,
|
||||
PHANDLE aOutHandle) {
|
||||
// Currently unsupported
|
||||
return false;
|
||||
}
|
||||
|
||||
void LoaderObserver::OnEndDllLoad(void* aContext, NTSTATUS aNtStatus,
|
||||
ModuleLoadInfo&& aModuleLoadInfo) {
|
||||
#ifdef _M_AMD64
|
||||
DesuppressStackWalking();
|
||||
#endif
|
||||
|
||||
MOZ_ASSERT_IF(IsProfilerPresent(), aContext);
|
||||
UniquePtr<LoadContext> loadContext(static_cast<LoadContext*>(aContext));
|
||||
if (loadContext && IsValidProfilerLabel(loadContext->mProfilerLabel)) {
|
||||
ProfilerLabelEnd(loadContext->mProfilerLabel);
|
||||
}
|
||||
|
||||
if (!NT_SUCCESS(aNtStatus) || !aModuleLoadInfo.WasMapped()) {
|
||||
return;
|
||||
}
|
||||
|
||||
{ // Scope for lock
|
||||
AutoSharedLock lock(gDllServicesLock);
|
||||
if (gDllServices) {
|
||||
gDllServices->DispatchDllLoadNotification(std::move(aModuleLoadInfo));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// No dll services, save for later
|
||||
AutoExclusiveLock lock(mLock);
|
||||
if (!mModuleLoads) {
|
||||
mModuleLoads = new ModuleLoadInfoVec();
|
||||
}
|
||||
|
||||
Unused << mModuleLoads->emplaceBack(
|
||||
std::forward<ModuleLoadInfo>(aModuleLoadInfo));
|
||||
}
|
||||
|
||||
void LoaderObserver::Forward(nt::LoaderObserver* aNext) {
|
||||
MOZ_ASSERT_UNREACHABLE(
|
||||
"This implementation does not forward to any more "
|
||||
"nt::LoaderObserver objects");
|
||||
}
|
||||
|
||||
void LoaderObserver::Forward(detail::DllServicesBase* aNext) {
|
||||
MOZ_ASSERT(aNext);
|
||||
if (!aNext) {
|
||||
return;
|
||||
}
|
||||
|
||||
ModuleLoadInfoVec* moduleLoads = nullptr;
|
||||
|
||||
{ // Scope for lock
|
||||
AutoExclusiveLock lock(mLock);
|
||||
moduleLoads = mModuleLoads;
|
||||
mModuleLoads = nullptr;
|
||||
}
|
||||
|
||||
if (!moduleLoads) {
|
||||
return;
|
||||
}
|
||||
|
||||
aNext->DispatchModuleLoadBacklogNotification(std::move(*moduleLoads));
|
||||
delete moduleLoads;
|
||||
}
|
||||
|
||||
void LoaderObserver::Clear() {
|
||||
ModuleLoadInfoVec* moduleLoads = nullptr;
|
||||
|
||||
{ // Scope for lock
|
||||
AutoExclusiveLock lock(mLock);
|
||||
moduleLoads = mModuleLoads;
|
||||
mModuleLoads = nullptr;
|
||||
}
|
||||
|
||||
delete moduleLoads;
|
||||
}
|
||||
|
||||
void LoaderObserver::OnForward(ModuleLoadInfoVec&& aInfo) {
|
||||
AutoExclusiveLock lock(mLock);
|
||||
if (!mModuleLoads) {
|
||||
mModuleLoads = new ModuleLoadInfoVec();
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mModuleLoads->empty());
|
||||
if (mModuleLoads->empty()) {
|
||||
*mModuleLoads = std::move(aInfo);
|
||||
} else {
|
||||
// This should not happen, but we can handle it
|
||||
for (auto&& item : aInfo) {
|
||||
Unused << mModuleLoads->append(std::move(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace glue
|
||||
} // namespace mozilla
|
|
@ -1,44 +0,0 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_glue_LoaderObserver_h
|
||||
#define mozilla_glue_LoaderObserver_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/NtLoaderAPI.h"
|
||||
#include "mozilla/glue/WindowsDllServices.h"
|
||||
#include "mozilla/glue/WinUtils.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace glue {
|
||||
|
||||
class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS LoaderObserver final
|
||||
: public nt::LoaderObserver {
|
||||
public:
|
||||
constexpr LoaderObserver() : mModuleLoads(nullptr) {}
|
||||
|
||||
void OnBeginDllLoad(void** aContext,
|
||||
PCUNICODE_STRING aPreliminaryDllName) final;
|
||||
bool SubstituteForLSP(PCUNICODE_STRING aLspLeafName,
|
||||
PHANDLE aOutHandle) final;
|
||||
void OnEndDllLoad(void* aContext, NTSTATUS aNtStatus,
|
||||
ModuleLoadInfo&& aModuleLoadInfo) final;
|
||||
void Forward(nt::LoaderObserver* aNext) final;
|
||||
void OnForward(ModuleLoadInfoVec&& aInfo) final;
|
||||
|
||||
void Forward(mozilla::glue::detail::DllServicesBase* aSvc);
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
Win32SRWLock mLock;
|
||||
ModuleLoadInfoVec* mModuleLoads;
|
||||
};
|
||||
|
||||
} // namespace glue
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_glue_LoaderObserver_h
|
|
@ -1,87 +0,0 @@
|
|||
/* -*- 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 "ModuleLoadFrame.h"
|
||||
#include "mozilla/NativeNt.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "WindowsFallbackLoaderAPI.h"
|
||||
|
||||
static bool IsNullTerminated(PCUNICODE_STRING aStr) {
|
||||
return aStr && (aStr->MaximumLength >= (aStr->Length + sizeof(WCHAR))) &&
|
||||
aStr->Buffer && aStr->Buffer[aStr->Length / sizeof(WCHAR)] == 0;
|
||||
}
|
||||
|
||||
static mozilla::FallbackLoaderAPI gFallbackLoaderAPI;
|
||||
|
||||
namespace mozilla {
|
||||
namespace glue {
|
||||
|
||||
nt::LoaderAPI* ModuleLoadFrame::sLoaderAPI;
|
||||
|
||||
using GetNtLoaderAPIFn = decltype(&mozilla::GetNtLoaderAPI);
|
||||
|
||||
/* static */
|
||||
void ModuleLoadFrame::StaticInit(nt::LoaderObserver* aNewObserver) {
|
||||
const auto pGetNtLoaderAPI = reinterpret_cast<GetNtLoaderAPIFn>(
|
||||
::GetProcAddress(::GetModuleHandleW(nullptr), "GetNtLoaderAPI"));
|
||||
if (!pGetNtLoaderAPI) {
|
||||
// This case occurs in processes other than firefox.exe that do not contain
|
||||
// the launcher process blocklist.
|
||||
gFallbackLoaderAPI.SetObserver(aNewObserver);
|
||||
sLoaderAPI = &gFallbackLoaderAPI;
|
||||
return;
|
||||
}
|
||||
|
||||
sLoaderAPI = pGetNtLoaderAPI(aNewObserver);
|
||||
}
|
||||
|
||||
ModuleLoadFrame::ModuleLoadFrame(PCUNICODE_STRING aRequestedDllName)
|
||||
: mAlreadyLoaded(false),
|
||||
mContext(nullptr),
|
||||
mDllLoadStatus(STATUS_UNSUCCESSFUL),
|
||||
mLoadInfo(sLoaderAPI->ConstructAndNotifyBeginDllLoad(&mContext,
|
||||
aRequestedDllName)) {
|
||||
if (!aRequestedDllName) {
|
||||
return;
|
||||
}
|
||||
|
||||
UniquePtr<WCHAR[]> nameBuf;
|
||||
const WCHAR* name = nullptr;
|
||||
|
||||
if (IsNullTerminated(aRequestedDllName)) {
|
||||
name = aRequestedDllName->Buffer;
|
||||
} else {
|
||||
USHORT charLenExclNul = aRequestedDllName->Length / sizeof(WCHAR);
|
||||
USHORT charLenInclNul = charLenExclNul + 1;
|
||||
nameBuf = MakeUnique<WCHAR[]>(charLenInclNul);
|
||||
if (!wcsncpy_s(nameBuf.get(), charLenInclNul, aRequestedDllName->Buffer,
|
||||
charLenExclNul)) {
|
||||
name = nameBuf.get();
|
||||
}
|
||||
}
|
||||
|
||||
mAlreadyLoaded = name && !!::GetModuleHandleW(name);
|
||||
}
|
||||
|
||||
ModuleLoadFrame::~ModuleLoadFrame() {
|
||||
sLoaderAPI->NotifyEndDllLoad(mContext, mDllLoadStatus, std::move(mLoadInfo));
|
||||
}
|
||||
|
||||
void ModuleLoadFrame::SetLoadStatus(NTSTATUS aNtStatus, HANDLE aHandle) {
|
||||
mDllLoadStatus = aNtStatus;
|
||||
void* baseAddr = mozilla::nt::PEHeaders::HModuleToBaseAddr(
|
||||
reinterpret_cast<HMODULE>(aHandle));
|
||||
mLoadInfo.mBaseAddr = baseAddr;
|
||||
if (!mAlreadyLoaded) {
|
||||
mLoadInfo.mSectionName = sLoaderAPI->GetSectionName(baseAddr);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace glue
|
||||
} // namespace mozilla
|
|
@ -1,43 +0,0 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_glue_ModuleLoadFrame_h
|
||||
#define mozilla_glue_ModuleLoadFrame_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/NtLoaderAPI.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace glue {
|
||||
|
||||
class MOZ_RAII ModuleLoadFrame final {
|
||||
public:
|
||||
explicit ModuleLoadFrame(PCUNICODE_STRING aRequestedDllName);
|
||||
~ModuleLoadFrame();
|
||||
|
||||
void SetLoadStatus(NTSTATUS aNtStatus, HANDLE aHandle);
|
||||
|
||||
ModuleLoadFrame(const ModuleLoadFrame&) = delete;
|
||||
ModuleLoadFrame(ModuleLoadFrame&&) = delete;
|
||||
ModuleLoadFrame& operator=(const ModuleLoadFrame&) = delete;
|
||||
ModuleLoadFrame& operator=(ModuleLoadFrame&&) = delete;
|
||||
|
||||
static void StaticInit(nt::LoaderObserver* aNewObserver);
|
||||
|
||||
private:
|
||||
bool mAlreadyLoaded;
|
||||
void* mContext;
|
||||
NTSTATUS mDllLoadStatus;
|
||||
ModuleLoadInfo mLoadInfo;
|
||||
|
||||
private:
|
||||
static nt::LoaderAPI* sLoaderAPI;
|
||||
};
|
||||
|
||||
} // namespace glue
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_glue_ModuleLoadFrame_h
|
|
@ -1,74 +0,0 @@
|
|||
/* -*- 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 "WindowsFallbackLoaderAPI.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
ModuleLoadInfo FallbackLoaderAPI::ConstructAndNotifyBeginDllLoad(
|
||||
void** aContext, PCUNICODE_STRING aRequestedDllName) {
|
||||
ModuleLoadInfo loadInfo(aRequestedDllName);
|
||||
|
||||
MOZ_ASSERT(mLoaderObserver);
|
||||
if (mLoaderObserver) {
|
||||
mLoaderObserver->OnBeginDllLoad(aContext, aRequestedDllName);
|
||||
}
|
||||
|
||||
return loadInfo;
|
||||
}
|
||||
|
||||
bool FallbackLoaderAPI::SubstituteForLSP(PCUNICODE_STRING aLSPLeafName,
|
||||
PHANDLE aOutHandle) {
|
||||
MOZ_ASSERT(mLoaderObserver);
|
||||
if (!mLoaderObserver) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return mLoaderObserver->SubstituteForLSP(aLSPLeafName, aOutHandle);
|
||||
}
|
||||
|
||||
void FallbackLoaderAPI::NotifyEndDllLoad(void* aContext, NTSTATUS aLoadNtStatus,
|
||||
ModuleLoadInfo&& aModuleLoadInfo) {
|
||||
aModuleLoadInfo.SetEndLoadTimeStamp();
|
||||
|
||||
if (NT_SUCCESS(aLoadNtStatus)) {
|
||||
aModuleLoadInfo.CaptureBacktrace();
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mLoaderObserver);
|
||||
if (mLoaderObserver) {
|
||||
mLoaderObserver->OnEndDllLoad(aContext, aLoadNtStatus,
|
||||
std::move(aModuleLoadInfo));
|
||||
}
|
||||
}
|
||||
|
||||
nt::AllocatedUnicodeString FallbackLoaderAPI::GetSectionName(
|
||||
void* aSectionAddr) {
|
||||
static const StaticDynamicallyLinkedFunctionPtr<decltype(
|
||||
&::NtQueryVirtualMemory)>
|
||||
pNtQueryVirtualMemory(L"ntdll.dll", "NtQueryVirtualMemory");
|
||||
MOZ_ASSERT(pNtQueryVirtualMemory);
|
||||
|
||||
if (!pNtQueryVirtualMemory) {
|
||||
return nt::AllocatedUnicodeString();
|
||||
}
|
||||
|
||||
nt::MemorySectionNameBuf buf;
|
||||
NTSTATUS ntStatus =
|
||||
pNtQueryVirtualMemory(::GetCurrentProcess(), aSectionAddr,
|
||||
MemorySectionName, &buf, sizeof(buf), nullptr);
|
||||
if (!NT_SUCCESS(ntStatus)) {
|
||||
return nt::AllocatedUnicodeString();
|
||||
}
|
||||
|
||||
return nt::AllocatedUnicodeString(&buf.mSectionFileName);
|
||||
}
|
||||
|
||||
void FallbackLoaderAPI::SetObserver(nt::LoaderObserver* aLoaderObserver) {
|
||||
mLoaderObserver = aLoaderObserver;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -1,36 +0,0 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_WindowsFallbackLoaderAPI_h
|
||||
#define mozilla_WindowsFallbackLoaderAPI_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/NtLoaderAPI.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS FallbackLoaderAPI final
|
||||
: public nt::LoaderAPI {
|
||||
public:
|
||||
constexpr FallbackLoaderAPI() : mLoaderObserver(nullptr) {}
|
||||
|
||||
ModuleLoadInfo ConstructAndNotifyBeginDllLoad(
|
||||
void** aContext, PCUNICODE_STRING aRequestedDllName) final;
|
||||
bool SubstituteForLSP(PCUNICODE_STRING aLSPLeafName,
|
||||
PHANDLE aOutHandle) final;
|
||||
void NotifyEndDllLoad(void* aContext, NTSTATUS aLoadNtStatus,
|
||||
ModuleLoadInfo&& aModuleLoadInfo) final;
|
||||
nt::AllocatedUnicodeString GetSectionName(void* aSectionAddr) final;
|
||||
|
||||
void SetObserver(nt::LoaderObserver* aLoaderObserver);
|
||||
|
||||
private:
|
||||
nt::LoaderObserver* mLoaderObserver;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_WindowsFallbackLoaderAPI_h
|
|
@ -1,56 +0,0 @@
|
|||
# -*- 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/.
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT']:
|
||||
|
||||
SOURCES += [
|
||||
# This file contains a |using namespace mozilla;| statement
|
||||
'WindowsDllBlocklist.cpp',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'Authenticode.cpp',
|
||||
'LoaderObserver.cpp',
|
||||
'ModuleLoadFrame.cpp',
|
||||
'WindowsFallbackLoaderAPI.cpp',
|
||||
]
|
||||
|
||||
OS_LIBS += [
|
||||
'crypt32',
|
||||
'ntdll',
|
||||
'version',
|
||||
'wintrust',
|
||||
]
|
||||
|
||||
DELAYLOAD_DLLS += [
|
||||
'crypt32.dll',
|
||||
'wintrust.dll',
|
||||
]
|
||||
|
||||
EXPORTS.mozilla += [
|
||||
'Authenticode.h',
|
||||
'WindowsDllBlocklist.h',
|
||||
'WindowsDllBlocklistCommon.h',
|
||||
]
|
||||
|
||||
EXPORTS.mozilla.glue += [
|
||||
'WindowsDllServices.h',
|
||||
]
|
||||
|
||||
# Generate DLL Blocklists
|
||||
blocklist_header_types = ['A11y', 'Launcher', 'Legacy', 'Test']
|
||||
blocklist_file_leaf_tpl = 'WindowsDllBlocklist{0}Defs.h'
|
||||
blocklist_files = tuple([blocklist_file_leaf_tpl.format(type)
|
||||
for type in blocklist_header_types])
|
||||
GENERATED_FILES += [
|
||||
blocklist_files
|
||||
]
|
||||
blocklist_defs = GENERATED_FILES[blocklist_files]
|
||||
blocklist_defs.script = 'gen_dll_blocklist_defs.py:gen_blocklists'
|
||||
blocklist_defs.inputs = ['WindowsDllBlocklistDefs.in']
|
||||
EXPORTS.mozilla += ['!' + hdr for hdr in blocklist_files]
|
||||
|
||||
FINAL_LIBRARY = 'mozglue'
|
|
@ -16,14 +16,9 @@
|
|||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/WinHeaderOnlyUtils.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/Span.h"
|
||||
#include "mozilla/WinHeaderOnlyUtils.h"
|
||||
|
||||
#if defined(MOZILLA_INTERNAL_API)
|
||||
# include "nsString.h"
|
||||
#endif // defined(MOZILLA_INTERNAL_API)
|
||||
|
||||
// The declarations within this #if block are intended to be used for initial
|
||||
// process initialization ONLY. You probably don't want to be using these in
|
||||
|
@ -40,10 +35,6 @@ extern "C" {
|
|||
# define STATUS_DLL_NOT_FOUND ((NTSTATUS)0xC0000135L)
|
||||
# endif // !defined(STATUS_DLL_NOT_FOUND)
|
||||
|
||||
# if !defined(STATUS_UNSUCCESSFUL)
|
||||
# define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L)
|
||||
# endif // !defined(STATUS_UNSUCCESSFUL)
|
||||
|
||||
enum SECTION_INHERIT { ViewShare = 1, ViewUnmap = 2 };
|
||||
|
||||
NTSTATUS NTAPI NtMapViewOfSection(
|
||||
|
@ -79,29 +70,6 @@ BOOLEAN NTAPI RtlEqualUnicodeString(PCUNICODE_STRING aStr1,
|
|||
|
||||
NTSTATUS NTAPI RtlGetVersion(PRTL_OSVERSIONINFOW aOutVersionInformation);
|
||||
|
||||
VOID NTAPI RtlAcquireSRWLockExclusive(PSRWLOCK aLock);
|
||||
VOID NTAPI RtlAcquireSRWLockShared(PSRWLOCK aLock);
|
||||
|
||||
VOID NTAPI RtlReleaseSRWLockExclusive(PSRWLOCK aLock);
|
||||
VOID NTAPI RtlReleaseSRWLockShared(PSRWLOCK aLock);
|
||||
|
||||
NTSTATUS NTAPI NtReadVirtualMemory(HANDLE aProcessHandle, PVOID aBaseAddress,
|
||||
PVOID aBuffer, SIZE_T aNumBytesToRead,
|
||||
PSIZE_T aNumBytesRead);
|
||||
|
||||
NTSTATUS NTAPI LdrLoadDll(PWCHAR aDllPath, PULONG aFlags,
|
||||
PUNICODE_STRING aDllName, PHANDLE aOutHandle);
|
||||
|
||||
typedef ULONG(NTAPI* PRTL_RUN_ONCE_INIT_FN)(PRTL_RUN_ONCE, PVOID, PVOID*);
|
||||
NTSTATUS NTAPI RtlRunOnceExecuteOnce(PRTL_RUN_ONCE aRunOnce,
|
||||
PRTL_RUN_ONCE_INIT_FN aInitFn,
|
||||
PVOID aContext, PVOID* aParameter);
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // !defined(MOZILLA_INTERNAL_API)
|
||||
|
||||
extern "C" {
|
||||
PVOID NTAPI RtlAllocateHeap(PVOID aHeapHandle, ULONG aFlags, SIZE_T aSize);
|
||||
|
||||
PVOID NTAPI RtlReAllocateHeap(PVOID aHeapHandle, ULONG aFlags, LPVOID aMem,
|
||||
|
@ -109,128 +77,21 @@ PVOID NTAPI RtlReAllocateHeap(PVOID aHeapHandle, ULONG aFlags, LPVOID aMem,
|
|||
|
||||
BOOLEAN NTAPI RtlFreeHeap(PVOID aHeapHandle, ULONG aFlags, PVOID aHeapBase);
|
||||
|
||||
BOOLEAN NTAPI RtlQueryPerformanceCounter(LARGE_INTEGER* aPerfCount);
|
||||
VOID NTAPI RtlAcquireSRWLockExclusive(PSRWLOCK aLock);
|
||||
|
||||
#define RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE 1
|
||||
#define RTL_DUPLICATE_UNICODE_STRING_ALLOCATE_NULL_STRING 2
|
||||
NTSTATUS NTAPI RtlDuplicateUnicodeString(ULONG aFlags, PCUNICODE_STRING aSrc,
|
||||
PUNICODE_STRING aDest);
|
||||
VOID NTAPI RtlReleaseSRWLockExclusive(PSRWLOCK aLock);
|
||||
|
||||
NTSTATUS NTAPI NtReadVirtualMemory(HANDLE aProcessHandle, PVOID aBaseAddress,
|
||||
PVOID aBuffer, SIZE_T aNumBytesToRead,
|
||||
PSIZE_T aNumBytesRead);
|
||||
|
||||
VOID NTAPI RtlFreeUnicodeString(PUNICODE_STRING aUnicodeString);
|
||||
} // extern "C"
|
||||
|
||||
#endif // !defined(MOZILLA_INTERNAL_API)
|
||||
|
||||
namespace mozilla {
|
||||
namespace nt {
|
||||
|
||||
/**
|
||||
* This class encapsulates a UNICODE_STRING that owns its own buffer. The
|
||||
* buffer is always NULL terminated, thus allowing us to cast to a wide C-string
|
||||
* without requiring any mutation.
|
||||
*
|
||||
* We only allow creation of this owned buffer from outside XUL.
|
||||
*/
|
||||
class AllocatedUnicodeString final {
|
||||
public:
|
||||
AllocatedUnicodeString() : mUnicodeString() {}
|
||||
|
||||
#if defined(MOZILLA_INTERNAL_API)
|
||||
AllocatedUnicodeString(const AllocatedUnicodeString& aOther) = delete;
|
||||
|
||||
AllocatedUnicodeString& operator=(const AllocatedUnicodeString& aOther) =
|
||||
delete;
|
||||
#else
|
||||
explicit AllocatedUnicodeString(PCUNICODE_STRING aSrc) {
|
||||
if (!aSrc) {
|
||||
mUnicodeString = {};
|
||||
return;
|
||||
}
|
||||
|
||||
Duplicate(aSrc);
|
||||
}
|
||||
|
||||
AllocatedUnicodeString(const AllocatedUnicodeString& aOther) {
|
||||
Duplicate(&aOther.mUnicodeString);
|
||||
}
|
||||
|
||||
AllocatedUnicodeString& operator=(const AllocatedUnicodeString& aOther) {
|
||||
Clear();
|
||||
Duplicate(&aOther.mUnicodeString);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AllocatedUnicodeString& operator=(PCUNICODE_STRING aSrc) {
|
||||
Clear();
|
||||
Duplicate(aSrc);
|
||||
return *this;
|
||||
}
|
||||
#endif // defined(MOZILLA_INTERNAL_API)
|
||||
|
||||
AllocatedUnicodeString(AllocatedUnicodeString&& aOther)
|
||||
: mUnicodeString(aOther.mUnicodeString) {
|
||||
aOther.mUnicodeString = {};
|
||||
}
|
||||
|
||||
AllocatedUnicodeString& operator=(AllocatedUnicodeString&& aOther) {
|
||||
Clear();
|
||||
mUnicodeString = aOther.mUnicodeString;
|
||||
aOther.mUnicodeString = {};
|
||||
return *this;
|
||||
}
|
||||
|
||||
~AllocatedUnicodeString() { Clear(); }
|
||||
|
||||
bool IsEmpty() const {
|
||||
return !mUnicodeString.Buffer || !mUnicodeString.Length;
|
||||
}
|
||||
|
||||
operator PCUNICODE_STRING() const { return &mUnicodeString; }
|
||||
|
||||
operator const WCHAR*() const { return mUnicodeString.Buffer; }
|
||||
|
||||
USHORT CharLen() const { return mUnicodeString.Length / sizeof(WCHAR); }
|
||||
|
||||
#if defined(MOZILLA_INTERNAL_API)
|
||||
nsDependentString AsString() const {
|
||||
if (!mUnicodeString.Buffer) {
|
||||
return nsDependentString();
|
||||
}
|
||||
|
||||
// We can use nsDependentString here as we guaranteed null termination
|
||||
// when we allocated the string.
|
||||
return nsDependentString(mUnicodeString.Buffer, CharLen());
|
||||
}
|
||||
#endif // defined(MOZILLA_INTERNAL_API)
|
||||
|
||||
private:
|
||||
#if !defined(MOZILLA_INTERNAL_API)
|
||||
void Duplicate(PCUNICODE_STRING aSrc) {
|
||||
MOZ_ASSERT(aSrc);
|
||||
|
||||
// We duplicate with null termination so that this string may be used
|
||||
// as a wide C-string without any further manipulation.
|
||||
NTSTATUS ntStatus = ::RtlDuplicateUnicodeString(
|
||||
RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE, aSrc, &mUnicodeString);
|
||||
MOZ_ASSERT(NT_SUCCESS(ntStatus));
|
||||
if (!NT_SUCCESS(ntStatus)) {
|
||||
// Make sure that mUnicodeString does not contain bogus data
|
||||
// (since not all callers zero it out before invoking)
|
||||
mUnicodeString = {};
|
||||
}
|
||||
}
|
||||
#endif // !defined(MOZILLA_INTERNAL_API)
|
||||
|
||||
void Clear() {
|
||||
if (!mUnicodeString.Buffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
::RtlFreeUnicodeString(&mUnicodeString);
|
||||
mUnicodeString = {};
|
||||
}
|
||||
|
||||
UNICODE_STRING mUnicodeString;
|
||||
};
|
||||
|
||||
#if !defined(MOZILLA_INTERNAL_API)
|
||||
|
||||
struct MemorySectionNameBuf : public _MEMORY_SECTION_NAME {
|
||||
|
@ -240,8 +101,7 @@ struct MemorySectionNameBuf : public _MEMORY_SECTION_NAME {
|
|||
mSectionFileName.Buffer = mBuf;
|
||||
}
|
||||
|
||||
// Native NT paths, so we can't assume MAX_PATH. Use a larger buffer.
|
||||
WCHAR mBuf[2 * MAX_PATH];
|
||||
WCHAR mBuf[MAX_PATH];
|
||||
};
|
||||
|
||||
inline bool FindCharInUnicodeString(const UNICODE_STRING& aStr, WCHAR aChar,
|
||||
|
@ -782,13 +642,6 @@ inline HANDLE RtlGetProcessHeap() {
|
|||
return peb->Reserved4[1];
|
||||
}
|
||||
|
||||
inline DWORD RtlGetCurrentThreadId() {
|
||||
PTEB teb = ::NtCurrentTeb();
|
||||
CLIENT_ID* cid = reinterpret_cast<CLIENT_ID*>(&teb->Reserved1[8]);
|
||||
return static_cast<DWORD>(reinterpret_cast<uintptr_t>(cid->UniqueThread) &
|
||||
0xFFFFFFFFUL);
|
||||
}
|
||||
|
||||
inline LauncherResult<DWORD> GetParentProcessId() {
|
||||
struct PROCESS_BASIC_INFORMATION {
|
||||
NTSTATUS ExitStatus;
|
||||
|
@ -889,122 +742,6 @@ inline LauncherResult<HMODULE> GetProcessExeModule(HANDLE aProcess) {
|
|||
return static_cast<HMODULE>(baseAddress);
|
||||
}
|
||||
|
||||
#if !defined(MOZILLA_INTERNAL_API)
|
||||
|
||||
class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS SRWLock final {
|
||||
public:
|
||||
constexpr SRWLock() : mLock(SRWLOCK_INIT) {}
|
||||
|
||||
void LockShared() { ::RtlAcquireSRWLockShared(&mLock); }
|
||||
|
||||
void LockExclusive() { ::RtlAcquireSRWLockExclusive(&mLock); }
|
||||
|
||||
void UnlockShared() { ::RtlReleaseSRWLockShared(&mLock); }
|
||||
|
||||
void UnlockExclusive() { ::RtlReleaseSRWLockExclusive(&mLock); }
|
||||
|
||||
SRWLock(const SRWLock&) = delete;
|
||||
SRWLock(SRWLock&&) = delete;
|
||||
SRWLock& operator=(const SRWLock&) = delete;
|
||||
SRWLock& operator=(SRWLock&&) = delete;
|
||||
|
||||
SRWLOCK* operator&() { return &mLock; }
|
||||
|
||||
private:
|
||||
SRWLOCK mLock;
|
||||
};
|
||||
|
||||
class MOZ_RAII AutoExclusiveLock final {
|
||||
public:
|
||||
explicit AutoExclusiveLock(SRWLock& aLock) : mLock(aLock) {
|
||||
aLock.LockExclusive();
|
||||
}
|
||||
|
||||
~AutoExclusiveLock() { mLock.UnlockExclusive(); }
|
||||
|
||||
AutoExclusiveLock(const AutoExclusiveLock&) = delete;
|
||||
AutoExclusiveLock(AutoExclusiveLock&&) = delete;
|
||||
AutoExclusiveLock& operator=(const AutoExclusiveLock&) = delete;
|
||||
AutoExclusiveLock& operator=(AutoExclusiveLock&&) = delete;
|
||||
|
||||
private:
|
||||
SRWLock& mLock;
|
||||
};
|
||||
|
||||
class MOZ_RAII AutoSharedLock final {
|
||||
public:
|
||||
explicit AutoSharedLock(SRWLock& aLock) : mLock(aLock) { aLock.LockShared(); }
|
||||
|
||||
~AutoSharedLock() { mLock.UnlockShared(); }
|
||||
|
||||
AutoSharedLock(const AutoSharedLock&) = delete;
|
||||
AutoSharedLock(AutoSharedLock&&) = delete;
|
||||
AutoSharedLock& operator=(const AutoSharedLock&) = delete;
|
||||
AutoSharedLock& operator=(AutoSharedLock&&) = delete;
|
||||
|
||||
private:
|
||||
SRWLock& mLock;
|
||||
};
|
||||
|
||||
#endif // !defined(MOZILLA_INTERNAL_API)
|
||||
|
||||
class RtlAllocPolicy {
|
||||
public:
|
||||
template <typename T>
|
||||
T* maybe_pod_malloc(size_t aNumElems) {
|
||||
if (aNumElems & mozilla::tl::MulOverflowMask<sizeof(T)>::value) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return static_cast<T*>(
|
||||
::RtlAllocateHeap(RtlGetProcessHeap(), 0, aNumElems * sizeof(T)));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* maybe_pod_calloc(size_t aNumElems) {
|
||||
if (aNumElems & mozilla::tl::MulOverflowMask<sizeof(T)>::value) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return static_cast<T*>(::RtlAllocateHeap(
|
||||
RtlGetProcessHeap(), HEAP_ZERO_MEMORY, aNumElems * sizeof(T)));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* maybe_pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize) {
|
||||
if (aNewSize & mozilla::tl::MulOverflowMask<sizeof(T)>::value) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return static_cast<T*>(::RtlReAllocateHeap(RtlGetProcessHeap(), 0, aPtr,
|
||||
aNewSize * sizeof(T)));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* pod_malloc(size_t aNumElems) {
|
||||
return maybe_pod_malloc<T>(aNumElems);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* pod_calloc(size_t aNumElems) {
|
||||
return maybe_pod_calloc<T>(aNumElems);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize) {
|
||||
return maybe_pod_realloc<T>(aPtr, aOldSize, aNewSize);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void free_(T* aPtr, size_t aNumElems = 0) {
|
||||
::RtlFreeHeap(RtlGetProcessHeap(), 0, aPtr);
|
||||
}
|
||||
|
||||
void reportAllocOverflow() const {}
|
||||
|
||||
MOZ_MUST_USE bool checkSimulatedOOM() const { return true; }
|
||||
};
|
||||
|
||||
} // namespace nt
|
||||
} // namespace mozilla
|
||||
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
/* -*- 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 "WindowsUnicode.h"
|
||||
|
||||
#include <windows.h>
|
||||
// For UNICODE_STRING
|
||||
#include <winternl.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
namespace mozilla {
|
||||
namespace glue {
|
||||
|
||||
mozilla::UniquePtr<char[]> WideToUTF8(const wchar_t* aStr,
|
||||
const size_t aStrLenExclNul) {
|
||||
int numConv = ::WideCharToMultiByte(CP_UTF8, 0, aStr, aStrLenExclNul, nullptr,
|
||||
0, nullptr, nullptr);
|
||||
if (!numConv) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Include room for the null terminator by adding one
|
||||
auto buf = mozilla::MakeUnique<char[]>(numConv + 1);
|
||||
|
||||
numConv = ::WideCharToMultiByte(CP_UTF8, 0, aStr, aStrLenExclNul, buf.get(),
|
||||
numConv, nullptr, nullptr);
|
||||
if (!numConv) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Add null termination. numConv does not include the terminator, so we don't
|
||||
// subtract 1 when indexing into buf.
|
||||
buf[numConv] = 0;
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
mozilla::UniquePtr<char[]> WideToUTF8(const wchar_t* aStr) {
|
||||
return WideToUTF8(aStr, wcslen(aStr));
|
||||
}
|
||||
|
||||
mozilla::UniquePtr<char[]> WideToUTF8(const std::wstring& aStr) {
|
||||
return WideToUTF8(aStr.data(), aStr.length());
|
||||
}
|
||||
|
||||
mozilla::UniquePtr<char[]> WideToUTF8(PCUNICODE_STRING aStr) {
|
||||
if (!aStr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return WideToUTF8(aStr->Buffer, aStr->Length / sizeof(WCHAR));
|
||||
}
|
||||
|
||||
} // namespace glue
|
||||
} // namespace mozilla
|
|
@ -1,35 +0,0 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_glue_WindowsUnicode_h
|
||||
#define mozilla_glue_WindowsUnicode_h
|
||||
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
struct _UNICODE_STRING;
|
||||
|
||||
namespace mozilla {
|
||||
namespace glue {
|
||||
|
||||
mozilla::UniquePtr<char[]> WideToUTF8(const wchar_t* aStr,
|
||||
const size_t aStrLenExclNul);
|
||||
|
||||
mozilla::UniquePtr<char[]> WideToUTF8(const wchar_t* aStr);
|
||||
mozilla::UniquePtr<char[]> WideToUTF8(const std::wstring& aStr);
|
||||
mozilla::UniquePtr<char[]> WideToUTF8(const _UNICODE_STRING* aStr);
|
||||
|
||||
#if defined(bstr_t)
|
||||
inline mozilla::UniquePtr<char[]> WideToUTF8(const _bstr_t& aStr) {
|
||||
return WideToUTF8(static_cast<const wchar_t*>(aStr), aStr.length());
|
||||
}
|
||||
#endif // defined(bstr_t)
|
||||
|
||||
} // namespace glue
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_glue_WindowsUnicode_h
|
|
@ -18,7 +18,6 @@ EXPORTS.mozilla += [
|
|||
|
||||
EXPORTS.mozilla.glue += [
|
||||
'Debug.h',
|
||||
'WinUtils.h',
|
||||
]
|
||||
|
||||
if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
|
@ -50,14 +49,10 @@ if CONFIG['OS_ARCH'] == 'WINNT':
|
|||
'WindowsMapRemoteView.h',
|
||||
'WindowsProcessMitigations.h',
|
||||
]
|
||||
EXPORTS.mozilla.glue += [
|
||||
'WindowsUnicode.h',
|
||||
]
|
||||
SOURCES += [
|
||||
'TimeStamp_windows.cpp',
|
||||
'WindowsMapRemoteView.cpp',
|
||||
'WindowsProcessMitigations.cpp',
|
||||
'WindowsUnicode.cpp',
|
||||
]
|
||||
OS_LIBS += ['dbghelp']
|
||||
elif CONFIG['HAVE_CLOCK_MONOTONIC']:
|
||||
|
|
|
@ -13,9 +13,6 @@ if CONFIG['MOZ_LINKER']:
|
|||
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
|
||||
DIRS += ['android']
|
||||
|
||||
if CONFIG['OS_TARGET'] == 'WINNT':
|
||||
DIRS += ['dllservices']
|
||||
|
||||
DIRS += [
|
||||
'baseprofiler',
|
||||
'build',
|
||||
|
|
|
@ -93,11 +93,6 @@ int main(int argc, char* argv[]) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
if (RtlGetCurrentThreadId() != ::GetCurrentThreadId()) {
|
||||
printf("TEST-FAILED | NativeNt | RtlGetCurrentThreadId() is broken\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const wchar_t kKernel32[] = L"kernel32.dll";
|
||||
DWORD verInfoSize = ::GetFileVersionInfoSizeW(kKernel32, nullptr);
|
||||
if (!verInfoSize) {
|
||||
|
@ -173,6 +168,5 @@ int main(int argc, char* argv[]) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
printf("TEST-PASS | NativeNt | All tests ran successfully\n");
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1206,7 +1206,7 @@ class GeneratedFile(ContextDerived):
|
|||
self.localized = localized
|
||||
self.force = force
|
||||
|
||||
suffixes = [
|
||||
suffixes = (
|
||||
'.h',
|
||||
'.inc',
|
||||
'.py',
|
||||
|
@ -1216,17 +1216,7 @@ class GeneratedFile(ContextDerived):
|
|||
'android_apks',
|
||||
'.profdata',
|
||||
'.webidl'
|
||||
]
|
||||
|
||||
try:
|
||||
lib_suffix = context.config.substs['LIB_SUFFIX']
|
||||
suffixes += lib_suffix
|
||||
except KeyError:
|
||||
# Tests may not define LIB_SUFFIX
|
||||
pass
|
||||
|
||||
suffixes = tuple(suffixes)
|
||||
|
||||
)
|
||||
self.required_before_compile = [
|
||||
f for f in self.outputs if f.endswith(suffixes) or 'stl_wrappers/' in f]
|
||||
|
||||
|
|
|
@ -1,128 +0,0 @@
|
|||
|
||||
"third-party-modules" ping
|
||||
==========================
|
||||
|
||||
This ping contains information about events whereby third-party modules
|
||||
were loaded into Firefox processes.
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
{
|
||||
"type": "third-party-modules",
|
||||
... common ping data
|
||||
"clientId": <UUID>,
|
||||
"environment": { ... },
|
||||
"payload": {
|
||||
"structVersion": 1,
|
||||
"modules" [
|
||||
{
|
||||
// The sanitized name of the module as resolved by the Windows loader.
|
||||
"resolvedDllName": <string>,
|
||||
// Version of the DLL as contained in its resources's fixed version information.
|
||||
"fileVersion": <string>,
|
||||
// The value of the CompanyName field as extracted from the DLL's version information. This property is only present when such version info is present, and when the 'signedBy' property is absent.
|
||||
"companyName": <string>,
|
||||
// The organization whose certificate was used to sign the DLL. Only present for signed modules.
|
||||
"signedBy": <string>,
|
||||
// Flags that indicate this module's level of trustworthiness. This corresponds to one or more mozilla::ModuleTrustFlags OR'd together.
|
||||
"trustFlags": <unsigned int>
|
||||
},
|
||||
... Additional modules (maximum 100)
|
||||
],
|
||||
"processes": {
|
||||
<string containing pid, formatted as "0x%x">: {
|
||||
// Except for Default (which is remapped to "browser"), one of the process string names specified in xpcom/build/GeckoProcessTypes.h.
|
||||
"processType": <string>,
|
||||
// Elapsed time since process creation that this object was generated, in seconds.
|
||||
"elapsed": <number>,
|
||||
// Time spent loading xul.dll in milliseconds.
|
||||
"xulLoadDurationMS": <number>,
|
||||
// Number of dropped events due to failures sanitizing file paths.
|
||||
"sanitizationFailures": <int>,
|
||||
// Number of dropped events due to failures computing trust levels.
|
||||
"trustTestFailures": <int>,
|
||||
// Array of module load events for this process. The entries of this array are ordered to be in sync with the combinedStacks.stacks array (see below)
|
||||
"events": [
|
||||
{
|
||||
// Elapsed time since process creation that this event was generated, in milliseconds.
|
||||
"processUptimeMS": <int>,
|
||||
// Time spent loading this module, in milliseconds.
|
||||
"loadDurationMS": <number>,
|
||||
// Thread ID for the thread that loaded the module.
|
||||
"threadID": <int>,
|
||||
// Name of the thread that loaded the module, when applicable.
|
||||
"threadName": <string>,
|
||||
// The sanitized name of the module that was requested by the invoking code. Only exists when it is different from resolvedDllName.
|
||||
"requestedDllName": <string>,
|
||||
// The base address to which the loader mapped the module.
|
||||
"baseAddress": <string formatted as "0x%x">,
|
||||
// Index of the element in the modules array that contains details about the module that was loaded during this event.
|
||||
"moduleIndex": <int>
|
||||
},
|
||||
... Additional events (maximum 50)
|
||||
],
|
||||
"combinedStacks": [
|
||||
"memoryMap": [
|
||||
[
|
||||
// Name of the module symbol file, e.g. ``xul.pdb``
|
||||
<string>,
|
||||
// Breakpad identifier of the module, e.g. ``08A541B5942242BDB4AEABD8C87E4CFF2``
|
||||
<string>
|
||||
],
|
||||
... Additional modules
|
||||
],
|
||||
// Array of stacks for this process. These entries are ordered to be in sync with the events array
|
||||
"stacks": [
|
||||
[
|
||||
[
|
||||
// The module index or -1 for invalid module indices
|
||||
<integer>,
|
||||
// The program counter relative to its module base, or an absolute pc if the module index is -1
|
||||
<unsigned integer>
|
||||
],
|
||||
... Additional stack frames (maximum 512)
|
||||
],
|
||||
... Additional stack traces (maximum 50)
|
||||
]
|
||||
]
|
||||
},
|
||||
... Additional processes (maximum 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
payload.processes[...].events[...].resolvedDllName
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
The effective path to the module file, sanitized to remove any potentially
|
||||
sensitive information. In most cases, the directory path is removed leaving only
|
||||
the leaf name, e.g. ``foo.dll``. There are three exceptions:
|
||||
|
||||
* Paths under ``%ProgramFiles%`` are preserved, e.g. ``%ProgramFiles%\FooApplication\foo.dll``
|
||||
* Paths under ``%SystemRoot%`` are preserved, e.g. ``%SystemRoot%\System32\DriverStore\FileRepository\nvlt.inf_amd64_97992900c592012e\nvinitx.dll``
|
||||
* Paths under the temporary path are preserved, e.g. ``%TEMP%\bin\foo.dll``
|
||||
|
||||
payload.processes[...].events[...].requestedDllName
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
The name of the module as it was requested from the OS. This string is also
|
||||
sanitized in a similar fashion to to ``resolvedDllName``. This string is
|
||||
omitted from the ping when it is identical to ``resolvedDllName``.
|
||||
|
||||
Notes
|
||||
~~~~~
|
||||
* The client id is submitted with this ping.
|
||||
* The :doc:`Telemetry Environment <../data/environment>` is submitted in this ping.
|
||||
* String fields within ``payload`` are limited in length to 260 characters.
|
||||
* This ping is sent once daily.
|
||||
* If there are no events to report, this ping is not sent.
|
||||
|
||||
Version History
|
||||
~~~~~~~~~~~~~~~
|
||||
- Firefox 71:
|
||||
- Renamed from untrustedModules to third-party-modules with a revised schema (`bug 1542830 <https://bugzilla.mozilla.org/show_bug.cgi?id=1542830>`_).
|
||||
- Firefox 70:
|
||||
- Added ``%SystemRoot%`` as an exemption to path sanitization (`bug 1573275 <https://bugzilla.mozilla.org/show_bug.cgi?id=1573275>`_).
|
||||
- Firefox 66:
|
||||
- Added Windows Side-by-side directory trust flag (`bug 1514694 <https://bugzilla.mozilla.org/show_bug.cgi?id=1514694>`_).
|
||||
- Added module load times (``xulLoadDurationMS``, ``loadDurationMS``) and xul.dll trust flag (`bug 1518490 <https://bugzilla.mozilla.org/show_bug.cgi?id=1518490>`_).
|
||||
- Added SysWOW64 trust flag (`bug 1518798 <https://bugzilla.mozilla.org/show_bug.cgi?id=1518798>`_).
|
||||
- Firefox 65: Initial support (`bug 1435827 <https://bugzilla.mozilla.org/show_bug.cgi?id=1435827>`_).
|
|
@ -0,0 +1,142 @@
|
|||
|
||||
"untrustedModules" ping
|
||||
=======================
|
||||
|
||||
This ping contains information about events whereby untrusted modules
|
||||
were loaded into the Firefox process.
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
{
|
||||
"type": "untrustedModules",
|
||||
... common ping data
|
||||
"clientId": <UUID>,
|
||||
"environment": { ... },
|
||||
"payload": {
|
||||
"structVersion": <number>, // See below
|
||||
"xulLoadDurationMS": <number>, // Time spent loading xul.dll, in milliseconds (introduced in Firefox 66)
|
||||
"errorModules": <number>, // Number of modules that failed to be evaluated
|
||||
"events": [ ... ]
|
||||
"combinedStacks": { ... },
|
||||
}
|
||||
}
|
||||
|
||||
payload.structVersion
|
||||
---------------------
|
||||
Version of this payload structure. Reserved for future use; this is currently
|
||||
always ``1``.
|
||||
|
||||
payload.events
|
||||
--------------
|
||||
An array, each element representing the load of an untrusted module. Because
|
||||
modules can invoke the load of other modules, there may be
|
||||
multiple modules listed per event.
|
||||
|
||||
This array is synchronized with the ``payload.combinedStacks`` object which
|
||||
contains the captured stack trace for each event.
|
||||
|
||||
A maximum of 50 events are captured.
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
{
|
||||
"processUptimeMS": <number>, // Number of milliseconds since app startup
|
||||
"isStartup": <boolean>, // See below
|
||||
"threadID": <unsigned integer>, // Thread ID where the event occurred
|
||||
"threadName": <string>, // If available, the name of the thread
|
||||
"modules": [
|
||||
{
|
||||
"moduleName": <string>, // See below
|
||||
"loaderName": <string>, // See below
|
||||
"loadDurationMS" : <number>, // Optional. Time spent loading this module, in milliseconds (introduced in Firefox 66)
|
||||
"baseAddress": <string>, // Base address where the module was loaded, e.g. "0x7ffc01260000"
|
||||
"fileVersion": <string>, // The module file version, e.g. "1.10.2.6502"
|
||||
"moduleTrustFlags": <unsigned integer> // See below
|
||||
},
|
||||
... Additional modules
|
||||
]
|
||||
}
|
||||
|
||||
payload.events[...].isStartup
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
* ``true`` if the event represents a module that was already loaded before monitoring was initiated.
|
||||
* ``false`` if the event was captured during normal execution.
|
||||
|
||||
payload.events[...].modules[...].moduleName
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
The path to the module file, modified to remove any potentially sensitive
|
||||
information. In most cases, the directory path is removed leaving only the
|
||||
file name, e.g. ``foo.dll``. There are three exceptions:
|
||||
|
||||
* Paths under ``%ProgramFiles%`` are preserved, e.g. ``%ProgramFiles%\FooApplication\foo.dll``
|
||||
* Paths under ``%SystemRoot%`` are preserved, e.g. ``%SystemRoot%\System32\DriverStore\FileRepository\nvlt.inf_amd64_97992900c592012e\nvinitx.dll``
|
||||
* Paths under the temporary path are preserved, e.g. ``%TEMP%\bin\foo.dll``
|
||||
|
||||
payload.events[...].modules[...].loaderName
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
The name of the module as it was requested from the OS. Generally this will be
|
||||
the same as the above ``moduleName``.
|
||||
|
||||
This is treated as a path and is modified for privacy in the same way as
|
||||
``moduleName`` above.
|
||||
|
||||
payload.events[...].modules[...].moduleTrustFlags
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
This is a bitfield indicating whether various attributes apply to the module.
|
||||
|
||||
* ``1`` if the module is digitally signed by Mozilla
|
||||
* ``2`` if the module is digitally signed by Microsoft
|
||||
* ``4`` if the module's version info indicates it's a Microsoft module
|
||||
* ``8`` if the module is located in the Firefox application directory
|
||||
* ``0x10`` if the module has the same location and version information as the Firefox executable
|
||||
* ``0x20`` if the module is located in the system directory
|
||||
* ``0x40`` if the module is a known keyboard layout DLL
|
||||
* ``0x80`` if the module is an internally-recognized JIT module
|
||||
* ``0x100`` if the module is located in the Windows Side-by-side directory (introduced in Firefox 66)
|
||||
* ``0x200`` if the module is the XPCOM module, xul.dll (introduced in Firefox 66)
|
||||
* ``0x400`` if the module is located in the SysWOW64 directory (introduced in Firefox 66)
|
||||
|
||||
payload.combinedStacks
|
||||
----------------------
|
||||
This object holds stack traces that correspond to events in ``payload.events``.
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
"combinedStacks": {
|
||||
"memoryMap": [
|
||||
[
|
||||
<string>, // Name of the module symbol file, e.g. ``xul.pdb``
|
||||
<string> // Breakpad identifier of the module, e.g. ``08A541B5942242BDB4AEABD8C87E4CFF2``
|
||||
],
|
||||
... Additional modules
|
||||
],
|
||||
"stacks": [
|
||||
[
|
||||
[
|
||||
<integer>, // The module index or -1 for invalid module indices
|
||||
<unsigned integer> // The program counter relative to its module base, or an absolute pc
|
||||
],
|
||||
... Additional stack frames (maximum 500)
|
||||
],
|
||||
... Additional stack traces (maximum 50)
|
||||
]
|
||||
},
|
||||
|
||||
Notes
|
||||
~~~~~
|
||||
* The client id is submitted with this ping.
|
||||
* The :doc:`Telemetry Environment <../data/environment>` is submitted in this ping.
|
||||
* String fields within ``payload`` are limited in length to 260 characters.
|
||||
* This ping is only enabled on Nightly builds of Firefox Desktop for Windows.
|
||||
* This ping is sent once daily.
|
||||
* Only events occurring on the main browser process are recorded.
|
||||
* If there are no events to report, this ping is not sent.
|
||||
|
||||
Version History
|
||||
~~~~~~~~~~~~~~~
|
||||
- Firefox 65: Initial support (`bug 1435827 <https://bugzilla.mozilla.org/show_bug.cgi?id=1435827>`_).
|
||||
- Firefox 66:
|
||||
|
||||
- Added Windows Side-by-side directory trust flag (`bug 1514694 <https://bugzilla.mozilla.org/show_bug.cgi?id=1514694>`_).
|
||||
- Added module load times (``xulLoadDurationMS``, ``loadDurationMS``) and xul.dll trust flag (`bug 1518490 <https://bugzilla.mozilla.org/show_bug.cgi?id=1518490>`_).
|
||||
- Added SysWOW64 trust flag (`bug 1518798 <https://bugzilla.mozilla.org/show_bug.cgi?id=1518798>`_).
|
|
@ -104,19 +104,6 @@ void CombinedStacks::RemoveStack(unsigned aIndex) {
|
|||
}
|
||||
}
|
||||
|
||||
void CombinedStacks::Swap(CombinedStacks& aOther) {
|
||||
mModules.swap(aOther.mModules);
|
||||
mStacks.swap(aOther.mStacks);
|
||||
|
||||
size_t nextIndex = aOther.mNextIndex;
|
||||
aOther.mNextIndex = mNextIndex;
|
||||
mNextIndex = nextIndex;
|
||||
|
||||
size_t maxStacksCount = aOther.mMaxStacksCount;
|
||||
aOther.mMaxStacksCount = mMaxStacksCount;
|
||||
mMaxStacksCount = maxStacksCount;
|
||||
}
|
||||
|
||||
#if defined(MOZ_GECKO_PROFILER)
|
||||
void CombinedStacks::Clear() {
|
||||
mNextIndex = 0;
|
||||
|
|
|
@ -25,11 +25,6 @@ class CombinedStacks {
|
|||
explicit CombinedStacks();
|
||||
explicit CombinedStacks(size_t aMaxStacksCount);
|
||||
|
||||
CombinedStacks(CombinedStacks&&) = default;
|
||||
CombinedStacks& operator=(CombinedStacks&&) = default;
|
||||
|
||||
void Swap(CombinedStacks& aOther);
|
||||
|
||||
typedef std::vector<Telemetry::ProcessedStack::Frame> Stack;
|
||||
const Telemetry::ProcessedStack::Module& GetModule(unsigned aIndex) const;
|
||||
size_t GetModuleCount() const;
|
||||
|
|
|
@ -76,19 +76,25 @@ BatchProcessedStackGenerator::BatchProcessedStackGenerator()
|
|||
#endif
|
||||
}
|
||||
|
||||
#ifndef MOZ_GECKO_PROFILER
|
||||
static ProcessedStack GetStackAndModulesInternal(
|
||||
std::vector<StackFrame>& aRawStack) {
|
||||
#else
|
||||
static ProcessedStack GetStackAndModulesInternal(
|
||||
std::vector<StackFrame>& aRawStack, SharedLibraryInfo& aSortedRawModules) {
|
||||
SharedLibraryInfo rawModules(aSortedRawModules);
|
||||
ProcessedStack BatchProcessedStackGenerator::GetStackAndModules(
|
||||
const std::vector<uintptr_t>& aPCs) {
|
||||
std::vector<StackFrame> rawStack;
|
||||
auto stackEnd = aPCs.begin() + std::min(aPCs.size(), kMaxChromeStackDepth);
|
||||
for (auto i = aPCs.begin(); i != stackEnd; ++i) {
|
||||
uintptr_t aPC = *i;
|
||||
StackFrame Frame = {aPC, static_cast<uint16_t>(rawStack.size()),
|
||||
std::numeric_limits<uint16_t>::max()};
|
||||
rawStack.push_back(Frame);
|
||||
}
|
||||
|
||||
#ifdef MOZ_GECKO_PROFILER
|
||||
SharedLibraryInfo rawModules(mSortedRawModules);
|
||||
// Remove all modules not referenced by a PC on the stack
|
||||
std::sort(aRawStack.begin(), aRawStack.end(), CompareByPC);
|
||||
std::sort(rawStack.begin(), rawStack.end(), CompareByPC);
|
||||
|
||||
size_t moduleIndex = 0;
|
||||
size_t stackIndex = 0;
|
||||
size_t stackSize = aRawStack.size();
|
||||
size_t stackSize = rawStack.size();
|
||||
|
||||
while (moduleIndex < rawModules.GetSize()) {
|
||||
const SharedLibrary& module = rawModules.GetEntry(moduleIndex);
|
||||
|
@ -98,20 +104,20 @@ static ProcessedStack GetStackAndModulesInternal(
|
|||
|
||||
bool moduleReferenced = false;
|
||||
for (; stackIndex < stackSize; ++stackIndex) {
|
||||
uintptr_t pc = aRawStack[stackIndex].mPC;
|
||||
uintptr_t pc = rawStack[stackIndex].mPC;
|
||||
if (pc >= moduleEnd) break;
|
||||
|
||||
if (pc >= moduleStart) {
|
||||
// If the current PC is within the current module, mark
|
||||
// module as used
|
||||
moduleReferenced = true;
|
||||
aRawStack[stackIndex].mPC -= moduleStart;
|
||||
aRawStack[stackIndex].mModIndex = moduleIndex;
|
||||
rawStack[stackIndex].mPC -= moduleStart;
|
||||
rawStack[stackIndex].mModIndex = moduleIndex;
|
||||
} else {
|
||||
// PC does not belong to any module. It is probably from
|
||||
// the JIT. Use a fixed mPC so that we don't get different
|
||||
// stacks on different runs.
|
||||
aRawStack[stackIndex].mPC = std::numeric_limits<uintptr_t>::max();
|
||||
rawStack[stackIndex].mPC = std::numeric_limits<uintptr_t>::max();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,15 +131,15 @@ static ProcessedStack GetStackAndModulesInternal(
|
|||
|
||||
for (; stackIndex < stackSize; ++stackIndex) {
|
||||
// These PCs are past the last module.
|
||||
aRawStack[stackIndex].mPC = std::numeric_limits<uintptr_t>::max();
|
||||
rawStack[stackIndex].mPC = std::numeric_limits<uintptr_t>::max();
|
||||
}
|
||||
|
||||
std::sort(aRawStack.begin(), aRawStack.end(), CompareByIndex);
|
||||
std::sort(rawStack.begin(), rawStack.end(), CompareByIndex);
|
||||
#endif
|
||||
|
||||
// Copy the information to the return value.
|
||||
ProcessedStack Ret;
|
||||
for (auto& rawFrame : aRawStack) {
|
||||
for (auto& rawFrame : rawStack) {
|
||||
mozilla::Telemetry::ProcessedStack::Frame frame = {rawFrame.mPC,
|
||||
rawFrame.mModIndex};
|
||||
Ret.AddFrame(frame);
|
||||
|
@ -151,40 +157,5 @@ static ProcessedStack GetStackAndModulesInternal(
|
|||
return Ret;
|
||||
}
|
||||
|
||||
ProcessedStack BatchProcessedStackGenerator::GetStackAndModules(
|
||||
const std::vector<uintptr_t>& aPCs) {
|
||||
std::vector<StackFrame> rawStack;
|
||||
auto stackEnd = aPCs.begin() + std::min(aPCs.size(), kMaxChromeStackDepth);
|
||||
for (auto i = aPCs.begin(); i != stackEnd; ++i) {
|
||||
uintptr_t aPC = *i;
|
||||
StackFrame Frame = {aPC, static_cast<uint16_t>(rawStack.size()),
|
||||
std::numeric_limits<uint16_t>::max()};
|
||||
rawStack.push_back(Frame);
|
||||
}
|
||||
|
||||
#if defined(MOZ_GECKO_PROFILER)
|
||||
return GetStackAndModulesInternal(rawStack, mSortedRawModules);
|
||||
#else
|
||||
return GetStackAndModulesInternal(rawStack);
|
||||
#endif
|
||||
}
|
||||
|
||||
ProcessedStack BatchProcessedStackGenerator::GetStackAndModules(
|
||||
const uintptr_t* aBegin, const uintptr_t* aEnd) {
|
||||
std::vector<StackFrame> rawStack;
|
||||
for (auto i = aBegin; i != aEnd; ++i) {
|
||||
uintptr_t aPC = *i;
|
||||
StackFrame Frame = {aPC, static_cast<uint16_t>(rawStack.size()),
|
||||
std::numeric_limits<uint16_t>::max()};
|
||||
rawStack.push_back(Frame);
|
||||
}
|
||||
|
||||
#if defined(MOZ_GECKO_PROFILER)
|
||||
return GetStackAndModulesInternal(rawStack, mSortedRawModules);
|
||||
#else
|
||||
return GetStackAndModulesInternal(rawStack);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Telemetry
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "mozilla/Vector.h"
|
||||
#include "nsString.h"
|
||||
#if defined(MOZ_GECKO_PROFILER)
|
||||
# include "shared-libraries.h"
|
||||
|
@ -66,16 +64,7 @@ class BatchProcessedStackGenerator {
|
|||
BatchProcessedStackGenerator();
|
||||
ProcessedStack GetStackAndModules(const std::vector<uintptr_t>& aPCs);
|
||||
|
||||
template <typename AllocatorPolicy>
|
||||
ProcessedStack GetStackAndModules(
|
||||
const Vector<void*, 0, AllocatorPolicy>& aPCs) {
|
||||
return GetStackAndModules(reinterpret_cast<const uintptr_t*>(aPCs.begin()),
|
||||
reinterpret_cast<const uintptr_t*>(aPCs.end()));
|
||||
}
|
||||
|
||||
private:
|
||||
ProcessedStack GetStackAndModules(const uintptr_t* aBegin,
|
||||
const uintptr_t* aEnd);
|
||||
#if defined(MOZ_GECKO_PROFILER)
|
||||
SharedLibraryInfo mSortedRawModules;
|
||||
#endif
|
||||
|
|
|
@ -7,53 +7,40 @@
|
|||
#include "UntrustedModules.h"
|
||||
|
||||
#include "core/TelemetryCommon.h"
|
||||
#include "mozilla/UntrustedModulesProcessor.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/WinDllServices.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsDataHashtable.h"
|
||||
#include "nsLocalFile.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "nsProxyRelease.h"
|
||||
#include "nsUnicharUtils.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
|
||||
namespace {
|
||||
using IndexMap = nsDataHashtable<nsStringHashKey, uint32_t>;
|
||||
} // anonymous namespace
|
||||
#include "nsXPCOMCIDInternal.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace Telemetry {
|
||||
|
||||
static const uint32_t kThirdPartyModulesPingVersion = 1;
|
||||
static const uint32_t kMaxModulesArrayLen = 100;
|
||||
static const int32_t kUntrustedModuleLoadEventsTelemetryVersion = 1;
|
||||
|
||||
/**
|
||||
* Limits the length of a string by removing the middle of the string, replacing
|
||||
* with ellipsis.
|
||||
* with ellipses.
|
||||
* e.g. LimitStringLength("hello world", 6) would result in "he...d"
|
||||
*
|
||||
* @param aStr [in,out] The string to transform
|
||||
* @param aMaxFieldLength [in] The maximum length of the resulting string.
|
||||
* this must be long enough to hold the ellipses.
|
||||
*/
|
||||
static void LimitStringLength(nsAString& aStr, size_t aMaxFieldLength) {
|
||||
if (aStr.Length() <= aMaxFieldLength) {
|
||||
return;
|
||||
}
|
||||
|
||||
NS_NAMED_LITERAL_STRING(kEllipsis, "...");
|
||||
NS_NAMED_LITERAL_STRING(kEllipses, "...");
|
||||
|
||||
if (aMaxFieldLength <= (kEllipsis.Length() + 3)) {
|
||||
// An ellipsis is useless in this case, as it would obscure the string to
|
||||
// the point that we cannot even determine the string's contents. We might
|
||||
// as well just truncate.
|
||||
aStr.Truncate(aMaxFieldLength);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t cutPos = (aMaxFieldLength - kEllipsis.Length()) / 2;
|
||||
size_t rightLen = aMaxFieldLength - kEllipsis.Length() - cutPos;
|
||||
MOZ_ASSERT(aMaxFieldLength >= kEllipses.Length());
|
||||
size_t cutPos = (aMaxFieldLength - kEllipses.Length()) / 2;
|
||||
size_t rightLen = aMaxFieldLength - kEllipses.Length() - cutPos;
|
||||
size_t cutLen = aStr.Length() - (cutPos + rightLen);
|
||||
|
||||
aStr.Replace(cutPos, cutLen, kEllipsis);
|
||||
aStr.Replace(cutPos, cutLen, kEllipses);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,7 +57,7 @@ static void LimitStringLength(nsAString& aStr, size_t aMaxFieldLength) {
|
|||
*/
|
||||
static bool AddLengthLimitedStringProp(JSContext* cx, JS::HandleObject aObj,
|
||||
const char* aName, const nsAString& aVal,
|
||||
size_t aMaxFieldLength = MAX_PATH) {
|
||||
size_t aMaxFieldLength = 260) {
|
||||
JS::RootedValue jsval(cx);
|
||||
nsAutoString shortVal(aVal);
|
||||
LimitStringLength(shortVal, aMaxFieldLength);
|
||||
|
@ -78,26 +65,6 @@ static bool AddLengthLimitedStringProp(JSContext* cx, JS::HandleObject aObj,
|
|||
return JS_DefineProperty(cx, aObj, aName, jsval, JSPROP_ENUMERATE);
|
||||
};
|
||||
|
||||
static JSString* ModuleVersionToJSString(JSContext* aCx,
|
||||
const ModuleVersion& aVersion) {
|
||||
uint16_t major, minor, patch, build;
|
||||
|
||||
Tie(major, minor, patch, build) = aVersion.AsTuple();
|
||||
|
||||
NS_NAMED_LITERAL_STRING(dot, ".");
|
||||
|
||||
nsAutoString strVer;
|
||||
strVer.AppendInt(major);
|
||||
strVer.Append(dot);
|
||||
strVer.AppendInt(minor);
|
||||
strVer.Append(dot);
|
||||
strVer.AppendInt(patch);
|
||||
strVer.Append(dot);
|
||||
strVer.AppendInt(build);
|
||||
|
||||
return Common::ToJSString(aCx, strVer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given mozilla::Vector to a JavaScript array.
|
||||
*
|
||||
|
@ -112,20 +79,18 @@ static JSString* ModuleVersionToJSString(JSContext* aCx,
|
|||
* const ArrayElementT& aElement)
|
||||
* @return true if aRet was successfully assigned to the new array object.
|
||||
*/
|
||||
template <typename T, size_t N, typename AllocPolicy, typename Converter,
|
||||
typename... Args>
|
||||
template <typename T, size_t N, typename AllocPolicy, typename Converter>
|
||||
static bool VectorToJSArray(JSContext* cx, JS::MutableHandleObject aRet,
|
||||
const Vector<T, N, AllocPolicy>& aContainer,
|
||||
Converter&& aElementConverter, Args&&... aArgs) {
|
||||
Converter&& aElementConverter) {
|
||||
JS::RootedObject arr(cx, JS_NewArrayObject(cx, 0));
|
||||
if (!arr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0, l = aContainer.length(); i < l; ++i) {
|
||||
for (size_t i = 0; i < aContainer.length(); ++i) {
|
||||
JS::RootedValue jsel(cx);
|
||||
if (!aElementConverter(cx, &jsel, aContainer[i],
|
||||
std::forward<Args>(aArgs)...)) {
|
||||
if (!aElementConverter(cx, &jsel, aContainer[i])) {
|
||||
return false;
|
||||
}
|
||||
if (!JS_DefineElement(cx, arr, i, jsel, JSPROP_ENUMERATE)) {
|
||||
|
@ -137,362 +102,271 @@ static bool VectorToJSArray(JSContext* cx, JS::MutableHandleObject aRet,
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool SerializeModule(JSContext* aCx, JS::MutableHandleValue aElement,
|
||||
const RefPtr<ModuleRecord>& aModule) {
|
||||
if (!aModule) {
|
||||
/**
|
||||
* Converts a ModuleLoadEvent::ModuleInfo to a JS object.
|
||||
*
|
||||
* @param cx [in] The JS context.
|
||||
* @param aRet [out] This gets assigned to the newly created object.
|
||||
* @param aModInfo [in] The source object to convert.
|
||||
* @return true if aRet was successfully assigned.
|
||||
*/
|
||||
static bool ModuleInfoToJSObj(JSContext* cx, JS::MutableHandleObject aRet,
|
||||
const ModuleLoadEvent::ModuleInfo& aModInfo) {
|
||||
JS::RootedObject modObj(cx, JS_NewObject(cx, nullptr));
|
||||
if (!modObj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JS::RootedObject obj(aCx, JS_NewPlainObject(aCx));
|
||||
if (!obj) {
|
||||
JS::RootedValue jsval(cx);
|
||||
|
||||
nsPrintfCString strBaseAddress("0x%p", (void*)aModInfo.mBase);
|
||||
jsval.setString(Common::ToJSString(cx, strBaseAddress));
|
||||
if (!JS_DefineProperty(cx, modObj, "baseAddress", jsval, JSPROP_ENUMERATE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!AddLengthLimitedStringProp(aCx, obj, "resolvedDllName",
|
||||
aModule->mSanitizedDllName)) {
|
||||
jsval.setString(Common::ToJSString(cx, aModInfo.mFileVersion));
|
||||
if (!JS_DefineProperty(cx, modObj, "fileVersion", jsval, JSPROP_ENUMERATE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aModule->mVersion.isSome()) {
|
||||
JS::RootedValue jsModuleVersion(aCx);
|
||||
jsModuleVersion.setString(
|
||||
ModuleVersionToJSString(aCx, aModule->mVersion.ref()));
|
||||
if (!JS_DefineProperty(aCx, obj, "fileVersion", jsModuleVersion,
|
||||
if (!AddLengthLimitedStringProp(cx, modObj, "loaderName",
|
||||
aModInfo.mLdrName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!AddLengthLimitedStringProp(cx, modObj, "moduleName",
|
||||
aModInfo.mFilePathClean)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aModInfo.mLoadDurationMS.isSome()) {
|
||||
jsval.setNumber(aModInfo.mLoadDurationMS.value());
|
||||
if (!JS_DefineProperty(cx, modObj, "loadDurationMS", jsval,
|
||||
JSPROP_ENUMERATE)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (aModule->mVendorInfo.isSome()) {
|
||||
const char* propName;
|
||||
|
||||
const VendorInfo& vendorInfo = aModule->mVendorInfo.ref();
|
||||
switch (vendorInfo.mSource) {
|
||||
case VendorInfo::Source::Signature:
|
||||
propName = "signedBy";
|
||||
break;
|
||||
case VendorInfo::Source::VersionInfo:
|
||||
propName = "companyName";
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Unknown VendorInfo Source!");
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!vendorInfo.mVendor.IsEmpty());
|
||||
if (vendorInfo.mVendor.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!AddLengthLimitedStringProp(aCx, obj, propName, vendorInfo.mVendor)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
JS::RootedValue jsTrustFlags(aCx);
|
||||
jsTrustFlags.setNumber(static_cast<uint32_t>(aModule->mTrustFlags));
|
||||
if (!JS_DefineProperty(aCx, obj, "trustFlags", jsTrustFlags,
|
||||
jsval.setNumber((uint32_t)aModInfo.mTrustFlags);
|
||||
if (!JS_DefineProperty(cx, modObj, "moduleTrustFlags", jsval,
|
||||
JSPROP_ENUMERATE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
aElement.setObject(*obj);
|
||||
aRet.set(modObj);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool SerializeEvent(JSContext* aCx, JS::MutableHandleValue aElement,
|
||||
const ProcessedModuleLoadEvent& aEvent,
|
||||
const IndexMap& aModuleIndices) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
JS::RootedObject obj(aCx, JS_NewPlainObject(aCx));
|
||||
if (!obj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JS::RootedValue jsProcessUptimeMS(aCx);
|
||||
// Javascript doesn't like 64-bit integers; convert to double.
|
||||
jsProcessUptimeMS.setNumber(static_cast<double>(aEvent.mProcessUptimeMS));
|
||||
if (!JS_DefineProperty(aCx, obj, "processUptimeMS", jsProcessUptimeMS,
|
||||
JSPROP_ENUMERATE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aEvent.mLoadDurationMS) {
|
||||
JS::RootedValue jsLoadDurationMS(aCx);
|
||||
jsLoadDurationMS.setNumber(aEvent.mLoadDurationMS.value());
|
||||
if (!JS_DefineProperty(aCx, obj, "loadDurationMS", jsLoadDurationMS,
|
||||
JSPROP_ENUMERATE)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
JS::RootedValue jsThreadId(aCx);
|
||||
jsThreadId.setNumber(static_cast<uint32_t>(aEvent.mThreadId));
|
||||
if (!JS_DefineProperty(aCx, obj, "threadID", jsThreadId, JSPROP_ENUMERATE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsDependentCString effectiveThreadName;
|
||||
if (aEvent.mThreadId == ::GetCurrentThreadId()) {
|
||||
effectiveThreadName.Rebind(NS_LITERAL_CSTRING("Main Thread"), 0);
|
||||
} else {
|
||||
effectiveThreadName.Rebind(aEvent.mThreadName, 0);
|
||||
}
|
||||
|
||||
if (!effectiveThreadName.IsEmpty()) {
|
||||
JS::RootedValue jsThreadName(aCx);
|
||||
jsThreadName.setString(Common::ToJSString(aCx, effectiveThreadName));
|
||||
if (!JS_DefineProperty(aCx, obj, "threadName", jsThreadName,
|
||||
JSPROP_ENUMERATE)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't add this property unless mRequestedDllName differs from
|
||||
// the associated module's mSanitizedDllName
|
||||
if (!aEvent.mRequestedDllName.IsEmpty() &&
|
||||
!aEvent.mRequestedDllName.Equals(aEvent.mModule->mSanitizedDllName,
|
||||
nsCaseInsensitiveStringComparator())) {
|
||||
if (!AddLengthLimitedStringProp(aCx, obj, "requestedDllName",
|
||||
aEvent.mRequestedDllName)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
nsAutoString strBaseAddress;
|
||||
strBaseAddress.AppendLiteral(u"0x");
|
||||
strBaseAddress.AppendInt(aEvent.mBaseAddress, 16);
|
||||
|
||||
JS::RootedValue jsBaseAddress(aCx);
|
||||
jsBaseAddress.setString(Common::ToJSString(aCx, strBaseAddress));
|
||||
if (!JS_DefineProperty(aCx, obj, "baseAddress", jsBaseAddress,
|
||||
JSPROP_ENUMERATE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsAutoString resolvedDllPath;
|
||||
const nsCOMPtr<nsIFile>& resolvedDllName = aEvent.mModule->mResolvedDllName;
|
||||
if (!resolvedDllName ||
|
||||
NS_FAILED(resolvedDllName->GetPath(resolvedDllPath))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t index;
|
||||
if (!aModuleIndices.Get(resolvedDllPath, &index)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JS::RootedValue jsModuleIndex(aCx);
|
||||
jsModuleIndex.setNumber(index);
|
||||
if (!JS_DefineProperty(aCx, obj, "moduleIndex", jsModuleIndex,
|
||||
JSPROP_ENUMERATE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
aElement.setObject(*obj);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static nsresult GetPerProcObject(JSContext* aCx, const IndexMap& aModuleIndices,
|
||||
const UntrustedModulesData& aData,
|
||||
JS::MutableHandleObject aObj) {
|
||||
nsDependentCString strProcType;
|
||||
if (aData.mProcessType == GeckoProcessType_Default) {
|
||||
strProcType.Rebind(NS_LITERAL_CSTRING("browser"), 0);
|
||||
} else {
|
||||
strProcType.Rebind(XRE_ChildProcessTypeToString(aData.mProcessType));
|
||||
}
|
||||
|
||||
JS::RootedValue jsProcType(aCx);
|
||||
jsProcType.setString(Common::ToJSString(aCx, strProcType));
|
||||
if (!JS_DefineProperty(aCx, aObj, "processType", jsProcType,
|
||||
JSPROP_ENUMERATE)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
JS::RootedValue jsElapsed(aCx);
|
||||
jsElapsed.setNumber(aData.mElapsed.ToSecondsSigDigits());
|
||||
if (!JS_DefineProperty(aCx, aObj, "elapsed", jsElapsed, JSPROP_ENUMERATE)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (aData.mXULLoadDurationMS.isSome()) {
|
||||
JS::RootedValue jsXulLoadDurationMS(aCx);
|
||||
jsXulLoadDurationMS.setNumber(aData.mXULLoadDurationMS.value());
|
||||
if (!JS_DefineProperty(aCx, aObj, "xulLoadDurationMS", jsXulLoadDurationMS,
|
||||
JSPROP_ENUMERATE)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
JS::RootedValue jsSanitizationFailures(aCx);
|
||||
jsSanitizationFailures.setNumber(aData.mSanitizationFailures);
|
||||
if (!JS_DefineProperty(aCx, aObj, "sanitizationFailures",
|
||||
jsSanitizationFailures, JSPROP_ENUMERATE)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
JS::RootedValue jsTrustTestFailures(aCx);
|
||||
jsTrustTestFailures.setNumber(aData.mTrustTestFailures);
|
||||
if (!JS_DefineProperty(aCx, aObj, "trustTestFailures", jsTrustTestFailures,
|
||||
JSPROP_ENUMERATE)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
JS::RootedObject eventsArray(aCx);
|
||||
if (!VectorToJSArray(aCx, &eventsArray, aData.mEvents, &SerializeEvent,
|
||||
aModuleIndices)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (!JS_DefineProperty(aCx, aObj, "events", eventsArray, JSPROP_ENUMERATE)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
JS::RootedObject combinedStacksObj(aCx,
|
||||
CreateJSStackObject(aCx, aData.mStacks));
|
||||
if (!combinedStacksObj) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (!JS_DefineProperty(aCx, aObj, "combinedStacks", combinedStacksObj,
|
||||
JSPROP_ENUMERATE)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a UntrustedModulesData to a JS object.
|
||||
* Converts a ModuleLoadEvent object to a Javascript array
|
||||
*
|
||||
* @param cx [in] The JS context
|
||||
* @param aRet [out] Handle that receives the resulting array object
|
||||
* @param aEvent [in] The event to convert from
|
||||
* @return true upon success
|
||||
*/
|
||||
static bool ModuleLoadEventToJSArray(JSContext* cx, JS::MutableHandleValue aRet,
|
||||
const ModuleLoadEvent& aEvent) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
JS::RootedValue jsval(cx);
|
||||
JS::RootedObject eObj(cx, JS_NewObject(cx, nullptr));
|
||||
if (!eObj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
jsval.setNumber((uint32_t)aEvent.mThreadID);
|
||||
if (!JS_DefineProperty(cx, eObj, "threadID", jsval, JSPROP_ENUMERATE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
jsval.setBoolean(aEvent.mIsStartup);
|
||||
if (!JS_DefineProperty(cx, eObj, "isStartup", jsval, JSPROP_ENUMERATE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Javascript doesn't like 64-bit integers; convert to double.
|
||||
jsval.setNumber((double)aEvent.mProcessUptimeMS);
|
||||
if (!JS_DefineProperty(cx, eObj, "processUptimeMS", jsval,
|
||||
JSPROP_ENUMERATE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This function should always get called on the main thread
|
||||
if (::GetCurrentThreadId() == aEvent.mThreadID) {
|
||||
jsval.setString(Common::ToJSString(cx, NS_LITERAL_STRING("Main Thread")));
|
||||
} else {
|
||||
jsval.setString(Common::ToJSString(cx, aEvent.mThreadName));
|
||||
}
|
||||
if (!JS_DefineProperty(cx, eObj, "threadName", jsval, JSPROP_ENUMERATE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JS::RootedObject modulesArray(cx);
|
||||
bool ok = VectorToJSArray(cx, &modulesArray, aEvent.mModules,
|
||||
[](JSContext* cx, JS::MutableHandleValue aRet,
|
||||
const ModuleLoadEvent::ModuleInfo& aModInfo) {
|
||||
JS::RootedObject obj(cx);
|
||||
if (!ModuleInfoToJSObj(cx, &obj, aModInfo)) {
|
||||
return false;
|
||||
}
|
||||
aRet.setObject(*obj);
|
||||
return true;
|
||||
});
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!JS_DefineProperty(cx, eObj, "modules", modulesArray, JSPROP_ENUMERATE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
aRet.setObject(*eObj);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a UntrustedModuleLoadTelemetryData to a JS object.
|
||||
*
|
||||
* @param aData [in] The source object to convert.
|
||||
* @param aCx [in] The JS context.
|
||||
* @param cx [in] The JS context.
|
||||
* @param aRet [out] This gets assigned to the newly created object.
|
||||
* @return nsresult
|
||||
*/
|
||||
static nsresult GetUntrustedModuleLoadEventsJSValue(
|
||||
const UntrustedModulesData& aData, JSContext* aCx,
|
||||
JS::MutableHandleValue aRet) {
|
||||
nsresult GetUntrustedModuleLoadEventsJSValue(
|
||||
const UntrustedModuleLoadTelemetryData& aData, JSContext* cx,
|
||||
JS::MutableHandle<JS::Value> aRet) {
|
||||
if (aData.mEvents.empty()) {
|
||||
aRet.setNull();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (aData.mModules.Count() > kMaxModulesArrayLen) {
|
||||
return NS_ERROR_CANNOT_CONVERT_DATA;
|
||||
}
|
||||
|
||||
JS::RootedObject mainObj(aCx, JS_NewPlainObject(aCx));
|
||||
JS::RootedValue jsval(cx);
|
||||
JS::RootedObject mainObj(cx, JS_NewObject(cx, nullptr));
|
||||
if (!mainObj) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
JS::RootedValue jsVersion(aCx);
|
||||
jsVersion.setNumber(kThirdPartyModulesPingVersion);
|
||||
if (!JS_DefineProperty(aCx, mainObj, "structVersion", jsVersion,
|
||||
jsval.setNumber((uint32_t)aData.mErrorModules);
|
||||
if (!JS_DefineProperty(cx, mainObj, "errorModules", jsval,
|
||||
JSPROP_ENUMERATE)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
IndexMap indexMap;
|
||||
uint32_t curModulesArrayIdx = 0;
|
||||
|
||||
JS::RootedObject modulesArray(aCx, JS_NewArrayObject(aCx, 0));
|
||||
if (!modulesArray) {
|
||||
jsval.setNumber((uint32_t)kUntrustedModuleLoadEventsTelemetryVersion);
|
||||
if (!JS_DefineProperty(cx, mainObj, "structVersion", jsval,
|
||||
JSPROP_ENUMERATE)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
JS::RootedObject perProcObjContainer(aCx, JS_NewPlainObject(aCx));
|
||||
if (!perProcObjContainer) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Serialize each entry in the modules hashtable out to the "modules" array
|
||||
// and store the indices in |indexMap|
|
||||
for (auto iter = aData.mModules.ConstIter(); !iter.Done(); iter.Next()) {
|
||||
auto addPtr = indexMap.LookupForAdd(iter.Key());
|
||||
if (!addPtr) {
|
||||
addPtr.OrInsert([curModulesArrayIdx]() { return curModulesArrayIdx; });
|
||||
|
||||
JS::RootedValue jsModule(aCx);
|
||||
if (!SerializeModule(aCx, &jsModule, iter.Data()) ||
|
||||
!JS_DefineElement(aCx, modulesArray, curModulesArrayIdx, jsModule,
|
||||
JSPROP_ENUMERATE)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
++curModulesArrayIdx;
|
||||
if (aData.mXULLoadDurationMS.isSome()) {
|
||||
jsval.setNumber(aData.mXULLoadDurationMS.value());
|
||||
if (!JS_DefineProperty(cx, mainObj, "xulLoadDurationMS", jsval,
|
||||
JSPROP_ENUMERATE)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
JS::RootedObject perProcObj(aCx, JS_NewPlainObject(aCx));
|
||||
if (!perProcObj) {
|
||||
JS::RootedObject eventsArray(cx);
|
||||
if (!VectorToJSArray(cx, &eventsArray, aData.mEvents,
|
||||
&ModuleLoadEventToJSArray)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsresult rv = GetPerProcObject(aCx, indexMap, aData, &perProcObj);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsAutoCString strPid;
|
||||
strPid.AppendLiteral("0x");
|
||||
strPid.AppendInt(static_cast<uint32_t>(aData.mPid), 16);
|
||||
|
||||
JS::RootedValue jsPerProcObjValue(aCx);
|
||||
jsPerProcObjValue.setObject(*perProcObj);
|
||||
if (!JS_DefineProperty(aCx, perProcObjContainer, strPid.get(),
|
||||
jsPerProcObjValue, JSPROP_ENUMERATE)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
JS::RootedValue jsModulesArrayValue(aCx);
|
||||
jsModulesArrayValue.setObject(*modulesArray);
|
||||
if (!JS_DefineProperty(aCx, mainObj, "modules", jsModulesArrayValue,
|
||||
if (!JS_DefineProperty(cx, mainObj, "events", eventsArray,
|
||||
JSPROP_ENUMERATE)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
JS::RootedValue jsPerProcObjContainerValue(aCx);
|
||||
jsPerProcObjContainerValue.setObject(*perProcObjContainer);
|
||||
if (!JS_DefineProperty(aCx, mainObj, "processes", jsPerProcObjContainerValue,
|
||||
JS::RootedObject combinedStacksObj(cx,
|
||||
CreateJSStackObject(cx, aData.mStacks));
|
||||
if (!combinedStacksObj) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (!JS_DefineProperty(cx, mainObj, "combinedStacks", combinedStacksObj,
|
||||
JSPROP_ENUMERATE)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
aRet.setObject(*mainObj);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
static void Serialize(Maybe<UntrustedModulesData>&& aData,
|
||||
RefPtr<dom::Promise>&& aPromise) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
class GetUntrustedModulesMainThreadRunnable final : public Runnable {
|
||||
nsMainThreadPtrHandle<dom::Promise> mPromise;
|
||||
bool mDataOK;
|
||||
UntrustedModuleLoadTelemetryData mData;
|
||||
nsCOMPtr<nsIThread> mWorkerThread;
|
||||
|
||||
dom::AutoJSAPI jsapi;
|
||||
if (NS_WARN_IF(!jsapi.Init(aPromise->GetGlobalObject()))) {
|
||||
aPromise->MaybeReject(NS_ERROR_FAILURE);
|
||||
return;
|
||||
public:
|
||||
GetUntrustedModulesMainThreadRunnable(
|
||||
const nsMainThreadPtrHandle<dom::Promise>& aPromise, bool aDataOK,
|
||||
UntrustedModuleLoadTelemetryData&& aData)
|
||||
: Runnable("GetUntrustedModulesMainThreadRunnable"),
|
||||
mPromise(aPromise),
|
||||
mDataOK(aDataOK),
|
||||
mData(std::move(aData)),
|
||||
mWorkerThread(do_GetCurrentThread()) {
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
}
|
||||
|
||||
if (aData.isNothing()) {
|
||||
aPromise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
|
||||
return;
|
||||
NS_IMETHOD
|
||||
Run() override {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
mWorkerThread->Shutdown();
|
||||
|
||||
dom::AutoJSAPI jsapi;
|
||||
if (NS_WARN_IF(!jsapi.Init(mPromise->GetGlobalObject()))) {
|
||||
mPromise->MaybeReject(NS_ERROR_FAILURE);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!mDataOK) {
|
||||
mPromise->MaybeReject(NS_ERROR_FAILURE);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
JSContext* cx = jsapi.cx();
|
||||
JS::RootedValue jsval(cx);
|
||||
|
||||
nsresult rv = GetUntrustedModuleLoadEventsJSValue(mData, cx, &jsval);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
mPromise->MaybeReject(rv);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mPromise->MaybeResolve(jsval);
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
class GetUntrustedModulesTelemetryDataRunnable final : public Runnable {
|
||||
nsMainThreadPtrHandle<dom::Promise> mPromise;
|
||||
|
||||
public:
|
||||
explicit GetUntrustedModulesTelemetryDataRunnable(
|
||||
const nsMainThreadPtrHandle<dom::Promise>& aPromise)
|
||||
: Runnable("GetUntrustedModulesTelemetryDataRunnable"),
|
||||
mPromise(aPromise) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
}
|
||||
|
||||
JSContext* cx = jsapi.cx();
|
||||
JS::RootedValue jsval(cx);
|
||||
NS_IMETHOD
|
||||
Run() override {
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
RefPtr<DllServices> dllSvc(DllServices::Get());
|
||||
UntrustedModuleLoadTelemetryData data;
|
||||
bool ok = dllSvc->GetUntrustedModuleTelemetryData(data);
|
||||
|
||||
nsresult rv = GetUntrustedModuleLoadEventsJSValue(aData.ref(), cx, &jsval);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aPromise->MaybeReject(rv);
|
||||
return;
|
||||
// Dispatch back to the main thread for remaining JS processing.
|
||||
return NS_DispatchToMainThread(new GetUntrustedModulesMainThreadRunnable(
|
||||
mPromise, ok, std::move(data)));
|
||||
}
|
||||
|
||||
aPromise->MaybeResolve(jsval);
|
||||
}
|
||||
};
|
||||
|
||||
nsresult GetUntrustedModuleLoadEvents(JSContext* cx, dom::Promise** aPromise) {
|
||||
// Create a promise using global context.
|
||||
|
@ -502,21 +376,31 @@ nsresult GetUntrustedModuleLoadEvents(JSContext* cx, dom::Promise** aPromise) {
|
|||
}
|
||||
|
||||
ErrorResult result;
|
||||
RefPtr<dom::Promise> promise(dom::Promise::Create(global, result));
|
||||
RefPtr<dom::Promise> promise = dom::Promise::Create(global, result);
|
||||
if (NS_WARN_IF(result.Failed())) {
|
||||
return result.StealNSResult();
|
||||
}
|
||||
|
||||
RefPtr<DllServices> dllSvc(DllServices::Get());
|
||||
dllSvc->GetUntrustedModulesData()->Then(
|
||||
GetMainThreadSerialEventTarget(), __func__,
|
||||
[promise](Maybe<UntrustedModulesData>&& aData) mutable {
|
||||
Serialize(std::move(aData), std::move(promise));
|
||||
},
|
||||
[promise](nsresult aRv) { promise->MaybeReject(aRv); });
|
||||
// Create a worker thread to perform the heavy work.
|
||||
nsCOMPtr<nsIThread> workThread;
|
||||
nsresult rv = NS_NewNamedThread("UntrustedDLLs", getter_AddRefs(workThread));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
promise->MaybeReject(NS_ERROR_FAILURE);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// In order to pass the promise through the worker thread to the main thread,
|
||||
// this is needed.
|
||||
nsMainThreadPtrHandle<dom::Promise> mainThreadPromise(
|
||||
new nsMainThreadPtrHolder<dom::Promise>(
|
||||
"Telemetry::UntrustedModuleLoadEvents::Promise", promise));
|
||||
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
new GetUntrustedModulesTelemetryDataRunnable(mainThreadPromise);
|
||||
promise.forget(aPromise);
|
||||
return NS_OK;
|
||||
|
||||
return workThread->Dispatch(runnable.forget(),
|
||||
nsIEventTarget::DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
} // namespace Telemetry
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
#ifndef telemetry_UntrustedModules_h__
|
||||
#define telemetry_UntrustedModules_h__
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "jsapi.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace Telemetry {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* This module periodically sends a Telemetry ping containing information
|
||||
* about untrusted module loads on Windows.
|
||||
*
|
||||
* https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/data/third-party-modules-ping.html
|
||||
* https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/data/untrusted-modules-ping.html
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
@ -35,7 +35,7 @@ const DEFAULT_INTERVAL_SECONDS = 24 * 60 * 60; // 1 day
|
|||
const LOGGER_NAME = "Toolkit.Telemetry";
|
||||
const LOGGER_PREFIX = "TelemetryUntrustedModulesPing::";
|
||||
const TIMER_NAME = "telemetry_untrustedmodules_ping";
|
||||
const PING_SUBMISSION_NAME = "third-party-modules";
|
||||
const PING_SUBMISSION_NAME = "untrustedModules";
|
||||
|
||||
var TelemetryUntrustedModulesPing = Object.freeze({
|
||||
_log: Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX),
|
||||
|
|
|
@ -22,3 +22,7 @@ if CONFIG['COMPILE_ENVIRONMENT']:
|
|||
shared_library = '!%smodules-test%s' % (CONFIG['DLL_PREFIX'], CONFIG['DLL_SUFFIX'])
|
||||
TEST_HARNESS_FILES.xpcshell.toolkit.components.telemetry.tests.unit += [shared_library]
|
||||
|
||||
if CONFIG['ENABLE_TESTS'] and CONFIG['OS_ARCH'] == 'WINNT':
|
||||
DIRS += [
|
||||
'untrusted-startup-test-dll',
|
||||
]
|
||||
|
|
|
@ -16,7 +16,6 @@ const { ctypes } = ChromeUtils.import("resource://gre/modules/ctypes.jsm");
|
|||
const kDllName = "modules-test.dll";
|
||||
|
||||
let gDllHandle;
|
||||
let gCurrentPidStr;
|
||||
|
||||
add_task(async function setup() {
|
||||
do_get_profile();
|
||||
|
@ -32,9 +31,6 @@ add_task(async function setup() {
|
|||
Preferences.set("toolkit.telemetry.untrustedModulesPing.frequency", 0);
|
||||
Preferences.set("app.update.url", "http://localhost");
|
||||
|
||||
let currentPid = Services.appinfo.processID;
|
||||
gCurrentPidStr = "0x" + currentPid.toString(16);
|
||||
|
||||
// Start the local ping server and setup Telemetry to use it during the tests.
|
||||
PingServer.start();
|
||||
Preferences.set(
|
||||
|
@ -56,16 +52,26 @@ registerCleanupFunction(function() {
|
|||
add_task(async function test_send_ping() {
|
||||
let expectedModules = [
|
||||
// This checks that a DLL loaded during runtime is evaluated properly.
|
||||
// This is hard-coded as untrusted in toolkit/xre/UntrustedModules.cpp for
|
||||
// testing purposes.
|
||||
// This is hard-coded as untrusted in ModuleEvaluator for testing.
|
||||
{
|
||||
nameMatch: new RegExp(kDllName, "i"),
|
||||
expectedTrusted: false,
|
||||
isStartup: false,
|
||||
wasFound: false,
|
||||
},
|
||||
|
||||
// These check that a DLL loaded at startup is evaluated properly.
|
||||
// This is hard-coded as untrusted in ModuleEvaluator for testing.
|
||||
{
|
||||
nameMatch: /untrusted-startup-test-dll.dll/i,
|
||||
expectedTrusted: false,
|
||||
isStartup: true,
|
||||
wasFound: false,
|
||||
},
|
||||
{
|
||||
nameMatch: /kernelbase.dll/i,
|
||||
expectedTrusted: true,
|
||||
isStartup: true,
|
||||
wasFound: false,
|
||||
},
|
||||
];
|
||||
|
@ -75,7 +81,7 @@ add_task(async function test_send_ping() {
|
|||
let found;
|
||||
while (true) {
|
||||
found = await PingServer.promiseNextPing();
|
||||
if (found.type == "third-party-modules") {
|
||||
if (found.type == "untrustedModules") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -86,55 +92,43 @@ add_task(async function test_send_ping() {
|
|||
Assert.ok(typeof found.clientId != "undefined", "Ping has a client ID");
|
||||
|
||||
Assert.equal(found.payload.structVersion, 1, "Version is correct");
|
||||
Assert.ok(found.payload.modules, "'modules' object exists");
|
||||
Assert.ok(Array.isArray(found.payload.modules), "'modules' is an array");
|
||||
Assert.ok(found.payload.processes, "'processes' object exists");
|
||||
Assert.ok(
|
||||
gCurrentPidStr in found.payload.processes,
|
||||
`Current process "${gCurrentPidStr}" is included in payload`
|
||||
);
|
||||
|
||||
let ourProcInfo = found.payload.processes[gCurrentPidStr];
|
||||
Assert.equal(ourProcInfo.processType, "browser", "'processType' is correct");
|
||||
Assert.ok(typeof ourProcInfo.elapsed == "number", "'elapsed' exists");
|
||||
Assert.ok(found.payload.combinedStacks, "'combinedStacks' array exists");
|
||||
Assert.ok(found.payload.events, "'events' array exists");
|
||||
Assert.equal(
|
||||
ourProcInfo.sanitizationFailures,
|
||||
0,
|
||||
"'sanitizationFailures' is 0"
|
||||
);
|
||||
Assert.equal(ourProcInfo.trustTestFailures, 0, "'trustTestFailures' is 0");
|
||||
|
||||
Assert.equal(
|
||||
ourProcInfo.combinedStacks.stacks.length,
|
||||
ourProcInfo.events.length,
|
||||
"combinedStacks.stacks.length == events.length"
|
||||
found.payload.combinedStacks.stacks.length,
|
||||
found.payload.events.length,
|
||||
"combinedStacks.length == events.length"
|
||||
);
|
||||
|
||||
for (let event of ourProcInfo.events) {
|
||||
Assert.ok(
|
||||
typeof event.processUptimeMS == "number",
|
||||
"'processUptimeMS' exists"
|
||||
);
|
||||
Assert.ok(typeof event.threadID == "number", "'threadID' exists");
|
||||
Assert.ok(typeof event.baseAddress == "string", "'baseAddress' exists");
|
||||
|
||||
Assert.ok(typeof event.moduleIndex == "number", "'moduleIndex' exists");
|
||||
Assert.ok(event.moduleIndex >= 0, "'moduleIndex' is non-negative");
|
||||
|
||||
let modRecord = found.payload.modules[event.moduleIndex];
|
||||
Assert.ok(modRecord, "module record for this event exists");
|
||||
Assert.ok(
|
||||
typeof modRecord.resolvedDllName == "string",
|
||||
"'resolvedDllName' exists"
|
||||
);
|
||||
Assert.ok(typeof modRecord.trustFlags == "number", "'trustFlags' exists");
|
||||
|
||||
let mod = expectedModules.find(function(elem) {
|
||||
return elem.nameMatch.test(modRecord.resolvedDllName);
|
||||
});
|
||||
|
||||
if (mod) {
|
||||
mod.wasFound = true;
|
||||
for (let event of found.payload.events) {
|
||||
Assert.ok(event.modules, "'modules' array exists");
|
||||
for (let mod of event.modules) {
|
||||
Assert.ok(
|
||||
typeof mod.moduleName != "undefined",
|
||||
`Module contains moduleName: ${mod.moduleName}`
|
||||
);
|
||||
Assert.ok(
|
||||
typeof mod.moduleTrustFlags != "undefined",
|
||||
`Module contains moduleTrustFlags: ${mod.moduleTrustFlags}`
|
||||
);
|
||||
Assert.ok(
|
||||
typeof mod.baseAddress != "undefined",
|
||||
"Module contains baseAddress"
|
||||
);
|
||||
Assert.ok(
|
||||
typeof mod.loaderName != "undefined",
|
||||
"Module contains loaderName"
|
||||
);
|
||||
for (let x of expectedModules) {
|
||||
if (x.nameMatch.test(mod.moduleName)) {
|
||||
x.wasFound = true;
|
||||
Assert.equal(
|
||||
x.isStartup,
|
||||
event.isStartup,
|
||||
`isStartup == expected for module: ${x.nameMatch.source}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -96,7 +96,7 @@ skip-if = (os == "android") || (os == "linux" && bits == 32)
|
|||
[test_TelemetryGC.js]
|
||||
[test_TelemetryAndroidEnvironment.js]
|
||||
[test_TelemetryUtils.js]
|
||||
[test_ThirdPartyModulesPing.js]
|
||||
[test_UntrustedModulesPing.js]
|
||||
run-if = nightly_build && (os == 'win')
|
||||
[test_EcosystemTelemetry.js]
|
||||
skip-if = (os == "linux" && bits == 32) # lack of support on test runners (see bug 1310703, comment78)
|
||||
|
|
|
@ -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('untrusted-startup-test-dll')
|
||||
|
||||
UNIFIED_SOURCES = [
|
||||
'untrusted-startup-test-dll.cpp',
|
||||
]
|
||||
|
||||
NO_PGO = True
|
||||
|
||||
if CONFIG['COMPILE_ENVIRONMENT']:
|
||||
TEST_HARNESS_FILES.xpcshell.toolkit.components.telemetry.tests.unit += ['!untrusted-startup-test-dll.dll']
|
|
@ -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/. */
|
||||
|
||||
/*
|
||||
* This source file is used to build "untrusted-startup-test-dll.dll" on
|
||||
* Windows. During xpcshell tests, it's loaded early enough to be detected as a
|
||||
* startup module, and hard-coded to be reported in the untrusted modules
|
||||
* telemetry ping, which allows for testing some code paths.
|
||||
*/
|
||||
|
||||
void nothing() {}
|
|
@ -46,7 +46,6 @@
|
|||
# include "nsDirectoryServiceUtils.h"
|
||||
|
||||
# include "nsWindowsDllInterceptor.h"
|
||||
# include "mozilla/WindowsDllBlocklist.h"
|
||||
# include "mozilla/WindowsVersion.h"
|
||||
# include "psapi.h" // For PERFORMANCE_INFORAMTION
|
||||
#elif defined(XP_MACOSX)
|
||||
|
@ -97,6 +96,7 @@ using mozilla::InjectCrashRunnable;
|
|||
|
||||
#include "mozilla/IOInterposer.h"
|
||||
#include "mozilla/mozalloc_oom.h"
|
||||
#include "mozilla/WindowsDllBlocklist.h"
|
||||
#include "mozilla/recordreplay/ParentIPC.h"
|
||||
|
||||
#if defined(XP_MACOSX)
|
||||
|
|
|
@ -1,245 +0,0 @@
|
|||
/* -*- 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 "ModuleEvaluator.h"
|
||||
|
||||
#include <algorithm> // For std::find()
|
||||
|
||||
#include <windows.h>
|
||||
#include <shlobj.h>
|
||||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/ModuleVersionInfo.h"
|
||||
#include "mozilla/TypeTraits.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "mozilla/WinDllServices.h"
|
||||
#include "mozilla/WinHeaderOnlyUtils.h"
|
||||
#include "nsReadableUtils.h"
|
||||
#include "nsWindowsHelpers.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
|
||||
// Fills a Vector with keyboard layout DLLs found in the registry.
|
||||
// These are leaf names only, not full paths. Here we will convert them to
|
||||
// lowercase before returning, to facilitate case-insensitive searches.
|
||||
// On error, this may return partial results.
|
||||
static Vector<nsString> GetKeyboardLayoutDlls() {
|
||||
Vector<nsString> result;
|
||||
|
||||
HKEY rawKey;
|
||||
if (::RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
||||
L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts",
|
||||
0, KEY_ENUMERATE_SUB_KEYS, &rawKey) != ERROR_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
nsAutoRegKey key(rawKey);
|
||||
|
||||
DWORD iKey = 0;
|
||||
wchar_t strTemp[MAX_PATH] = {};
|
||||
while (true) {
|
||||
DWORD strTempSize = ArrayLength(strTemp);
|
||||
if (RegEnumKeyExW(rawKey, iKey, strTemp, &strTempSize, nullptr, nullptr,
|
||||
nullptr, nullptr) != ERROR_SUCCESS) {
|
||||
// ERROR_NO_MORE_ITEMS or a real error: bail with what we have.
|
||||
return result;
|
||||
}
|
||||
iKey++;
|
||||
|
||||
strTempSize = sizeof(strTemp);
|
||||
if (::RegGetValueW(rawKey, strTemp, L"Layout File", RRF_RT_REG_SZ, nullptr,
|
||||
strTemp, &strTempSize) == ERROR_SUCCESS &&
|
||||
strTempSize) {
|
||||
nsString ws(strTemp, ((strTempSize + 1) / sizeof(wchar_t)) - 1);
|
||||
ToLowerCase(ws); // To facilitate case-insensitive searches
|
||||
Unused << result.emplaceBack(std::move(ws));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/* static */
|
||||
bool ModuleEvaluator::ResolveKnownFolder(REFKNOWNFOLDERID aFolderId,
|
||||
nsIFile** aOutFile) {
|
||||
if (!aOutFile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*aOutFile = nullptr;
|
||||
|
||||
// Since we're running off main thread, we can't use NS_GetSpecialDirectory
|
||||
PWSTR rawPath = nullptr;
|
||||
HRESULT hr =
|
||||
::SHGetKnownFolderPath(aFolderId, KF_FLAG_DEFAULT, nullptr, &rawPath);
|
||||
if (FAILED(hr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
using ShellStringUniquePtr =
|
||||
UniquePtr<RemovePointer<PWSTR>::Type, CoTaskMemFreeDeleter>;
|
||||
|
||||
ShellStringUniquePtr path(rawPath);
|
||||
|
||||
nsresult rv = NS_NewLocalFile(nsDependentString(path.get()), false, aOutFile);
|
||||
return NS_SUCCEEDED(rv);
|
||||
}
|
||||
|
||||
ModuleEvaluator::ModuleEvaluator()
|
||||
: mKeyboardLayoutDlls(GetKeyboardLayoutDlls()) {
|
||||
#if defined(_M_IX86)
|
||||
// We want to resolve to SYSWOW64 when applicable
|
||||
REFKNOWNFOLDERID systemFolderId = FOLDERID_SystemX86;
|
||||
#else
|
||||
REFKNOWNFOLDERID systemFolderId = FOLDERID_System;
|
||||
#endif // defined(_M_IX86)
|
||||
|
||||
bool resolveOk =
|
||||
ResolveKnownFolder(systemFolderId, getter_AddRefs(mSysDirectory));
|
||||
MOZ_ASSERT(resolveOk);
|
||||
if (!resolveOk) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIFile> winSxSDir;
|
||||
resolveOk = ResolveKnownFolder(FOLDERID_Windows, getter_AddRefs(winSxSDir));
|
||||
MOZ_ASSERT(resolveOk);
|
||||
if (!resolveOk) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsresult rv = winSxSDir->Append(NS_LITERAL_STRING("WinSxS"));
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
if (NS_FAILED(rv)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mWinSxSDirectory = std::move(winSxSDir);
|
||||
|
||||
nsCOMPtr<nsIFile> exeFile;
|
||||
rv = XRE_GetBinaryPath(getter_AddRefs(exeFile));
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
if (NS_FAILED(rv)) {
|
||||
return;
|
||||
}
|
||||
|
||||
rv = exeFile->GetParent(getter_AddRefs(mExeDirectory));
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
if (NS_FAILED(rv)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsAutoString exePath;
|
||||
rv = exeFile->GetPath(exePath);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
if (NS_FAILED(rv)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ModuleVersionInfo exeVi;
|
||||
if (!exeVi.GetFromImage(exePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mExeVersion = Some(ModuleVersion(exeVi.mFileVersion.Version64()));
|
||||
}
|
||||
|
||||
ModuleEvaluator::operator bool() const {
|
||||
return mExeVersion.isSome() && mExeDirectory && mSysDirectory &&
|
||||
mWinSxSDirectory;
|
||||
}
|
||||
|
||||
Maybe<ModuleTrustFlags> ModuleEvaluator::GetTrust(
|
||||
const ModuleRecord& aModuleRecord) const {
|
||||
// We start by checking authenticode signatures, as the presence of any
|
||||
// signature will produce an immediate pass/fail.
|
||||
if (aModuleRecord.mVendorInfo.isSome() &&
|
||||
aModuleRecord.mVendorInfo.ref().mSource ==
|
||||
VendorInfo::Source::Signature) {
|
||||
const nsString& signedBy = aModuleRecord.mVendorInfo.ref().mVendor;
|
||||
|
||||
if (signedBy.EqualsLiteral("Microsoft Windows")) {
|
||||
return Some(ModuleTrustFlags::MicrosoftWindowsSignature);
|
||||
} else if (signedBy.EqualsLiteral("Microsoft Corporation")) {
|
||||
return Some(ModuleTrustFlags::MicrosoftWindowsSignature);
|
||||
} else if (signedBy.EqualsLiteral("Mozilla Corporation")) {
|
||||
return Some(ModuleTrustFlags::MozillaSignature);
|
||||
} else {
|
||||
// Being signed by somebody who is neither Microsoft nor us is an
|
||||
// automatic and immediate disqualification.
|
||||
return Some(ModuleTrustFlags::None);
|
||||
}
|
||||
}
|
||||
|
||||
const nsCOMPtr<nsIFile>& dllFile = aModuleRecord.mResolvedDllName;
|
||||
|
||||
nsAutoString dllLeafLower;
|
||||
if (NS_FAILED(dllFile->GetLeafName(dllLeafLower))) {
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
ToLowerCase(dllLeafLower); // To facilitate case-insensitive searching
|
||||
|
||||
// The JIT profiling module doesn't really have any other practical way to
|
||||
// match; hard-code it as being trusted.
|
||||
if (dllLeafLower.EqualsLiteral("jitpi.dll")) {
|
||||
return Some(ModuleTrustFlags::JitPI);
|
||||
}
|
||||
|
||||
ModuleTrustFlags result = ModuleTrustFlags::None;
|
||||
|
||||
nsresult rv;
|
||||
bool contained;
|
||||
|
||||
// Is the DLL in the system directory?
|
||||
rv = mSysDirectory->Contains(dllFile, &contained);
|
||||
if (NS_SUCCEEDED(rv) && contained) {
|
||||
result |= ModuleTrustFlags::SystemDirectory;
|
||||
}
|
||||
|
||||
// Is the DLL in the WinSxS directory? Some Microsoft DLLs (e.g. comctl32) are
|
||||
// loaded from here and don't have digital signatures. So while this is not a
|
||||
// guarantee of trustworthiness, but is at least as valid as system32.
|
||||
rv = mWinSxSDirectory->Contains(dllFile, &contained);
|
||||
if (NS_SUCCEEDED(rv) && contained) {
|
||||
result |= ModuleTrustFlags::WinSxSDirectory;
|
||||
}
|
||||
|
||||
// Is it a keyboard layout DLL?
|
||||
if (std::find(mKeyboardLayoutDlls.begin(), mKeyboardLayoutDlls.end(),
|
||||
dllLeafLower) != mKeyboardLayoutDlls.end()) {
|
||||
result |= ModuleTrustFlags::KeyboardLayout;
|
||||
// This doesn't guarantee trustworthiness by itself. Keyboard layouts also
|
||||
// must be in the system directory.
|
||||
}
|
||||
|
||||
if (aModuleRecord.mVendorInfo.isSome() &&
|
||||
aModuleRecord.mVendorInfo.ref().mSource ==
|
||||
VendorInfo::Source::VersionInfo) {
|
||||
const nsString& companyName = aModuleRecord.mVendorInfo.ref().mVendor;
|
||||
|
||||
if (companyName.EqualsLiteral("Microsoft Corporation")) {
|
||||
result |= ModuleTrustFlags::MicrosoftVersion;
|
||||
}
|
||||
}
|
||||
|
||||
rv = mExeDirectory->Contains(dllFile, &contained);
|
||||
if (NS_SUCCEEDED(rv) && contained) {
|
||||
result |= ModuleTrustFlags::FirefoxDirectory;
|
||||
|
||||
// If the DLL is in the Firefox directory, does it also share the Firefox
|
||||
// version info?
|
||||
if (mExeVersion.isSome() && aModuleRecord.mVersion.isSome() &&
|
||||
mExeVersion.value() == aModuleRecord.mVersion.value()) {
|
||||
result |= ModuleTrustFlags::FirefoxDirectoryAndVersion;
|
||||
}
|
||||
}
|
||||
|
||||
return Some(result);
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -1,48 +0,0 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_ModuleEvaluator_h
|
||||
#define mozilla_ModuleEvaluator_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/UntrustedModulesData.h"
|
||||
#include "mozilla/Vector.h"
|
||||
#include "mozilla/WinHeaderOnlyUtils.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsString.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class ModuleRecord;
|
||||
|
||||
/**
|
||||
* This class performs trustworthiness evaluation for incoming DLLs.
|
||||
*/
|
||||
class MOZ_RAII ModuleEvaluator final {
|
||||
public:
|
||||
ModuleEvaluator();
|
||||
|
||||
explicit operator bool() const;
|
||||
|
||||
Maybe<ModuleTrustFlags> GetTrust(const ModuleRecord& aModuleRecord) const;
|
||||
|
||||
private:
|
||||
static bool ResolveKnownFolder(REFKNOWNFOLDERID aFolderId,
|
||||
nsIFile** aOutFile);
|
||||
|
||||
private:
|
||||
Maybe<ModuleVersion> mExeVersion; // Version number of the running EXE image
|
||||
nsCOMPtr<nsIFile> mExeDirectory;
|
||||
nsCOMPtr<nsIFile> mSysDirectory;
|
||||
nsCOMPtr<nsIFile> mWinSxSDirectory;
|
||||
Vector<nsString> mKeyboardLayoutDlls;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_ModuleEvaluator_h
|
|
@ -0,0 +1,381 @@
|
|||
/* -*- 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 "ModuleEvaluator_windows.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <algorithm> // For std::find()
|
||||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/CmdLineAndEnvUtils.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsDirectoryServiceDefs.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsReadableUtils.h"
|
||||
#include "nsWindowsHelpers.h"
|
||||
#include "WinUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
// Utility function to get the parent directory of aFile.
|
||||
// Returns true upon success.
|
||||
static bool GetDirectoryName(const nsCOMPtr<nsIFile> aFile,
|
||||
nsAString& aParent) {
|
||||
nsCOMPtr<nsIFile> parentDir;
|
||||
if (NS_FAILED(aFile->GetParent(getter_AddRefs(parentDir))) || !parentDir) {
|
||||
return false;
|
||||
}
|
||||
if (NS_FAILED(parentDir->GetPath(aParent))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ModuleLoadEvent::ModuleInfo::ModuleInfo(uintptr_t aBase)
|
||||
: mBase(aBase), mTrustFlags(ModuleTrustFlags::None) {}
|
||||
|
||||
ModuleLoadEvent::ModuleInfo::ModuleInfo(
|
||||
const glue::ModuleLoadEvent::ModuleInfo& aOther)
|
||||
: mBase(aOther.mBase),
|
||||
mLoadDurationMS(Some(aOther.mLoadDurationMS)),
|
||||
mTrustFlags(ModuleTrustFlags::None) {
|
||||
if (aOther.mLdrName) {
|
||||
mLdrName.Assign(aOther.mLdrName.get());
|
||||
}
|
||||
|
||||
if (aOther.mFullPath) {
|
||||
nsDependentString tempPath(aOther.mFullPath.get());
|
||||
DebugOnly<nsresult> rv =
|
||||
NS_NewLocalFile(tempPath, false, getter_AddRefs(mFile));
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
}
|
||||
}
|
||||
|
||||
bool ModuleLoadEvent::ModuleInfo::PopulatePathInfo() {
|
||||
MOZ_ASSERT(mBase && mLdrName.IsEmpty() && !mFile);
|
||||
if (!widget::WinUtils::GetModuleFullPath(reinterpret_cast<HMODULE>(mBase),
|
||||
mLdrName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return NS_SUCCEEDED(NS_NewLocalFile(mLdrName, false, getter_AddRefs(mFile)));
|
||||
}
|
||||
|
||||
bool ModuleLoadEvent::ModuleInfo::PrepForTelemetry() {
|
||||
MOZ_ASSERT(!mLdrName.IsEmpty() && mFile);
|
||||
if (mLdrName.IsEmpty() || !mFile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
using PathTransformFlags = widget::WinUtils::PathTransformFlags;
|
||||
|
||||
if (!widget::WinUtils::PreparePathForTelemetry(
|
||||
mLdrName,
|
||||
PathTransformFlags::Default & ~PathTransformFlags::Canonicalize)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsAutoString dllFullPath;
|
||||
if (NS_FAILED(mFile->GetPath(dllFullPath))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!widget::WinUtils::MakeLongPath(dllFullPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Replace mFile with the lengthened version
|
||||
if (NS_FAILED(NS_NewLocalFile(dllFullPath, false, getter_AddRefs(mFile)))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsAutoString sanitized(dllFullPath);
|
||||
|
||||
if (!widget::WinUtils::PreparePathForTelemetry(
|
||||
sanitized,
|
||||
PathTransformFlags::Default & ~(PathTransformFlags::Canonicalize |
|
||||
PathTransformFlags::Lengthen))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mFilePathClean = std::move(sanitized);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ModuleLoadEvent::ModuleLoadEvent(const ModuleLoadEvent& aOther,
|
||||
CopyOption aOption)
|
||||
: mIsStartup(aOther.mIsStartup),
|
||||
mThreadID(aOther.mThreadID),
|
||||
mThreadName(aOther.mThreadName),
|
||||
mProcessUptimeMS(aOther.mProcessUptimeMS) {
|
||||
Unused << mStack.reserve(aOther.mStack.length());
|
||||
for (auto& x : aOther.mStack) {
|
||||
Unused << mStack.append(x);
|
||||
}
|
||||
if (aOption != CopyOption::CopyWithoutModules) {
|
||||
Unused << mModules.reserve(aOther.mModules.length());
|
||||
for (auto& x : aOther.mModules) {
|
||||
Unused << mModules.append(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ModuleLoadEvent::ModuleLoadEvent(const glue::ModuleLoadEvent& aOther)
|
||||
: mIsStartup(
|
||||
false) // Events originating in glue:: cannot be a startup event.
|
||||
,
|
||||
mThreadID(aOther.mThreadID),
|
||||
mProcessUptimeMS(aOther.mProcessUptimeMS) {
|
||||
for (auto& frame : aOther.mStack) {
|
||||
Unused << mStack.append(frame);
|
||||
}
|
||||
for (auto& module : aOther.mModules) {
|
||||
Unused << mModules.append(ModuleInfo(module));
|
||||
}
|
||||
}
|
||||
|
||||
// Fills a Vector with keyboard layout DLLs found in the registry.
|
||||
// These are leaf names only, not full paths. Here we will convert them to
|
||||
// lowercase before returning, to facilitate case-insensitive searches.
|
||||
// On error, this may return partial results.
|
||||
static void GetKeyboardLayoutDlls(
|
||||
Vector<nsString, 0, InfallibleAllocPolicy>& aOut) {
|
||||
HKEY rawKey;
|
||||
if (::RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
||||
L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts",
|
||||
0, KEY_ENUMERATE_SUB_KEYS, &rawKey) != ERROR_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
nsAutoRegKey key(rawKey);
|
||||
|
||||
DWORD iKey = 0;
|
||||
wchar_t strTemp[MAX_PATH] = {0};
|
||||
while (true) {
|
||||
DWORD strTempSize = ArrayLength(strTemp);
|
||||
if (RegEnumKeyExW(rawKey, iKey, strTemp, &strTempSize, nullptr, nullptr,
|
||||
nullptr, nullptr) != ERROR_SUCCESS) {
|
||||
return; // ERROR_NO_MORE_ITEMS or a real error: bail with what we have.
|
||||
}
|
||||
iKey++;
|
||||
|
||||
strTempSize = sizeof(strTemp);
|
||||
if (::RegGetValueW(rawKey, strTemp, L"Layout File", RRF_RT_REG_SZ, nullptr,
|
||||
strTemp, &strTempSize) == ERROR_SUCCESS) {
|
||||
nsString ws(strTemp, (strTempSize / sizeof(wchar_t)) - 1);
|
||||
ToLowerCase(ws); // To facilitate searches
|
||||
Unused << aOut.emplaceBack(ws);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ModuleEvaluator::ModuleEvaluator() {
|
||||
GetKeyboardLayoutDlls(mKeyboardLayoutDlls);
|
||||
|
||||
nsresult rv =
|
||||
NS_GetSpecialDirectory(NS_OS_SYSTEM_DIR, getter_AddRefs(mSysDirectory));
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
|
||||
nsCOMPtr<nsIFile> winSxSDir;
|
||||
rv = NS_GetSpecialDirectory(NS_WIN_WINDOWS_DIR, getter_AddRefs(winSxSDir));
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
rv = winSxSDir->Append(NS_LITERAL_STRING("WinSxS"));
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
mWinSxSDirectory = std::move(winSxSDir);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _M_IX86
|
||||
WCHAR sysWow64Buf[MAX_PATH + 1] = {};
|
||||
|
||||
UINT sysWowLen =
|
||||
::GetSystemWow64DirectoryW(sysWow64Buf, ArrayLength(sysWow64Buf));
|
||||
if (sysWowLen > 0 && sysWowLen < ArrayLength(sysWow64Buf)) {
|
||||
rv = NS_NewLocalFile(nsDependentString(sysWow64Buf, sysWowLen), false,
|
||||
getter_AddRefs(mSysWOW64Directory));
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
}
|
||||
#endif // _M_IX86
|
||||
|
||||
rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(mExeDirectory));
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
|
||||
nsCOMPtr<nsIFile> exeFile;
|
||||
rv = XRE_GetBinaryPath(getter_AddRefs(exeFile));
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
nsAutoString exePath;
|
||||
rv = exeFile->GetPath(exePath);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
ModuleVersionInfo exeVi;
|
||||
if (exeVi.GetFromImage(exePath)) {
|
||||
mExeVersion = Some(exeVi.mFileVersion.Version64());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Maybe<bool> ModuleEvaluator::IsModuleTrusted(
|
||||
ModuleLoadEvent::ModuleInfo& aDllInfo, const ModuleLoadEvent& aEvent,
|
||||
Authenticode* aSvc) const {
|
||||
MOZ_ASSERT(aDllInfo.mTrustFlags == ModuleTrustFlags::None);
|
||||
MOZ_ASSERT(aDllInfo.mFile);
|
||||
|
||||
nsAutoString dllFullPath;
|
||||
if (NS_FAILED(aDllInfo.mFile->GetPath(dllFullPath))) {
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
// We start by checking authenticode signatures, since any result from this
|
||||
// test will produce an immediate pass/fail.
|
||||
if (aSvc) {
|
||||
UniquePtr<wchar_t[]> szSignedBy = aSvc->GetBinaryOrgName(dllFullPath.get());
|
||||
|
||||
if (szSignedBy) {
|
||||
nsDependentString signedBy(szSignedBy.get());
|
||||
|
||||
if (signedBy.EqualsLiteral("Microsoft Windows")) {
|
||||
aDllInfo.mTrustFlags |= ModuleTrustFlags::MicrosoftWindowsSignature;
|
||||
return Some(true);
|
||||
} else if (signedBy.EqualsLiteral("Microsoft Corporation")) {
|
||||
aDllInfo.mTrustFlags |= ModuleTrustFlags::MicrosoftWindowsSignature;
|
||||
return Some(true);
|
||||
} else if (signedBy.EqualsLiteral("Mozilla Corporation")) {
|
||||
aDllInfo.mTrustFlags |= ModuleTrustFlags::MozillaSignature;
|
||||
return Some(true);
|
||||
} else {
|
||||
// Being signed by somebody who is neither Microsoft nor us is an
|
||||
// automatic disqualification.
|
||||
aDllInfo.mTrustFlags = ModuleTrustFlags::None;
|
||||
return Some(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nsAutoString dllDirectory;
|
||||
if (!GetDirectoryName(aDllInfo.mFile, dllDirectory)) {
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
nsAutoString dllLeafLower;
|
||||
if (NS_FAILED(aDllInfo.mFile->GetLeafName(dllLeafLower))) {
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
ToLowerCase(dllLeafLower); // To facilitate case-insensitive searching
|
||||
|
||||
// The JIT profiling module doesn't really have any other practical way to
|
||||
// match; hard-code it as being trusted.
|
||||
if (dllLeafLower.EqualsLiteral("jitpi.dll")) {
|
||||
aDllInfo.mTrustFlags = ModuleTrustFlags::JitPI;
|
||||
return Some(true);
|
||||
}
|
||||
|
||||
// Accumulate a trustworthiness score as the module passes through several
|
||||
// checks. If the score ever reaches above the threshold, it's considered
|
||||
// trusted.
|
||||
uint32_t scoreThreshold = 100;
|
||||
|
||||
#ifdef ENABLE_TESTS
|
||||
// Check whether we are running as an xpcshell test.
|
||||
if (mozilla::EnvHasValue("XPCSHELL_TEST_PROFILE_DIR")) {
|
||||
// During xpcshell tests, these DLLs are hard-coded to pass through all
|
||||
// criteria checks and still result in "untrusted" status, so they show up
|
||||
// in the untrusted modules ping for the test to examine.
|
||||
// Setting the threshold very high ensures the test will cover all criteria.
|
||||
if (dllLeafLower.EqualsLiteral("untrusted-startup-test-dll.dll") ||
|
||||
dllLeafLower.EqualsLiteral("modules-test.dll")) {
|
||||
scoreThreshold = 99999;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
nsresult rv;
|
||||
bool contained;
|
||||
|
||||
uint32_t score = 0;
|
||||
|
||||
if (score < scoreThreshold) {
|
||||
// Is the DLL in the system directory?
|
||||
rv = mSysDirectory->Contains(aDllInfo.mFile, &contained);
|
||||
if (NS_SUCCEEDED(rv) && contained) {
|
||||
aDllInfo.mTrustFlags |= ModuleTrustFlags::SystemDirectory;
|
||||
score += 50;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _M_IX86
|
||||
// Under WOW64, SysWOW64 is the effective system directory. Give SysWOW64 the
|
||||
// same trustworthiness as ModuleTrustFlags::SystemDirectory.
|
||||
if (mSysWOW64Directory) {
|
||||
rv = mSysWOW64Directory->Contains(aDllInfo.mFile, &contained);
|
||||
if (NS_SUCCEEDED(rv) && contained) {
|
||||
aDllInfo.mTrustFlags |= ModuleTrustFlags::SysWOW64Directory;
|
||||
score += 50;
|
||||
}
|
||||
}
|
||||
#endif // _M_IX86
|
||||
|
||||
// Is the DLL in the WinSxS directory? Some Microsoft DLLs (e.g. comctl32) are
|
||||
// loaded from here and don't have digital signatures. So while this is not a
|
||||
// guarantee of trustworthiness, but is at least as valid as system32.
|
||||
rv = mWinSxSDirectory->Contains(aDllInfo.mFile, &contained);
|
||||
if (NS_SUCCEEDED(rv) && contained) {
|
||||
aDllInfo.mTrustFlags |= ModuleTrustFlags::WinSxSDirectory;
|
||||
score += 50;
|
||||
}
|
||||
|
||||
// Is it a keyboard layout DLL?
|
||||
if (std::find(mKeyboardLayoutDlls.begin(), mKeyboardLayoutDlls.end(),
|
||||
dllLeafLower) != mKeyboardLayoutDlls.end()) {
|
||||
aDllInfo.mTrustFlags |= ModuleTrustFlags::KeyboardLayout;
|
||||
// This doesn't guarantee trustworthiness by itself. Keyboard layouts also
|
||||
// must be in the system directory, which will bump the score >= 100.
|
||||
score += 50;
|
||||
}
|
||||
|
||||
if (score < scoreThreshold) {
|
||||
ModuleVersionInfo vi;
|
||||
if (vi.GetFromImage(dllFullPath)) {
|
||||
aDllInfo.mFileVersion = vi.mFileVersion.ToString();
|
||||
|
||||
if (vi.mCompanyName.EqualsLiteral("Microsoft Corporation")) {
|
||||
aDllInfo.mTrustFlags |= ModuleTrustFlags::MicrosoftVersion;
|
||||
score += 50;
|
||||
}
|
||||
|
||||
rv = mExeDirectory->Contains(aDllInfo.mFile, &contained);
|
||||
if (NS_SUCCEEDED(rv) && contained) {
|
||||
score += 50;
|
||||
aDllInfo.mTrustFlags |= ModuleTrustFlags::FirefoxDirectory;
|
||||
|
||||
if (dllLeafLower.EqualsLiteral("xul.dll")) {
|
||||
// The caller wants to know if this DLL is xul.dll, but this flag
|
||||
// doesn't need to affect trust score. Xul will be considered trusted
|
||||
// by other measures.
|
||||
aDllInfo.mTrustFlags |= ModuleTrustFlags::Xul;
|
||||
}
|
||||
|
||||
// If it's in the Firefox directory, does it also share the Firefox
|
||||
// version info? We only care about this inside the app directory.
|
||||
if (mExeVersion.isSome() &&
|
||||
(vi.mFileVersion.Version64() == mExeVersion.value())) {
|
||||
aDllInfo.mTrustFlags |= ModuleTrustFlags::FirefoxDirectoryAndVersion;
|
||||
score += 50;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Some(score >= scoreThreshold);
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,142 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_ModuleEvaluator_windows_h
|
||||
#define mozilla_ModuleEvaluator_windows_h
|
||||
|
||||
#include "mozilla/glue/WindowsDllServices.h"
|
||||
#include "mozilla/Authenticode.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/ModuleVersionInfo_windows.h"
|
||||
#include "mozilla/TypedEnumBits.h"
|
||||
#include "mozilla/Vector.h"
|
||||
#include "nsString.h"
|
||||
|
||||
class InfallibleAllocPolicy;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
enum class ModuleTrustFlags : uint32_t {
|
||||
None = 0,
|
||||
MozillaSignature = 1,
|
||||
MicrosoftWindowsSignature = 2,
|
||||
MicrosoftVersion = 4,
|
||||
FirefoxDirectory = 8,
|
||||
FirefoxDirectoryAndVersion = 0x10,
|
||||
SystemDirectory = 0x20,
|
||||
KeyboardLayout = 0x40,
|
||||
JitPI = 0x80,
|
||||
WinSxSDirectory = 0x100,
|
||||
Xul = 0x200,
|
||||
SysWOW64Directory = 0x400,
|
||||
};
|
||||
|
||||
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ModuleTrustFlags);
|
||||
|
||||
// This class is very similar to mozilla::glue::ModuleLoadEvent, except this is
|
||||
// more Gecko-friendly, and has a few more fields that enable us to evaluate
|
||||
// trustworthiness.
|
||||
class ModuleLoadEvent {
|
||||
public:
|
||||
class ModuleInfo {
|
||||
public:
|
||||
ModuleInfo() = delete;
|
||||
ModuleInfo(const ModuleInfo&) = default;
|
||||
ModuleInfo(ModuleInfo&&) = default;
|
||||
ModuleInfo& operator=(const ModuleInfo&) = default;
|
||||
ModuleInfo& operator=(ModuleInfo&&) = default;
|
||||
|
||||
explicit ModuleInfo(uintptr_t aBase);
|
||||
|
||||
// Construct from the mozilla::glue version of this class.
|
||||
explicit ModuleInfo(const glue::ModuleLoadEvent::ModuleInfo&);
|
||||
|
||||
bool PopulatePathInfo();
|
||||
bool PrepForTelemetry();
|
||||
|
||||
uintptr_t mBase;
|
||||
nsString mLdrName;
|
||||
nsCOMPtr<nsIFile> mFile; // Path as reported by GetModuleFileName()
|
||||
Maybe<double> mLoadDurationMS;
|
||||
|
||||
// The following members are populated as we evaluate the module.
|
||||
nsString mFilePathClean; // Path sanitized for telemetry reporting.
|
||||
ModuleTrustFlags mTrustFlags;
|
||||
nsCString mFileVersion;
|
||||
};
|
||||
|
||||
ModuleLoadEvent() = default;
|
||||
ModuleLoadEvent(ModuleLoadEvent&&) = default;
|
||||
|
||||
// The standard ModuleLoadEvent copy constructor "copy everything" behavior
|
||||
// can be tweaked to suit the context using this option.
|
||||
enum class CopyOption : int {
|
||||
CopyEverything, // Default
|
||||
CopyWithoutModules,
|
||||
};
|
||||
|
||||
ModuleLoadEvent(const ModuleLoadEvent& aOther,
|
||||
CopyOption aOption = CopyOption::CopyEverything);
|
||||
|
||||
ModuleLoadEvent& operator=(ModuleLoadEvent&& aOther) = default;
|
||||
ModuleLoadEvent& operator=(const ModuleLoadEvent& aOther) = delete;
|
||||
|
||||
// Copy-construct from the mozilla::glue version of this class.
|
||||
explicit ModuleLoadEvent(const glue::ModuleLoadEvent& aOther);
|
||||
|
||||
// Indicates whether the DLL was already loaded before we had a chance to
|
||||
// capture live info about it such as uptime / stack / thread.
|
||||
bool mIsStartup;
|
||||
DWORD mThreadID;
|
||||
nsCString mThreadName;
|
||||
uint64_t mProcessUptimeMS;
|
||||
Vector<uintptr_t, 0, InfallibleAllocPolicy> mStack;
|
||||
Vector<ModuleInfo, 0, InfallibleAllocPolicy> mModules;
|
||||
};
|
||||
|
||||
// This class performs trustworthiness evaluation for incoming DLLs.
|
||||
class ModuleEvaluator {
|
||||
Maybe<uint64_t> mExeVersion; // Version number of the running EXE image
|
||||
nsCOMPtr<nsIFile> mExeDirectory;
|
||||
nsCOMPtr<nsIFile> mSysDirectory;
|
||||
nsCOMPtr<nsIFile> mWinSxSDirectory;
|
||||
#ifdef _M_IX86
|
||||
nsCOMPtr<nsIFile> mSysWOW64Directory;
|
||||
#endif // _M_IX86
|
||||
Vector<nsString, 0, InfallibleAllocPolicy> mKeyboardLayoutDlls;
|
||||
|
||||
public:
|
||||
ModuleEvaluator();
|
||||
|
||||
explicit operator bool() const {
|
||||
// We exclude mSysWOW64Directory as it may not always be present
|
||||
return mExeVersion.isSome() && mExeDirectory && mSysDirectory &&
|
||||
mWinSxSDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the trustworthiness of the given module and fills in remaining
|
||||
* fields in ModuleInfo.
|
||||
*
|
||||
* @param aDllInfo [in,out] Info about the DLL in question.
|
||||
* The following members must be valid:
|
||||
* mBase
|
||||
* mFile
|
||||
* This function fills in remaining fields of aDllInfo.
|
||||
* @param aEvent [in] Info about the relevant event that contains this DLL.
|
||||
* @param aSvc [in] Pointer to the service we use for Authenticode ops
|
||||
* @return Some(true) if the given module is trusted.
|
||||
* Some(false) if the given module is untrusted.
|
||||
* Nothing() if there was a failure.
|
||||
*/
|
||||
Maybe<bool> IsModuleTrusted(ModuleLoadEvent::ModuleInfo& aDllInfo,
|
||||
const ModuleLoadEvent& aEvent,
|
||||
Authenticode* aSvc) const;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_ModuleEvaluator_windows_h
|
|
@ -4,7 +4,7 @@
|
|||
* 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 "ModuleVersionInfo.h"
|
||||
#include "ModuleVersionInfo_windows.h"
|
||||
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
|
@ -4,8 +4,8 @@
|
|||
* 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/. */
|
||||
|
||||
#ifndef mozilla_ModuleVersionInfo_h
|
||||
#define mozilla_ModuleVersionInfo_h
|
||||
#ifndef mozilla_ModuleVersionInfo_windows_h
|
||||
#define mozilla_ModuleVersionInfo_windows_h
|
||||
|
||||
#include <windows.h>
|
||||
#include "nsString.h"
|
||||
|
@ -68,4 +68,4 @@ class ModuleVersionInfo {
|
|||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_ModuleVersionInfo_h
|
||||
#endif // mozilla_ModuleVersionInfo_windows_h
|
|
@ -1,358 +0,0 @@
|
|||
/* -*- 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 "UntrustedModulesData.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "mozilla/CmdLineAndEnvUtils.h"
|
||||
#include "mozilla/FileUtilsWin.h"
|
||||
#include "mozilla/Likely.h"
|
||||
#include "mozilla/MathAlgorithms.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "mozilla/WinDllServices.h"
|
||||
#include "ModuleEvaluator.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
#include "WinUtils.h"
|
||||
|
||||
// Some utility functions
|
||||
|
||||
static LONGLONG GetQPCFreq() {
|
||||
static const LONGLONG sFreq = []() -> LONGLONG {
|
||||
LARGE_INTEGER freq;
|
||||
::QueryPerformanceFrequency(&freq);
|
||||
return freq.QuadPart;
|
||||
}();
|
||||
|
||||
return sFreq;
|
||||
}
|
||||
|
||||
template <typename ReturnT>
|
||||
static ReturnT QPCToTimeUnits(const LONGLONG aTimeStamp,
|
||||
const LONGLONG aUnitsPerSec) {
|
||||
return ReturnT(aTimeStamp * aUnitsPerSec) / ReturnT(GetQPCFreq());
|
||||
}
|
||||
|
||||
template <typename ReturnT>
|
||||
static ReturnT QPCToMilliseconds(const LONGLONG aTimeStamp) {
|
||||
const LONGLONG kMillisecondsPerSec = 1000;
|
||||
return QPCToTimeUnits<ReturnT>(aTimeStamp, kMillisecondsPerSec);
|
||||
}
|
||||
|
||||
template <typename ReturnT>
|
||||
static ReturnT QPCToMicroseconds(const LONGLONG aTimeStamp) {
|
||||
const LONGLONG kMicrosecondsPerSec = 1000000;
|
||||
return QPCToTimeUnits<ReturnT>(aTimeStamp, kMicrosecondsPerSec);
|
||||
}
|
||||
|
||||
static LONGLONG TimeUnitsToQPC(const LONGLONG aTimeStamp,
|
||||
const LONGLONG aUnitsPerSec) {
|
||||
MOZ_ASSERT(aUnitsPerSec != 0);
|
||||
|
||||
LONGLONG result = aTimeStamp;
|
||||
result *= GetQPCFreq();
|
||||
result /= aUnitsPerSec;
|
||||
return result;
|
||||
}
|
||||
|
||||
static Maybe<double> QPCLoadDurationToMilliseconds(
|
||||
const ModuleLoadInfo& aNtInfo) {
|
||||
if (aNtInfo.IsBare()) {
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
return Some(QPCToMilliseconds<double>(aNtInfo.mLoadTimeInfo.QuadPart));
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
ModuleRecord::ModuleRecord(const nsAString& aResolvedPath)
|
||||
: mTrustFlags(ModuleTrustFlags::None) {
|
||||
if (aResolvedPath.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsresult rv =
|
||||
NS_NewLocalFile(aResolvedPath, false, getter_AddRefs(mResolvedDllName));
|
||||
if (NS_FAILED(rv) || !mResolvedDllName) {
|
||||
return;
|
||||
}
|
||||
|
||||
GetVersionAndVendorInfo(aResolvedPath);
|
||||
|
||||
// Now sanitize the resolved DLL name. If we cannot sanitize this then this
|
||||
// record must not be considered valid.
|
||||
nsAutoString strSanitizedPath(aResolvedPath);
|
||||
if (!widget::WinUtils::PreparePathForTelemetry(strSanitizedPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mSanitizedDllName = strSanitizedPath;
|
||||
}
|
||||
|
||||
void ModuleRecord::GetVersionAndVendorInfo(const nsAString& aPath) {
|
||||
RefPtr<DllServices> dllSvc(DllServices::Get());
|
||||
UniquePtr<wchar_t[]> signedBy(
|
||||
dllSvc->GetBinaryOrgName(PromiseFlatString(aPath).get()));
|
||||
if (signedBy) {
|
||||
mVendorInfo = Some(VendorInfo(VendorInfo::Source::Signature,
|
||||
nsDependentString(signedBy.get())));
|
||||
}
|
||||
|
||||
ModuleVersionInfo verInfo;
|
||||
if (!verInfo.GetFromImage(aPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (verInfo.mFileVersion.Version64()) {
|
||||
mVersion = Some(ModuleVersion(verInfo.mFileVersion.Version64()));
|
||||
}
|
||||
|
||||
if (!mVendorInfo && !verInfo.mCompanyName.IsEmpty()) {
|
||||
mVendorInfo =
|
||||
Some(VendorInfo(VendorInfo::Source::VersionInfo, verInfo.mCompanyName));
|
||||
}
|
||||
}
|
||||
|
||||
bool ModuleRecord::IsXUL() const {
|
||||
if (!mResolvedDllName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsAutoString leafName;
|
||||
nsresult rv = mResolvedDllName->GetLeafName(leafName);
|
||||
if (NS_FAILED(rv)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return leafName.EqualsIgnoreCase("xul.dll");
|
||||
}
|
||||
|
||||
int32_t ModuleRecord::GetScoreThreshold() const {
|
||||
#ifdef ENABLE_TESTS
|
||||
// Check whether we are running as an xpcshell test.
|
||||
if (MOZ_UNLIKELY(mozilla::EnvHasValue("XPCSHELL_TEST_PROFILE_DIR"))) {
|
||||
nsAutoString dllLeaf;
|
||||
if (NS_SUCCEEDED(mResolvedDllName->GetLeafName(dllLeaf))) {
|
||||
// During xpcshell tests, this DLL is hard-coded to pass through all
|
||||
// criteria checks and still result in "untrusted" status, so it shows up
|
||||
// in the untrusted modules ping for the test to examine.
|
||||
// Setting the threshold very high ensures the test will cover all
|
||||
// criteria.
|
||||
if (dllLeaf.EqualsIgnoreCase("modules-test.dll")) {
|
||||
return 99999;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return 100;
|
||||
}
|
||||
|
||||
bool ModuleRecord::IsTrusted() const {
|
||||
if (mTrustFlags == ModuleTrustFlags::None) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// These flags are immediate passes
|
||||
if (mTrustFlags &
|
||||
(ModuleTrustFlags::MicrosoftWindowsSignature |
|
||||
ModuleTrustFlags::MozillaSignature | ModuleTrustFlags::JitPI)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The remaining flags, when set, each count for 50 points toward a
|
||||
// trustworthiness score.
|
||||
int32_t score = static_cast<int32_t>(
|
||||
CountPopulation32(static_cast<uint32_t>(mTrustFlags))) *
|
||||
50;
|
||||
return score >= GetScoreThreshold();
|
||||
}
|
||||
|
||||
ProcessedModuleLoadEvent::ProcessedModuleLoadEvent(
|
||||
glue::EnhancedModuleLoadInfo&& aModLoadInfo,
|
||||
RefPtr<ModuleRecord>&& aModuleRecord)
|
||||
: mProcessUptimeMS(QPCTimeStampToProcessUptimeMilliseconds(
|
||||
aModLoadInfo.mNtLoadInfo.mBeginTimestamp)),
|
||||
mLoadDurationMS(QPCLoadDurationToMilliseconds(aModLoadInfo.mNtLoadInfo)),
|
||||
mThreadId(aModLoadInfo.mNtLoadInfo.mThreadId),
|
||||
mThreadName(std::move(aModLoadInfo.mThreadName)),
|
||||
mBaseAddress(
|
||||
reinterpret_cast<uintptr_t>(aModLoadInfo.mNtLoadInfo.mBaseAddr)),
|
||||
mModule(std::move(aModuleRecord)) {
|
||||
if (!mModule || !(*mModule)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sanitize the requested DLL name. It is not a critical failure if we
|
||||
// cannot do so; we simply do not provide that field to Telemetry.
|
||||
nsAutoString strRequested(
|
||||
aModLoadInfo.mNtLoadInfo.mRequestedDllName.AsString());
|
||||
if (!strRequested.IsEmpty() &&
|
||||
widget::WinUtils::PreparePathForTelemetry(strRequested)) {
|
||||
mRequestedDllName = strRequested;
|
||||
}
|
||||
}
|
||||
|
||||
/* static */
|
||||
Maybe<LONGLONG>
|
||||
ProcessedModuleLoadEvent::ComputeQPCTimeStampForProcessCreation() {
|
||||
// This is similar to the algorithm used by TimeStamp::ProcessCreation:
|
||||
|
||||
// 1. Get current timestamps as both QPC and FILETIME;
|
||||
LARGE_INTEGER nowQPC;
|
||||
::QueryPerformanceCounter(&nowQPC);
|
||||
|
||||
SYSTEMTIME nowSys;
|
||||
::GetSystemTime(&nowSys);
|
||||
|
||||
FILETIME nowFile;
|
||||
if (!::SystemTimeToFileTime(&nowSys, &nowFile)) {
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
// 2. Get the process creation timestamp as FILETIME;
|
||||
FILETIME creationTime, exitTime, kernelTime, userTime;
|
||||
if (!::GetProcessTimes(::GetCurrentProcess(), &creationTime, &exitTime,
|
||||
&kernelTime, &userTime)) {
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
// 3. Take the difference between the FILETIMEs from (1) and (2),
|
||||
// respectively, yielding the elapsed process uptime in microseconds.
|
||||
ULARGE_INTEGER ulCreation = {
|
||||
{creationTime.dwLowDateTime, creationTime.dwHighDateTime}};
|
||||
ULARGE_INTEGER ulNow = {{nowFile.dwLowDateTime, nowFile.dwHighDateTime}};
|
||||
|
||||
ULONGLONG timeSinceCreationMicroSec =
|
||||
(ulNow.QuadPart - ulCreation.QuadPart) / 10ULL;
|
||||
|
||||
// 4. Convert the QPC timestamp from (1) to microseconds.
|
||||
LONGLONG nowQPCMicroSec = QPCToMicroseconds<LONGLONG>(nowQPC.QuadPart);
|
||||
|
||||
// 5. Convert the elapsed uptime to an absolute timestamp by subtracting
|
||||
// from (4), which yields the absolute timestamp for process creation.
|
||||
// We convert back to QPC units before returning.
|
||||
const LONGLONG kMicrosecondsPerSec = 1000000;
|
||||
return Some(TimeUnitsToQPC(nowQPCMicroSec - timeSinceCreationMicroSec,
|
||||
kMicrosecondsPerSec));
|
||||
}
|
||||
|
||||
/* static */
|
||||
uint64_t ProcessedModuleLoadEvent::QPCTimeStampToProcessUptimeMilliseconds(
|
||||
const LARGE_INTEGER& aTimeStamp) {
|
||||
static const Maybe<LONGLONG> sProcessCreationTimeStamp =
|
||||
ComputeQPCTimeStampForProcessCreation();
|
||||
|
||||
if (!sProcessCreationTimeStamp) {
|
||||
return 0ULL;
|
||||
}
|
||||
|
||||
LONGLONG diff = aTimeStamp.QuadPart - sProcessCreationTimeStamp.value();
|
||||
return QPCToMilliseconds<uint64_t>(diff);
|
||||
}
|
||||
|
||||
bool ProcessedModuleLoadEvent::IsXULLoad() const {
|
||||
if (!mModule || !mLoadDurationMS || !IsTrusted()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return mModule->IsXUL();
|
||||
}
|
||||
|
||||
bool ProcessedModuleLoadEvent::IsTrusted() const {
|
||||
if (!mModule) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return mModule->IsTrusted();
|
||||
}
|
||||
|
||||
void UntrustedModulesData::AddNewLoads(
|
||||
const ModulesMap& aModules, Vector<ProcessedModuleLoadEvent>&& aEvents,
|
||||
Vector<Telemetry::ProcessedStack>&& aStacks) {
|
||||
MOZ_ASSERT(aEvents.length() == aStacks.length());
|
||||
|
||||
for (auto iter = aModules.ConstIter(); !iter.Done(); iter.Next()) {
|
||||
if (iter.Data()->IsTrusted()) {
|
||||
// Filter out trusted module records
|
||||
continue;
|
||||
}
|
||||
|
||||
auto addPtr = mModules.LookupForAdd(iter.Key());
|
||||
if (addPtr) {
|
||||
// |mModules| already contains this record
|
||||
continue;
|
||||
}
|
||||
|
||||
RefPtr<ModuleRecord> rec(iter.Data());
|
||||
addPtr.OrInsert([rec = std::move(rec)]() { return rec; });
|
||||
}
|
||||
|
||||
// This constant matches the maximum in Telemetry::CombinedStacks
|
||||
const size_t kMaxEvents = 50;
|
||||
MOZ_ASSERT(mEvents.length() <= kMaxEvents);
|
||||
|
||||
if (mEvents.length() + aEvents.length() > kMaxEvents) {
|
||||
// Ensure that we will never retain more tha kMaxEvents events
|
||||
size_t newLength = kMaxEvents - mEvents.length();
|
||||
if (!newLength) {
|
||||
return;
|
||||
}
|
||||
|
||||
aEvents.shrinkTo(newLength);
|
||||
aStacks.shrinkTo(newLength);
|
||||
}
|
||||
|
||||
if (mEvents.empty()) {
|
||||
mEvents = std::move(aEvents);
|
||||
} else {
|
||||
Unused << mEvents.reserve(mEvents.length() + aEvents.length());
|
||||
for (auto&& event : aEvents) {
|
||||
Unused << mEvents.emplaceBack(std::move(event));
|
||||
}
|
||||
}
|
||||
|
||||
for (auto&& stack : aStacks) {
|
||||
mStacks.AddStack(stack);
|
||||
}
|
||||
}
|
||||
|
||||
void UntrustedModulesData::Swap(UntrustedModulesData& aOther) {
|
||||
GeckoProcessType tmpProcessType = mProcessType;
|
||||
mProcessType = aOther.mProcessType;
|
||||
aOther.mProcessType = tmpProcessType;
|
||||
|
||||
DWORD tmpPid = mPid;
|
||||
mPid = aOther.mPid;
|
||||
aOther.mPid = tmpPid;
|
||||
|
||||
TimeDuration tmpElapsed = mElapsed;
|
||||
mElapsed = aOther.mElapsed;
|
||||
aOther.mElapsed = tmpElapsed;
|
||||
|
||||
mModules.SwapElements(aOther.mModules);
|
||||
mEvents.swap(aOther.mEvents);
|
||||
mStacks.Swap(aOther.mStacks);
|
||||
|
||||
Maybe<double> tmpXULLoadDurationMS = mXULLoadDurationMS;
|
||||
mXULLoadDurationMS = aOther.mXULLoadDurationMS;
|
||||
aOther.mXULLoadDurationMS = tmpXULLoadDurationMS;
|
||||
|
||||
uint32_t tmpSanitizationFailures = mSanitizationFailures;
|
||||
mSanitizationFailures = aOther.mSanitizationFailures;
|
||||
aOther.mSanitizationFailures = tmpSanitizationFailures;
|
||||
|
||||
uint32_t tmpTrustTestFailures = mTrustTestFailures;
|
||||
mTrustTestFailures = aOther.mTrustTestFailures;
|
||||
aOther.mTrustTestFailures = tmpTrustTestFailures;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -1,159 +0,0 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_UntrustedModulesData_h
|
||||
#define mozilla_UntrustedModulesData_h
|
||||
|
||||
#include "mozilla/CombinedStacks.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/TypedEnumBits.h"
|
||||
#include "mozilla/Vector.h"
|
||||
#include "mozilla/WinHeaderOnlyUtils.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "nsRefPtrHashtable.h"
|
||||
#include "nsString.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace glue {
|
||||
struct EnhancedModuleLoadInfo;
|
||||
} // namespace glue
|
||||
|
||||
enum class ModuleTrustFlags : uint32_t {
|
||||
None = 0,
|
||||
MozillaSignature = 1,
|
||||
MicrosoftWindowsSignature = 2,
|
||||
MicrosoftVersion = 4,
|
||||
FirefoxDirectory = 8,
|
||||
FirefoxDirectoryAndVersion = 0x10,
|
||||
SystemDirectory = 0x20,
|
||||
KeyboardLayout = 0x40,
|
||||
JitPI = 0x80,
|
||||
WinSxSDirectory = 0x100,
|
||||
};
|
||||
|
||||
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ModuleTrustFlags);
|
||||
|
||||
class VendorInfo final {
|
||||
public:
|
||||
enum class Source : uint32_t {
|
||||
Signature,
|
||||
VersionInfo,
|
||||
};
|
||||
|
||||
VendorInfo(const Source aSource, const nsAString& aVendor)
|
||||
: mSource(aSource), mVendor(aVendor) {
|
||||
MOZ_ASSERT(!aVendor.IsEmpty());
|
||||
}
|
||||
|
||||
Source mSource;
|
||||
nsString mVendor;
|
||||
};
|
||||
|
||||
class ModuleRecord final {
|
||||
public:
|
||||
explicit ModuleRecord(const nsAString& aResolvedPath);
|
||||
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ModuleRecord)
|
||||
|
||||
nsCOMPtr<nsIFile> mResolvedDllName;
|
||||
nsString mSanitizedDllName;
|
||||
Maybe<ModuleVersion> mVersion;
|
||||
Maybe<VendorInfo> mVendorInfo;
|
||||
ModuleTrustFlags mTrustFlags;
|
||||
|
||||
explicit operator bool() const { return !mSanitizedDllName.IsEmpty(); }
|
||||
bool IsXUL() const;
|
||||
bool IsTrusted() const;
|
||||
|
||||
ModuleRecord(const ModuleRecord&) = delete;
|
||||
ModuleRecord(ModuleRecord&&) = delete;
|
||||
|
||||
ModuleRecord& operator=(const ModuleRecord&) = delete;
|
||||
ModuleRecord& operator=(ModuleRecord&&) = delete;
|
||||
|
||||
private:
|
||||
~ModuleRecord() = default;
|
||||
void GetVersionAndVendorInfo(const nsAString& aPath);
|
||||
int32_t GetScoreThreshold() const;
|
||||
};
|
||||
|
||||
class ProcessedModuleLoadEvent final {
|
||||
public:
|
||||
ProcessedModuleLoadEvent(glue::EnhancedModuleLoadInfo&& aModLoadInfo,
|
||||
RefPtr<ModuleRecord>&& aModuleRecord);
|
||||
|
||||
explicit operator bool() const { return mModule && *mModule; }
|
||||
bool IsXULLoad() const;
|
||||
bool IsTrusted() const;
|
||||
|
||||
uint64_t mProcessUptimeMS;
|
||||
Maybe<double> mLoadDurationMS;
|
||||
DWORD mThreadId;
|
||||
nsCString mThreadName;
|
||||
nsString mRequestedDllName;
|
||||
// We intentionally store mBaseAddress as part of the event and not the
|
||||
// module, as relocation may cause it to change between loads. If so, we want
|
||||
// to know about it.
|
||||
uintptr_t mBaseAddress;
|
||||
RefPtr<ModuleRecord> mModule;
|
||||
|
||||
ProcessedModuleLoadEvent(const ProcessedModuleLoadEvent&) = delete;
|
||||
ProcessedModuleLoadEvent& operator=(const ProcessedModuleLoadEvent&) = delete;
|
||||
|
||||
ProcessedModuleLoadEvent(ProcessedModuleLoadEvent&&) = default;
|
||||
ProcessedModuleLoadEvent& operator=(ProcessedModuleLoadEvent&&) = default;
|
||||
|
||||
private:
|
||||
static Maybe<LONGLONG> ComputeQPCTimeStampForProcessCreation();
|
||||
static uint64_t QPCTimeStampToProcessUptimeMilliseconds(
|
||||
const LARGE_INTEGER& aTimeStamp);
|
||||
};
|
||||
|
||||
class UntrustedModulesData final {
|
||||
public:
|
||||
using ModulesMap = nsRefPtrHashtable<nsStringHashKey, ModuleRecord>;
|
||||
|
||||
UntrustedModulesData()
|
||||
: mProcessType(XRE_GetProcessType()),
|
||||
mPid(::GetCurrentProcessId()),
|
||||
mSanitizationFailures(0),
|
||||
mTrustTestFailures(0) {}
|
||||
|
||||
UntrustedModulesData(UntrustedModulesData&&) = default;
|
||||
UntrustedModulesData& operator=(UntrustedModulesData&&) = default;
|
||||
|
||||
UntrustedModulesData(const UntrustedModulesData&) = delete;
|
||||
UntrustedModulesData& operator=(const UntrustedModulesData&) = delete;
|
||||
|
||||
explicit operator bool() const {
|
||||
return !mEvents.empty() || mSanitizationFailures || mTrustTestFailures ||
|
||||
mXULLoadDurationMS.isSome();
|
||||
}
|
||||
|
||||
void AddNewLoads(const ModulesMap& aModulesMap,
|
||||
Vector<ProcessedModuleLoadEvent>&& aEvents,
|
||||
Vector<Telemetry::ProcessedStack>&& aStacks);
|
||||
|
||||
void Swap(UntrustedModulesData& aOther);
|
||||
|
||||
GeckoProcessType mProcessType;
|
||||
DWORD mPid;
|
||||
TimeDuration mElapsed;
|
||||
ModulesMap mModules;
|
||||
Vector<ProcessedModuleLoadEvent> mEvents;
|
||||
Telemetry::CombinedStacks mStacks;
|
||||
Maybe<double> mXULLoadDurationMS;
|
||||
uint32_t mSanitizationFailures;
|
||||
uint32_t mTrustTestFailures;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_UntrustedModulesData_h
|
|
@ -1,393 +0,0 @@
|
|||
/* -*- 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 "UntrustedModulesProcessor.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "mozilla/CmdLineAndEnvUtils.h"
|
||||
#include "mozilla/Likely.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "ModuleEvaluator.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/* static */
|
||||
RefPtr<UntrustedModulesProcessor> UntrustedModulesProcessor::Create() {
|
||||
#if defined(NIGHTLY_BUILD)
|
||||
if (!XRE_IsParentProcess()) {
|
||||
// Not currently supported outside the parent process
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!Telemetry::CanRecordReleaseData()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<UntrustedModulesProcessor> result(new UntrustedModulesProcessor());
|
||||
return result.forget();
|
||||
#else
|
||||
return nullptr;
|
||||
#endif // defined(NIGHTLY_BUILD)
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(UntrustedModulesProcessor, nsIObserver)
|
||||
|
||||
static const uint32_t kThreadTimeoutMS = 120000; // 2 minutes
|
||||
|
||||
UntrustedModulesProcessor::UntrustedModulesProcessor()
|
||||
: mThread(new LazyIdleThread(kThreadTimeoutMS,
|
||||
NS_LITERAL_CSTRING("Untrusted Modules"),
|
||||
LazyIdleThread::ManualShutdown)),
|
||||
mUnprocessedMutex(
|
||||
"mozilla::UntrustedModulesProcessor::mUnprocessedMutex"),
|
||||
mAllowProcessing(true) {
|
||||
AddObservers();
|
||||
}
|
||||
|
||||
void UntrustedModulesProcessor::AddObservers() {
|
||||
nsCOMPtr<nsIObserverService> obsServ(services::GetObserverService());
|
||||
obsServ->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false);
|
||||
obsServ->AddObserver(this, "xpcom-shutdown-threads", false);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP UntrustedModulesProcessor::Observe(nsISupports* aSubject,
|
||||
const char* aTopic,
|
||||
const char16_t* aData) {
|
||||
if (!strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID)) {
|
||||
// No more background processing allowed beyond this point
|
||||
mAllowProcessing = false;
|
||||
MutexAutoLock lock(mUnprocessedMutex);
|
||||
CancelScheduledProcessing(lock);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!strcmp(aTopic, "xpcom-shutdown-threads")) {
|
||||
mThread->Shutdown();
|
||||
|
||||
RemoveObservers();
|
||||
|
||||
mThread = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
MOZ_ASSERT_UNREACHABLE("Not reachable");
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void UntrustedModulesProcessor::RemoveObservers() {
|
||||
nsCOMPtr<nsIObserverService> obsServ(services::GetObserverService());
|
||||
obsServ->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
|
||||
obsServ->RemoveObserver(this, "xpcom-shutdown-threads");
|
||||
}
|
||||
|
||||
void UntrustedModulesProcessor::ScheduleNonEmptyQueueProcessing(
|
||||
const char* aSource, const MutexAutoLock& aProofOfLock) {
|
||||
// In case something tried to load a DLL during shutdown
|
||||
if (!mThread) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(ENABLE_TESTS)
|
||||
// Don't bother scheduling background processing in short-lived xpcshell
|
||||
// processes; it makes the test suites take too long.
|
||||
if (MOZ_UNLIKELY(mozilla::EnvHasValue("XPCSHELL_TEST_PROFILE_DIR"))) {
|
||||
return;
|
||||
}
|
||||
#endif // defined(ENABLE_TESTS)
|
||||
|
||||
if (mIdleRunnable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mAllowProcessing) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Schedule a runnable to trigger background processing once the main thread
|
||||
// has gone idle. We do it this way to ensure that we don't start doing a
|
||||
// bunch of processing during periods of heavy main thread activity.
|
||||
nsCOMPtr<nsIRunnable> idleRunnable(NewCancelableRunnableMethod<const char*>(
|
||||
"UntrustedModulesProcessor::DispatchBackgroundProcessing", this,
|
||||
&UntrustedModulesProcessor::DispatchBackgroundProcessing, aSource));
|
||||
|
||||
if (NS_FAILED(NS_DispatchToMainThreadQueue(do_AddRef(idleRunnable),
|
||||
EventQueuePriority::Idle))) {
|
||||
return;
|
||||
}
|
||||
|
||||
mIdleRunnable = std::move(idleRunnable);
|
||||
}
|
||||
|
||||
void UntrustedModulesProcessor::CancelScheduledProcessing(
|
||||
const MutexAutoLock& aProofOfLock) {
|
||||
if (!mIdleRunnable) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsICancelableRunnable> cancelable(do_QueryInterface(mIdleRunnable));
|
||||
if (cancelable) {
|
||||
// Stop the pending idle runnable from doing anything
|
||||
cancelable->Cancel();
|
||||
}
|
||||
|
||||
mIdleRunnable = nullptr;
|
||||
}
|
||||
|
||||
void UntrustedModulesProcessor::DispatchBackgroundProcessing(
|
||||
const char* aSource) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!mAllowProcessing) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIRunnable> runnable(NewRunnableMethod<const char*>(
|
||||
"UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue", this,
|
||||
&UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue, aSource));
|
||||
|
||||
mThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
void UntrustedModulesProcessor::Enqueue(
|
||||
glue::EnhancedModuleLoadInfo&& aModLoadInfo) {
|
||||
if (!mAllowProcessing) {
|
||||
return;
|
||||
}
|
||||
|
||||
MutexAutoLock lock(mUnprocessedMutex);
|
||||
|
||||
Unused << mUnprocessedModuleLoads.emplaceBack(std::move(aModLoadInfo));
|
||||
|
||||
ScheduleNonEmptyQueueProcessing(__func__, lock);
|
||||
}
|
||||
|
||||
void UntrustedModulesProcessor::Enqueue(ModuleLoadInfoVec&& aEvents) {
|
||||
if (!mAllowProcessing) {
|
||||
return;
|
||||
}
|
||||
|
||||
MutexAutoLock lock(mUnprocessedMutex);
|
||||
|
||||
for (auto& event : aEvents) {
|
||||
Unused << mUnprocessedModuleLoads.emplaceBack(std::move(event));
|
||||
}
|
||||
|
||||
ScheduleNonEmptyQueueProcessing(__func__, lock);
|
||||
}
|
||||
|
||||
void UntrustedModulesProcessor::AssertRunningOnLazyIdleThread() {
|
||||
#if defined(DEBUG)
|
||||
PRThread* curThread;
|
||||
PRThread* lazyIdleThread;
|
||||
|
||||
MOZ_ASSERT(NS_SUCCEEDED(NS_GetCurrentThread()->GetPRThread(&curThread)) &&
|
||||
NS_SUCCEEDED(mThread->GetPRThread(&lazyIdleThread)) &&
|
||||
curThread == lazyIdleThread);
|
||||
#endif // defined(DEBUG)
|
||||
}
|
||||
|
||||
RefPtr<UntrustedModulesPromise> UntrustedModulesProcessor::GetProcessedData() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
RefPtr<UntrustedModulesProcessor> self(this);
|
||||
return InvokeAsync(
|
||||
mThread->SerialEventTarget(), __func__,
|
||||
[self = std::move(self)]() { return self->GetProcessedDataInternal(); });
|
||||
}
|
||||
|
||||
RefPtr<UntrustedModulesPromise>
|
||||
UntrustedModulesProcessor::GetProcessedDataInternal() {
|
||||
AssertRunningOnLazyIdleThread();
|
||||
|
||||
ProcessModuleLoadQueue(__func__);
|
||||
|
||||
UntrustedModulesData result;
|
||||
|
||||
if (!mProcessedModuleLoads) {
|
||||
return UntrustedModulesPromise::CreateAndResolve(Nothing(), __func__);
|
||||
}
|
||||
|
||||
result.Swap(mProcessedModuleLoads);
|
||||
|
||||
result.mElapsed = TimeStamp::Now() - TimeStamp::ProcessCreation();
|
||||
|
||||
return UntrustedModulesPromise::CreateAndResolve(
|
||||
Some(UntrustedModulesData(std::move(result))), __func__);
|
||||
}
|
||||
|
||||
class MOZ_RAII BackgroundPriorityRegion final {
|
||||
public:
|
||||
BackgroundPriorityRegion()
|
||||
: mIsBackground(::SetThreadPriority(::GetCurrentThread(),
|
||||
THREAD_MODE_BACKGROUND_BEGIN)) {}
|
||||
|
||||
~BackgroundPriorityRegion() {
|
||||
if (!mIsBackground) {
|
||||
return;
|
||||
}
|
||||
|
||||
::SetThreadPriority(::GetCurrentThread(), THREAD_MODE_BACKGROUND_END);
|
||||
}
|
||||
|
||||
BackgroundPriorityRegion(const BackgroundPriorityRegion&) = delete;
|
||||
BackgroundPriorityRegion(BackgroundPriorityRegion&&) = delete;
|
||||
BackgroundPriorityRegion& operator=(const BackgroundPriorityRegion&) = delete;
|
||||
BackgroundPriorityRegion& operator=(BackgroundPriorityRegion&&) = delete;
|
||||
|
||||
private:
|
||||
const BOOL mIsBackground;
|
||||
};
|
||||
|
||||
void UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue(
|
||||
const char* aSource) {
|
||||
BackgroundPriorityRegion bgRgn;
|
||||
ProcessModuleLoadQueue(aSource);
|
||||
}
|
||||
|
||||
RefPtr<ModuleRecord> UntrustedModulesProcessor::GetModuleRecord(
|
||||
UntrustedModulesData::ModulesMap& aModules, const ModuleEvaluator& aModEval,
|
||||
const nsAString& aResolvedNtPath) {
|
||||
nsAutoString resolvedDosPath;
|
||||
if (!NtPathToDosPath(aResolvedNtPath, resolvedDosPath)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto addPtr = aModules.LookupForAdd(resolvedDosPath);
|
||||
if (addPtr) {
|
||||
return addPtr.Data();
|
||||
}
|
||||
|
||||
RefPtr<ModuleRecord> newMod(new ModuleRecord(resolvedDosPath));
|
||||
|
||||
Maybe<ModuleTrustFlags> maybeTrust = aModEval.GetTrust(*newMod);
|
||||
if (maybeTrust.isNothing()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
newMod->mTrustFlags = maybeTrust.value();
|
||||
|
||||
addPtr.OrInsert([newMod]() { return newMod; });
|
||||
|
||||
return newMod;
|
||||
}
|
||||
|
||||
// This function contains multiple |mAllowProcessing| checks so that we can
|
||||
// quickly bail out at the first sign of shutdown. This may be important when
|
||||
// the current thread is running under background priority.
|
||||
void UntrustedModulesProcessor::ProcessModuleLoadQueue(const char* aSource) {
|
||||
Vector<glue::EnhancedModuleLoadInfo> loadsToProcess;
|
||||
|
||||
{ // Scope for lock
|
||||
MutexAutoLock lock(mUnprocessedMutex);
|
||||
CancelScheduledProcessing(lock);
|
||||
loadsToProcess.swap(mUnprocessedModuleLoads);
|
||||
}
|
||||
|
||||
if (!mAllowProcessing) {
|
||||
return;
|
||||
}
|
||||
|
||||
ModuleEvaluator modEval;
|
||||
MOZ_ASSERT(!!modEval);
|
||||
if (!modEval) {
|
||||
return;
|
||||
}
|
||||
|
||||
Telemetry::BatchProcessedStackGenerator stackProcessor;
|
||||
nsRefPtrHashtable<nsStringHashKey, ModuleRecord> modules;
|
||||
|
||||
Maybe<double> maybeXulLoadDuration;
|
||||
Vector<Telemetry::ProcessedStack> processedStacks;
|
||||
Vector<ProcessedModuleLoadEvent> processedEvents;
|
||||
uint32_t sanitizationFailures = 0;
|
||||
uint32_t trustTestFailures = 0;
|
||||
|
||||
for (glue::EnhancedModuleLoadInfo& entry : loadsToProcess) {
|
||||
if (!mAllowProcessing) {
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<ModuleRecord> module(GetModuleRecord(
|
||||
modules, modEval, entry.mNtLoadInfo.mSectionName.AsString()));
|
||||
if (!module) {
|
||||
// We failed to obtain trust information about the module.
|
||||
// Don't include test failures in the ping to avoid flooding it.
|
||||
++trustTestFailures;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!mAllowProcessing) {
|
||||
return;
|
||||
}
|
||||
|
||||
glue::EnhancedModuleLoadInfo::BacktraceType backtrace =
|
||||
std::move(entry.mNtLoadInfo.mBacktrace);
|
||||
ProcessedModuleLoadEvent event(std::move(entry), std::move(module));
|
||||
|
||||
if (!event) {
|
||||
// We don't have a sanitized DLL path, so we cannot include this event
|
||||
// for privacy reasons.
|
||||
++sanitizationFailures;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!mAllowProcessing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.IsTrusted()) {
|
||||
if (event.IsXULLoad()) {
|
||||
maybeXulLoadDuration = event.mLoadDurationMS;
|
||||
}
|
||||
|
||||
// Trusted modules are not included in the ping
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!mAllowProcessing) {
|
||||
return;
|
||||
}
|
||||
|
||||
Telemetry::ProcessedStack processedStack =
|
||||
stackProcessor.GetStackAndModules(backtrace);
|
||||
|
||||
if (!mAllowProcessing) {
|
||||
return;
|
||||
}
|
||||
|
||||
Unused << processedStacks.emplaceBack(std::move(processedStack));
|
||||
Unused << processedEvents.emplaceBack(std::move(event));
|
||||
}
|
||||
|
||||
if (processedStacks.empty() && processedEvents.empty() &&
|
||||
!sanitizationFailures && !trustTestFailures) {
|
||||
// Nothing to save
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mAllowProcessing) {
|
||||
return;
|
||||
}
|
||||
|
||||
mProcessedModuleLoads.AddNewLoads(modules, std::move(processedEvents),
|
||||
std::move(processedStacks));
|
||||
if (maybeXulLoadDuration) {
|
||||
MOZ_ASSERT(!mProcessedModuleLoads.mXULLoadDurationMS);
|
||||
mProcessedModuleLoads.mXULLoadDurationMS = maybeXulLoadDuration;
|
||||
}
|
||||
|
||||
mProcessedModuleLoads.mSanitizationFailures += sanitizationFailures;
|
||||
mProcessedModuleLoads.mTrustTestFailures += trustTestFailures;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -1,85 +0,0 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_UntrustedModulesProcessor_h
|
||||
#define mozilla_UntrustedModulesProcessor_h
|
||||
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/glue/WindowsDllServices.h"
|
||||
#include "mozilla/LazyIdleThread.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/MozPromise.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/UntrustedModulesData.h"
|
||||
#include "mozilla/Vector.h"
|
||||
#include "mozilla/WinHeaderOnlyUtils.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsIRunnable.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "nsIThread.h"
|
||||
#include "nsString.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class ModuleEvaluator;
|
||||
|
||||
using UntrustedModulesPromise =
|
||||
MozPromise<Maybe<UntrustedModulesData>, nsresult, true>;
|
||||
|
||||
class UntrustedModulesProcessor final : public nsIObserver {
|
||||
public:
|
||||
static RefPtr<UntrustedModulesProcessor> Create();
|
||||
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
||||
void Enqueue(glue::EnhancedModuleLoadInfo&& aModLoadInfo);
|
||||
void Enqueue(ModuleLoadInfoVec&& aEvents);
|
||||
|
||||
RefPtr<UntrustedModulesPromise> GetProcessedData();
|
||||
|
||||
UntrustedModulesProcessor(const UntrustedModulesProcessor&) = delete;
|
||||
UntrustedModulesProcessor(UntrustedModulesProcessor&&) = delete;
|
||||
UntrustedModulesProcessor& operator=(const UntrustedModulesProcessor&) =
|
||||
delete;
|
||||
UntrustedModulesProcessor& operator=(UntrustedModulesProcessor&&) = delete;
|
||||
|
||||
private:
|
||||
~UntrustedModulesProcessor() = default;
|
||||
UntrustedModulesProcessor();
|
||||
void AddObservers();
|
||||
void RemoveObservers();
|
||||
void ScheduleNonEmptyQueueProcessing(const char* aSource,
|
||||
const MutexAutoLock& aProofOfLock);
|
||||
void CancelScheduledProcessing(const MutexAutoLock& aProofOfLock);
|
||||
void DispatchBackgroundProcessing(const char* aSource);
|
||||
void BackgroundProcessModuleLoadQueue(const char* aSource);
|
||||
void ProcessModuleLoadQueue(const char* aSource);
|
||||
void AssertRunningOnLazyIdleThread();
|
||||
RefPtr<UntrustedModulesPromise> GetProcessedDataInternal();
|
||||
RefPtr<ModuleRecord> GetModuleRecord(
|
||||
UntrustedModulesData::ModulesMap& aModules,
|
||||
const ModuleEvaluator& aModEval, const nsAString& aResolvedNtPath);
|
||||
|
||||
RefPtr<LazyIdleThread> mThread;
|
||||
|
||||
Mutex mUnprocessedMutex;
|
||||
|
||||
// The members in this group are protected by mUnprocessedMutex
|
||||
Vector<glue::EnhancedModuleLoadInfo> mUnprocessedModuleLoads;
|
||||
nsCOMPtr<nsIRunnable> mIdleRunnable;
|
||||
|
||||
// This member must only be touched on mThread
|
||||
UntrustedModulesData mProcessedModuleLoads;
|
||||
|
||||
// This member may be touched by any thread
|
||||
Atomic<bool> mAllowProcessing;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_UntrustedModulesProcessor_h
|
|
@ -10,16 +10,375 @@
|
|||
#include <psapi.h>
|
||||
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/HashTable.h"
|
||||
#include "mozilla/mozalloc.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "mozilla/StaticLocalPtr.h"
|
||||
#include "mozilla/SystemGroup.h"
|
||||
#include "mozilla/UntrustedModulesProcessor.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsString.h"
|
||||
#include "WinUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
// Returns a Vector of currently-loaded module base addresses. Basically this
|
||||
// is a wrapper around EnumProcessModulesEx()
|
||||
// In case of error, returns an empty Vector.
|
||||
static Vector<uintptr_t, 0, InfallibleAllocPolicy> GetProcessModuleBases() {
|
||||
Vector<uintptr_t, 0, InfallibleAllocPolicy> ret;
|
||||
// At the time this is called, we are far into process execution so we can
|
||||
// expect quite a few modules to be loaded. 100 seems reasonable to start.
|
||||
static const int kProcessModulesInitialCapacity = 100;
|
||||
Unused << ret.resize(kProcessModulesInitialCapacity);
|
||||
DWORD cbNeeded = 0;
|
||||
while (true) {
|
||||
if (!EnumProcessModulesEx(GetCurrentProcess(), (HMODULE*)ret.begin(),
|
||||
ret.length() * sizeof(uintptr_t), &cbNeeded,
|
||||
LIST_MODULES_ALL)) {
|
||||
// If it fails, return empty. There's no way to guarantee the partial
|
||||
// data is still good.
|
||||
return Vector<uintptr_t, 0, InfallibleAllocPolicy>();
|
||||
}
|
||||
size_t elementsNeeded = cbNeeded / sizeof(HMODULE);
|
||||
if (elementsNeeded <= ret.length()) {
|
||||
// Success; resize to the real number of elements.
|
||||
Unused << ret.resize(elementsNeeded);
|
||||
return ret;
|
||||
}
|
||||
// Increase the size of ret and try again.
|
||||
Unused << ret.resize(elementsNeeded);
|
||||
}
|
||||
}
|
||||
|
||||
// This class keeps track of incoming module load events, and takes
|
||||
// care of processing these events, weeding out trusted DLLs and filling in
|
||||
// remaining data.
|
||||
class UntrustedModulesManager {
|
||||
// This mutex does synchronization for all members.
|
||||
//
|
||||
// WARNING: This mutex locks during the Windows loader, which means you must
|
||||
// never invoke the loader from within this lock, even if you're locking from
|
||||
// outside the loader.
|
||||
Mutex mMutex;
|
||||
|
||||
// We want to only process startup modules once, so keep track of that here.
|
||||
bool mHasProcessedStartupModules = false;
|
||||
|
||||
ModuleEvaluator mEvaluator;
|
||||
int mErrorModules = 0;
|
||||
Maybe<double> mXULLoadDurationMS;
|
||||
|
||||
// In order to get a list of modules loaded at startup, we take a list of
|
||||
// currently-loaded modules, and subtract:
|
||||
// - Modules in mProcessedEvents, which have been considered untrusted,
|
||||
// - Modules in mTrustedModuleHistory, which have been seen but discarded
|
||||
// The resulting list is a list of modules that we haven't seen before at all
|
||||
// and are (likely) still loaded.
|
||||
//
|
||||
// We can get away with comparing only base addresses (instead of full path),
|
||||
// because we're specifically searching for DLLs loaded at startup and remain
|
||||
// loaded.
|
||||
HashSet<uintptr_t, DefaultHasher<uintptr_t>, InfallibleAllocPolicy>
|
||||
mTrustedModuleHistory;
|
||||
|
||||
// Incoming events, queued for processing (not yet evaluated)
|
||||
Vector<ModuleLoadEvent, 0, InfallibleAllocPolicy> mQueuedEvents;
|
||||
|
||||
// Items that have been processed, considered untrusted, and ready for
|
||||
// telemetry consumption.
|
||||
//
|
||||
// Note that the contents of mProcessedEvents and mProcessedStacks must
|
||||
// always remain in sync, element-for-element.
|
||||
Vector<ModuleLoadEvent, 0, InfallibleAllocPolicy> mProcessedEvents;
|
||||
Telemetry::CombinedStacks mProcessedStacks;
|
||||
|
||||
public:
|
||||
UntrustedModulesManager() : mMutex("UntrustedModulesManager::mMutex") {
|
||||
// Ensure whitelisted paths are initialized on the main thread.
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
widget::WinUtils::GetWhitelistedPaths();
|
||||
}
|
||||
|
||||
// Handles incoming loader events, places events into the queue for later
|
||||
// processing.
|
||||
//
|
||||
// WARNING: This is called within the loader; only trivial calls are allowed.
|
||||
void OnNewEvents(
|
||||
const Vector<glue::ModuleLoadEvent, 0, InfallibleAllocPolicy>& aEvents) {
|
||||
// Because we can only get the thread name from the current thread, this
|
||||
// is the last chance to fill in thread name.
|
||||
const char* thisThreadName = PR_GetThreadName(PR_GetCurrentThread());
|
||||
|
||||
// Lock mQueuedEvents to append events and fill in thread name if
|
||||
// possible... Only trivial (loader lock friendly) code allowed here!
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
for (auto& event : aEvents) {
|
||||
Unused << mQueuedEvents.emplaceBack(ModuleLoadEvent(event));
|
||||
if (thisThreadName && (event.mThreadID == ::GetCurrentThreadId())) {
|
||||
mQueuedEvents.back().mThreadName = thisThreadName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run from a worker thread, this will process and move items from the
|
||||
* mQueuedEvents into mProcessedEvents and mProcessedStacks
|
||||
* @param aHasProcessedStartupModules [out] Receives the value of
|
||||
* mHasProcessedStartupModules. We grab this value during a lock, and
|
||||
* the caller will need it for subsequent calls, so passing it around
|
||||
* like this avoids at least one lock. The only risk with this is
|
||||
* that we could end up calling ProcessStartupModules() multiple
|
||||
* times, which is totally safe, and would be extremely rare.
|
||||
*/
|
||||
void ProcessQueuedEvents(bool& aHasProcessedStartupModules) {
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
MOZ_ASSERT(!!mEvaluator);
|
||||
|
||||
// Hold a reference to DllServices to ensure the object doesn't get deleted
|
||||
// during this call.
|
||||
RefPtr<DllServices> dllSvcRef(DllServices::Get());
|
||||
if (!dllSvcRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
Telemetry::BatchProcessedStackGenerator stackProcessor;
|
||||
|
||||
Vector<ModuleLoadEvent, 0, InfallibleAllocPolicy> queuedEvents;
|
||||
aHasProcessedStartupModules = false;
|
||||
|
||||
{ // Scope for lock
|
||||
// Lock mQueuedEvents to steal its contents, and
|
||||
// mHasProcessedStartupModules to see if we can skip some steps.
|
||||
// Only trivial (loader lock friendly) code allowed here!
|
||||
MutexAutoLock lock(mMutex);
|
||||
aHasProcessedStartupModules = mHasProcessedStartupModules;
|
||||
mQueuedEvents.swap(queuedEvents);
|
||||
}
|
||||
|
||||
if (!mEvaluator) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector<ModuleLoadEvent, 0, InfallibleAllocPolicy> processedEvents;
|
||||
int errorModules = 0;
|
||||
|
||||
HashSet<uintptr_t, DefaultHasher<uintptr_t>, InfallibleAllocPolicy>
|
||||
newTrustedModuleBases;
|
||||
|
||||
// Process queued events, weeding out trusted items as we go.
|
||||
for (auto& e : queuedEvents) {
|
||||
// Create a copy of the event without its modules; we'll then fill them
|
||||
// in, filtering out any trusted modules we can ignore.
|
||||
ModuleLoadEvent eventCopy(
|
||||
e, ModuleLoadEvent::CopyOption::CopyWithoutModules);
|
||||
for (auto& m : e.mModules) {
|
||||
bool ok = m.PrepForTelemetry();
|
||||
MOZ_ASSERT(ok);
|
||||
if (!ok) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Maybe<bool> maybeIsTrusted =
|
||||
mEvaluator.IsModuleTrusted(m, eventCopy, dllSvcRef.get());
|
||||
|
||||
// Save xul.dll load timing for the ping payload.
|
||||
if ((m.mTrustFlags & ModuleTrustFlags::Xul) &&
|
||||
mXULLoadDurationMS.isNothing()) {
|
||||
mXULLoadDurationMS = m.mLoadDurationMS;
|
||||
}
|
||||
|
||||
if (maybeIsTrusted.isNothing()) {
|
||||
// If there was an error, assume the DLL is trusted to avoid
|
||||
// flooding the telemetry packet, but record that an error occurred.
|
||||
errorModules++;
|
||||
} else if (maybeIsTrusted.value()) {
|
||||
// Module is trusted. If we haven't yet processed startup modules,
|
||||
// we need to remember it.
|
||||
if (!aHasProcessedStartupModules) {
|
||||
Unused << newTrustedModuleBases.put(m.mBase);
|
||||
}
|
||||
} else {
|
||||
// Module is untrusted; record it.
|
||||
Unused << eventCopy.mModules.append(std::move(m));
|
||||
}
|
||||
}
|
||||
|
||||
if (eventCopy.mModules.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Unused << processedEvents.emplaceBack(std::move(eventCopy));
|
||||
}
|
||||
|
||||
// Process the stacks. processedStacks will be element-for-element
|
||||
// in sync with processedEvents
|
||||
Vector<Telemetry::ProcessedStack, 0, InfallibleAllocPolicy> processedStacks;
|
||||
for (auto&& eventCopy : processedEvents) {
|
||||
std::vector<uintptr_t> stdCopy;
|
||||
for (auto&& f : eventCopy.mStack) {
|
||||
stdCopy.emplace_back(std::move(f));
|
||||
}
|
||||
Unused << processedStacks.emplaceBack(
|
||||
stackProcessor.GetStackAndModules(stdCopy));
|
||||
}
|
||||
|
||||
{ // Scope for lock
|
||||
// Lock mTrustedModuleHistory and mProcessedEvents in order to merge the
|
||||
// data we just processed.
|
||||
// Only trivial (loader lock friendly) code allowed here!
|
||||
MutexAutoLock lock(mMutex);
|
||||
for (auto it = newTrustedModuleBases.iter(); !it.done(); it.next()) {
|
||||
Unused << mTrustedModuleHistory.put(it.get());
|
||||
}
|
||||
|
||||
mErrorModules += errorModules;
|
||||
|
||||
for (size_t i = 0; i < processedEvents.length(); ++i) {
|
||||
auto&& processedEvent = processedEvents[i];
|
||||
size_t newIndex = mProcessedStacks.AddStack(processedStacks[i]);
|
||||
// CombinedStacks is circular, so as its buffer rolls over, follow it
|
||||
// to keep indices in sync.
|
||||
if ((newIndex + 1) > mProcessedEvents.length()) {
|
||||
Unused << mProcessedEvents.append(std::move(processedEvent));
|
||||
} else {
|
||||
mProcessedEvents[newIndex] = std::move(processedEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks at the currently-loaded module list, subtracts modules we've seen
|
||||
* before, and adds the remainder to the list of queued events. The idea is
|
||||
* to process modules that loaded before we started examining load events.
|
||||
*
|
||||
* @param aHasProcessedStartupModules [in] The value of
|
||||
* mHasProcessedStartupModules as received
|
||||
* by a previous call. This is to avoid
|
||||
* unnecessary locking.
|
||||
* @return true if any events were added to mQueuedEvents.
|
||||
*/
|
||||
bool ProcessStartupModules(bool aHasProcessedStartupModules) {
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
||||
// Hold a reference to DllServices to ensure the object doesn't get deleted
|
||||
// during this call.
|
||||
RefPtr<DllServices> dllSvcRef(DllServices::Get());
|
||||
if (!dllSvcRef) {
|
||||
return false;
|
||||
}
|
||||
// Prevent static analysis build warnings about unused "kungFuDeathGrip"
|
||||
Unused << dllSvcRef;
|
||||
|
||||
if (aHasProcessedStartupModules) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector<uintptr_t, 0, InfallibleAllocPolicy> allModuleBases =
|
||||
GetProcessModuleBases();
|
||||
|
||||
Vector<ModuleLoadEvent, 0, InfallibleAllocPolicy> startupEvents;
|
||||
|
||||
{ // Scope for lock
|
||||
// Lock mTrustedModuleHistory and mProcessedEvents in order to form
|
||||
// list of startup modules.
|
||||
// Only trivial (loader lock friendly) code allowed here!
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
mHasProcessedStartupModules = true;
|
||||
|
||||
for (auto& base : allModuleBases) {
|
||||
// Look for it in mTrustedModuleHistory
|
||||
if (mTrustedModuleHistory.has(base)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Look for it in mProcessedEvents
|
||||
bool wasFound = false;
|
||||
for (auto& e : mProcessedEvents) {
|
||||
for (auto& m : e.mModules) {
|
||||
if (m.mBase == base) {
|
||||
wasFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wasFound) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// It's never been seen before so it must be a startup module.
|
||||
// One module = one event here.
|
||||
ModuleLoadEvent::ModuleInfo mi(base);
|
||||
ModuleLoadEvent e;
|
||||
e.mIsStartup = true;
|
||||
e.mProcessUptimeMS = 0;
|
||||
Unused << e.mModules.emplaceBack(std::move(mi));
|
||||
Unused << startupEvents.emplaceBack(std::move(e));
|
||||
}
|
||||
|
||||
// Since we process startup modules only once, this data is no longer
|
||||
// needed.
|
||||
mTrustedModuleHistory.clearAndCompact();
|
||||
}
|
||||
|
||||
if (startupEvents.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fill out more info in startupEvents.
|
||||
for (auto& e : startupEvents) {
|
||||
MOZ_ASSERT(e.mModules.length() == 1);
|
||||
ModuleLoadEvent::ModuleInfo& mi(e.mModules[0]);
|
||||
mi.PopulatePathInfo();
|
||||
}
|
||||
|
||||
// Lock mQueuedEvents to add the new items.
|
||||
// Only trivial (loader lock friendly) code allowed here!
|
||||
MutexAutoLock lock(mMutex);
|
||||
for (auto&& e : startupEvents) {
|
||||
Unused << mQueuedEvents.emplaceBack(std::move(e));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetTelemetryData(UntrustedModuleLoadTelemetryData& aOut) {
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
||||
// Hold a reference to DllServices to ensure the object doesn't get deleted
|
||||
// during this call.
|
||||
RefPtr<DllServices> dllSvcRef(DllServices::Get());
|
||||
if (!dllSvcRef) {
|
||||
return false;
|
||||
}
|
||||
// Prevent static analysis build warnings about unused "kungFuDeathGrip"
|
||||
Unused << dllSvcRef;
|
||||
|
||||
bool hasProcessedStartupModules = false;
|
||||
ProcessQueuedEvents(hasProcessedStartupModules);
|
||||
if (ProcessStartupModules(hasProcessedStartupModules)) {
|
||||
// New events were added; process those too.
|
||||
ProcessQueuedEvents(hasProcessedStartupModules);
|
||||
}
|
||||
|
||||
aOut.mErrorModules = mErrorModules;
|
||||
aOut.mXULLoadDurationMS = mXULLoadDurationMS;
|
||||
|
||||
// Lock mProcessedEvents and mProcessedStacks to make a copy for the caller.
|
||||
// Only trivial (loader lock friendly) code allowed here!
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
aOut.mStacks = mProcessedStacks;
|
||||
for (auto& e : mProcessedEvents) {
|
||||
Unused << aOut.mEvents.append(e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
const char* DllServices::kTopicDllLoadedMainThread = "dll-loaded-main-thread";
|
||||
const char* DllServices::kTopicDllLoadedNonMainThread =
|
||||
"dll-loaded-non-main-thread";
|
||||
|
@ -51,48 +410,30 @@ DllServices* DllServices::Get() {
|
|||
}
|
||||
|
||||
DllServices::DllServices()
|
||||
: mUntrustedModulesProcessor(UntrustedModulesProcessor::Create()) {}
|
||||
: mUntrustedModulesManager(new UntrustedModulesManager()) {}
|
||||
|
||||
RefPtr<UntrustedModulesPromise> DllServices::GetUntrustedModulesData() {
|
||||
if (!mUntrustedModulesProcessor) {
|
||||
return UntrustedModulesPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED,
|
||||
__func__);
|
||||
}
|
||||
|
||||
return mUntrustedModulesProcessor->GetProcessedData();
|
||||
bool DllServices::GetUntrustedModuleTelemetryData(
|
||||
UntrustedModuleLoadTelemetryData& aOut) {
|
||||
return mUntrustedModulesManager->GetTelemetryData(aOut);
|
||||
}
|
||||
|
||||
void DllServices::NotifyDllLoad(glue::EnhancedModuleLoadInfo&& aModLoadInfo) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
void DllServices::NotifyDllLoad(const bool aIsMainThread,
|
||||
const nsString& aDllName) {
|
||||
const char* topic;
|
||||
|
||||
if (aModLoadInfo.mNtLoadInfo.mThreadId == ::GetCurrentThreadId()) {
|
||||
if (aIsMainThread) {
|
||||
topic = kTopicDllLoadedMainThread;
|
||||
} else {
|
||||
topic = kTopicDllLoadedNonMainThread;
|
||||
}
|
||||
|
||||
// We save the path to a nsAutoString because once we have submitted
|
||||
// aModLoadInfo for processing there is no guarantee that the original
|
||||
// buffer will continue to be valid.
|
||||
nsAutoString dllFilePath(aModLoadInfo.GetSectionName());
|
||||
|
||||
if (mUntrustedModulesProcessor) {
|
||||
mUntrustedModulesProcessor->Enqueue(std::move(aModLoadInfo));
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIObserverService> obsServ(mozilla::services::GetObserverService());
|
||||
obsServ->NotifyObservers(nullptr, topic, dllFilePath.get());
|
||||
obsServ->NotifyObservers(nullptr, topic, aDllName.get());
|
||||
}
|
||||
|
||||
void DllServices::NotifyModuleLoadBacklog(ModuleLoadInfoVec&& aEvents) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (!mUntrustedModulesProcessor) {
|
||||
return;
|
||||
}
|
||||
|
||||
mUntrustedModulesProcessor->Enqueue(std::move(aEvents));
|
||||
void DllServices::NotifyUntrustedModuleLoads(
|
||||
const Vector<glue::ModuleLoadEvent, 0, InfallibleAllocPolicy>& aEvents) {
|
||||
mUntrustedModulesManager->OnNewEvents(aEvents);
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -7,29 +7,67 @@
|
|||
#ifndef mozilla_WinDllServices_h
|
||||
#define mozilla_WinDllServices_h
|
||||
|
||||
#include "mozilla/CombinedStacks.h"
|
||||
#include "mozilla/glue/WindowsDllServices.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/UntrustedModulesProcessor.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/ModuleEvaluator_windows.h"
|
||||
#include "mozilla/mozalloc.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Vector.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class DllServices : public glue::DllServices {
|
||||
// Holds the data that telemetry requests, and will be later converted to the
|
||||
// telemetry payload.
|
||||
class UntrustedModuleLoadTelemetryData {
|
||||
public:
|
||||
UntrustedModuleLoadTelemetryData() = default;
|
||||
// Moves allowed, no copies.
|
||||
UntrustedModuleLoadTelemetryData(UntrustedModuleLoadTelemetryData&&) =
|
||||
default;
|
||||
UntrustedModuleLoadTelemetryData(
|
||||
const UntrustedModuleLoadTelemetryData& aOther) = delete;
|
||||
|
||||
Vector<ModuleLoadEvent, 0, InfallibleAllocPolicy> mEvents;
|
||||
Telemetry::CombinedStacks mStacks;
|
||||
int mErrorModules = 0;
|
||||
Maybe<double> mXULLoadDurationMS;
|
||||
};
|
||||
|
||||
class UntrustedModulesManager;
|
||||
|
||||
class DllServices : public mozilla::glue::DllServices {
|
||||
public:
|
||||
static DllServices* Get();
|
||||
|
||||
static const char* kTopicDllLoadedMainThread;
|
||||
static const char* kTopicDllLoadedNonMainThread;
|
||||
|
||||
RefPtr<UntrustedModulesPromise> GetUntrustedModulesData();
|
||||
/**
|
||||
* Processes pending untrusted module evaluation / examination, and returns
|
||||
* a copy of the total data we've gathered for use by the untrusted modules
|
||||
* telemetry ping.
|
||||
*
|
||||
* This function should be called on a background thread, and can take a
|
||||
* while.
|
||||
*
|
||||
* @param aOut [out] Receives a copy of internally-stored data.
|
||||
* @return true upon success.
|
||||
*/
|
||||
bool GetUntrustedModuleTelemetryData(UntrustedModuleLoadTelemetryData& aOut);
|
||||
|
||||
private:
|
||||
DllServices();
|
||||
~DllServices() = default;
|
||||
|
||||
void NotifyDllLoad(glue::EnhancedModuleLoadInfo&& aModLoadInfo) override;
|
||||
void NotifyModuleLoadBacklog(ModuleLoadInfoVec&& aEvents) override;
|
||||
void NotifyDllLoad(const bool aIsMainThread,
|
||||
const nsString& aDllName) override;
|
||||
|
||||
RefPtr<UntrustedModulesProcessor> mUntrustedModulesProcessor;
|
||||
void NotifyUntrustedModuleLoads(
|
||||
const Vector<glue::ModuleLoadEvent, 0, InfallibleAllocPolicy>& aEvents)
|
||||
override;
|
||||
|
||||
UniquePtr<UntrustedModulesManager> mUntrustedModulesManager;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -45,20 +45,17 @@ if CONFIG['MOZ_INSTRUMENT_EVENT_LOOP']:
|
|||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
|
||||
EXPORTS.mozilla += [
|
||||
'ModuleVersionInfo.h',
|
||||
'ModuleEvaluator_windows.h',
|
||||
'ModuleVersionInfo_windows.h',
|
||||
'PolicyChecks.h',
|
||||
'UntrustedModulesData.h',
|
||||
'UntrustedModulesProcessor.h',
|
||||
'WinDllServices.h',
|
||||
'WinTokenUtils.h',
|
||||
]
|
||||
UNIFIED_SOURCES += [
|
||||
'/toolkit/mozapps/update/common/updateutils_win.cpp',
|
||||
'ModuleEvaluator.cpp',
|
||||
'ModuleVersionInfo.cpp',
|
||||
'ModuleEvaluator_windows.cpp',
|
||||
'ModuleVersionInfo_windows.cpp',
|
||||
'nsNativeAppSupportWin.cpp',
|
||||
'UntrustedModulesData.cpp',
|
||||
'UntrustedModulesProcessor.cpp',
|
||||
'WinDllServices.cpp',
|
||||
'WinTokenUtils.cpp',
|
||||
]
|
||||
|
|
|
@ -82,8 +82,8 @@
|
|||
#include "mozilla/ipc/XPCShellEnvironment.h"
|
||||
#if defined(XP_WIN)
|
||||
# include "mozilla/WindowsConsole.h"
|
||||
# include "mozilla/WindowsDllBlocklist.h"
|
||||
#endif
|
||||
#include "mozilla/WindowsDllBlocklist.h"
|
||||
|
||||
#include "GMPProcessChild.h"
|
||||
#include "mozilla/gfx/GPUProcessImpl.h"
|
||||
|
|
|
@ -573,7 +573,7 @@ class ModuleVersion final {
|
|||
bool operator<(const uint64_t& aOther) const { return mVersion < aOther; }
|
||||
|
||||
private:
|
||||
uint64_t mVersion;
|
||||
const uint64_t mVersion;
|
||||
};
|
||||
|
||||
inline LauncherResult<ModuleVersion> GetModuleVersion(
|
||||
|
|
Загрузка…
Ссылка в новой задаче