gecko-dev/toolkit/xre/UntrustedModulesData.cpp
Aaron Klotz a66f824994 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
2019-09-23 20:19:17 +00:00

359 lines
11 KiB
C++

/* -*- 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