Bug 1542830: Part 6 - Rewrite the untrusted modules processor in toolkit/xre; r=mhowell

* Significant cleanup to `ModuleEvaluator`
* `UntrustedModuleData` holds all of the accumulated untrusted module info for
  a single process.
* `ProcessedModuleLoadEvent` holds information about an individual untrusted
  module load in a Gecko-friendly, sanitized, format.
* Since multiple `ProcessModuleLoadEvent` objects may reference the same
  module, we store module metadata in a shared `ModuleInfo` structure.
* The `UntrustedModulesProcessor` receives the events from `mozglue` and
  processes them on a background thread:
** It does not start background processing until the main thread has gone idle.
   The idea here is that we do not want to add any more background work until
   we are reasonably confident that Gecko is no longer starting up or doing
   other intense activity.
** Background processing runs at a background priority level, *except* when
   results are requested by telemetry itself.
** Telemetry requests the data via `UntrustedModulesProcessor::GetProcessedData`
   which runs at normal priority and returns a promise to the caller.

Depends on D43159

Differential Revision: https://phabricator.services.mozilla.com/D43160

--HG--
rename : toolkit/xre/ModuleEvaluator_windows.cpp => toolkit/xre/ModuleEvaluator.cpp
rename : toolkit/xre/ModuleEvaluator_windows.cpp => toolkit/xre/ModuleEvaluator.h
rename : toolkit/xre/ModuleVersionInfo_windows.cpp => toolkit/xre/ModuleVersionInfo.cpp
rename : toolkit/xre/ModuleVersionInfo_windows.h => toolkit/xre/ModuleVersionInfo.h
rename : toolkit/xre/ModuleEvaluator_windows.cpp => toolkit/xre/UntrustedModulesData.cpp
rename : toolkit/xre/ModuleEvaluator_windows.h => toolkit/xre/UntrustedModulesData.h
rename : toolkit/xre/ModuleEvaluator_windows.cpp => toolkit/xre/UntrustedModulesProcessor.cpp
rename : toolkit/xre/ModuleEvaluator_windows.h => toolkit/xre/UntrustedModulesProcessor.h
extra : moz-landing-system : lando
This commit is contained in:
Aaron Klotz 2019-09-23 20:19:17 +00:00
parent 4cd7d7d045
commit a66f824994
13 changed files with 1336 additions and 947 deletions

View File

@ -0,0 +1,245 @@
/* -*- 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

View File

@ -0,0 +1,48 @@
/* -*- 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

View File

@ -1,381 +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_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

View File

@ -1,142 +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_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

View File

@ -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_windows.h"
#include "ModuleVersionInfo.h"
#include "mozilla/UniquePtr.h"

View File

@ -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_windows_h
#define mozilla_ModuleVersionInfo_windows_h
#ifndef mozilla_ModuleVersionInfo_h
#define mozilla_ModuleVersionInfo_h
#include <windows.h>
#include "nsString.h"
@ -68,4 +68,4 @@ class ModuleVersionInfo {
} // namespace mozilla
#endif // mozilla_ModuleVersionInfo_windows_h
#endif // mozilla_ModuleVersionInfo_h

View File

@ -0,0 +1,358 @@
/* -*- 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

View File

@ -0,0 +1,159 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#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

View File

@ -0,0 +1,393 @@
/* -*- 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

View File

@ -0,0 +1,85 @@
/* -*- 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

View File

@ -10,375 +10,16 @@
#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/Unused.h"
#include "mozilla/UntrustedModulesProcessor.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";
@ -410,30 +51,48 @@ DllServices* DllServices::Get() {
}
DllServices::DllServices()
: mUntrustedModulesManager(new UntrustedModulesManager()) {}
: mUntrustedModulesProcessor(UntrustedModulesProcessor::Create()) {}
bool DllServices::GetUntrustedModuleTelemetryData(
UntrustedModuleLoadTelemetryData& aOut) {
return mUntrustedModulesManager->GetTelemetryData(aOut);
RefPtr<UntrustedModulesPromise> DllServices::GetUntrustedModulesData() {
if (!mUntrustedModulesProcessor) {
return UntrustedModulesPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED,
__func__);
}
return mUntrustedModulesProcessor->GetProcessedData();
}
void DllServices::NotifyDllLoad(const bool aIsMainThread,
const nsString& aDllName) {
void DllServices::NotifyDllLoad(glue::EnhancedModuleLoadInfo&& aModLoadInfo) {
MOZ_ASSERT(NS_IsMainThread());
const char* topic;
if (aIsMainThread) {
if (aModLoadInfo.mNtLoadInfo.mThreadId == ::GetCurrentThreadId()) {
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, aDllName.get());
obsServ->NotifyObservers(nullptr, topic, dllFilePath.get());
}
void DllServices::NotifyUntrustedModuleLoads(
const Vector<glue::ModuleLoadEvent, 0, InfallibleAllocPolicy>& aEvents) {
mUntrustedModulesManager->OnNewEvents(aEvents);
void DllServices::NotifyModuleLoadBacklog(ModuleLoadInfoVec&& aEvents) {
MOZ_ASSERT(NS_IsMainThread());
if (!mUntrustedModulesProcessor) {
return;
}
mUntrustedModulesProcessor->Enqueue(std::move(aEvents));
}
} // namespace mozilla

View File

@ -7,67 +7,29 @@
#ifndef mozilla_WinDllServices_h
#define mozilla_WinDllServices_h
#include "mozilla/CombinedStacks.h"
#include "mozilla/glue/WindowsDllServices.h"
#include "mozilla/Maybe.h"
#include "mozilla/ModuleEvaluator_windows.h"
#include "mozilla/mozalloc.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Vector.h"
#include "mozilla/RefPtr.h"
#include "mozilla/UntrustedModulesProcessor.h"
namespace mozilla {
// 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 {
class DllServices : public glue::DllServices {
public:
static DllServices* Get();
static const char* kTopicDllLoadedMainThread;
static const char* kTopicDllLoadedNonMainThread;
/**
* 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);
RefPtr<UntrustedModulesPromise> GetUntrustedModulesData();
private:
DllServices();
~DllServices() = default;
void NotifyDllLoad(const bool aIsMainThread,
const nsString& aDllName) override;
void NotifyDllLoad(glue::EnhancedModuleLoadInfo&& aModLoadInfo) override;
void NotifyModuleLoadBacklog(ModuleLoadInfoVec&& aEvents) override;
void NotifyUntrustedModuleLoads(
const Vector<glue::ModuleLoadEvent, 0, InfallibleAllocPolicy>& aEvents)
override;
UniquePtr<UntrustedModulesManager> mUntrustedModulesManager;
RefPtr<UntrustedModulesProcessor> mUntrustedModulesProcessor;
};
} // namespace mozilla

View File

@ -45,17 +45,20 @@ if CONFIG['MOZ_INSTRUMENT_EVENT_LOOP']:
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
EXPORTS.mozilla += [
'ModuleEvaluator_windows.h',
'ModuleVersionInfo_windows.h',
'ModuleVersionInfo.h',
'PolicyChecks.h',
'UntrustedModulesData.h',
'UntrustedModulesProcessor.h',
'WinDllServices.h',
'WinTokenUtils.h',
]
UNIFIED_SOURCES += [
'/toolkit/mozapps/update/common/updateutils_win.cpp',
'ModuleEvaluator_windows.cpp',
'ModuleVersionInfo_windows.cpp',
'ModuleEvaluator.cpp',
'ModuleVersionInfo.cpp',
'nsNativeAppSupportWin.cpp',
'UntrustedModulesData.cpp',
'UntrustedModulesProcessor.cpp',
'WinDllServices.cpp',
'WinTokenUtils.cpp',
]