forked from mirrors/gecko-dev
CLOSED TREE Backed out changeset 210012222277 (bug 1826761) Backed out changeset e364bb149efa (bug 1826760) Backed out changeset e456e2f9966c (bug 1826759) Backed out changeset 2b6ff545f4a3 (bug 1826758) Backed out changeset 95fe1de8ba00 (bug 1826757) Backed out changeset f8af52d7f2a1 (bug 1826756) Backed out changeset 2646e773f098 (bug 1826754) Backed out changeset 58d5d74b1835 (bug 1826753) Backed out changeset 8567e6595acc (bug 1826752)
452 lines
14 KiB
C++
452 lines
14 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/DynamicallyLinkedFunctionPtr.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 "nsDebug.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;
|
|
}
|
|
|
|
namespace mozilla {
|
|
|
|
static Maybe<double> QPCLoadDurationToMilliseconds(
|
|
const ModuleLoadInfo& aNtInfo) {
|
|
if (aNtInfo.IsBare()) {
|
|
return Nothing();
|
|
}
|
|
|
|
return Some(QPCToMilliseconds<double>(aNtInfo.mLoadTimeInfo.QuadPart));
|
|
}
|
|
|
|
ModuleRecord::ModuleRecord() : mTrustFlags(ModuleTrustFlags::None) {}
|
|
|
|
ModuleRecord::ModuleRecord(const nsAString& aResolvedNtPath)
|
|
: mResolvedNtName(aResolvedNtPath), mTrustFlags(ModuleTrustFlags::None) {
|
|
if (aResolvedNtPath.IsEmpty()) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
|
|
nsAutoString resolvedDosPath;
|
|
if (!NtPathToDosPath(aResolvedNtPath, resolvedDosPath)) {
|
|
#if defined(DEBUG)
|
|
nsAutoCString msg;
|
|
msg.AppendLiteral("NtPathToDosPath failed for path \"");
|
|
msg.Append(NS_ConvertUTF16toUTF8(aResolvedNtPath));
|
|
msg.AppendLiteral("\"");
|
|
NS_WARNING(msg.get());
|
|
#endif // defined(DEBUG)
|
|
return;
|
|
}
|
|
|
|
nsresult rv =
|
|
NS_NewLocalFile(resolvedDosPath, false, getter_AddRefs(mResolvedDosName));
|
|
if (NS_FAILED(rv) || !mResolvedDosName) {
|
|
return;
|
|
}
|
|
|
|
GetVersionAndVendorInfo(resolvedDosPath);
|
|
|
|
// Now sanitize the resolved DLL name. If we cannot sanitize this then this
|
|
// record must not be considered valid.
|
|
nsAutoString strSanitizedPath(resolvedDosPath);
|
|
if (!widget::WinUtils::PreparePathForTelemetry(strSanitizedPath)) {
|
|
return;
|
|
}
|
|
|
|
mSanitizedDllName = strSanitizedPath;
|
|
}
|
|
|
|
void ModuleRecord::GetVersionAndVendorInfo(const nsAString& aPath) {
|
|
RefPtr<DllServices> dllSvc(DllServices::Get());
|
|
|
|
// WinVerifyTrust is too slow and of limited utility for our purposes, so
|
|
// we pass SkipTrustVerification here to avoid it.
|
|
UniquePtr<wchar_t[]> signedBy(
|
|
dllSvc->GetBinaryOrgName(PromiseFlatString(aPath).get(),
|
|
AuthenticodeFlags::SkipTrustVerification));
|
|
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 (!mResolvedDosName) {
|
|
return false;
|
|
}
|
|
|
|
nsAutoString leafName;
|
|
nsresult rv = mResolvedDosName->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(mResolvedDosName->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()
|
|
: mProcessUptimeMS(0ULL),
|
|
mThreadId(0UL),
|
|
mBaseAddress(0U),
|
|
mIsDependent(false),
|
|
mLoadStatus(0) {}
|
|
|
|
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)),
|
|
mIsDependent(aModLoadInfo.mNtLoadInfo.mIsDependent),
|
|
mLoadStatus(static_cast<uint32_t>(aModLoadInfo.mNtLoadInfo.mStatus)) {
|
|
if (!mModule || !(*mModule)) {
|
|
return;
|
|
}
|
|
|
|
mRequestedDllName = aModLoadInfo.mNtLoadInfo.mRequestedDllName.AsString();
|
|
|
|
// If we're in the main process, sanitize the requested DLL name here.
|
|
// If not, we cannot use PreparePathForTelemetry because it may try to
|
|
// delayload shlwapi.dll and could fail if the process is sandboxed.
|
|
// We leave mRequestedDllName unsanitized here and sanitize it when
|
|
// transferring it to the main process.
|
|
// (See ParamTraits<mozilla::UntrustedModulesData>::ReadEvent)
|
|
if (XRE_IsParentProcess()) {
|
|
SanitizeRequestedDllName();
|
|
}
|
|
}
|
|
|
|
void ProcessedModuleLoadEvent::SanitizeRequestedDllName() {
|
|
if (!mRequestedDllName.IsEmpty() &&
|
|
!widget::WinUtils::PreparePathForTelemetry(mRequestedDllName)) {
|
|
// If we cannot sanitize a path, we simply do not provide that field to
|
|
// Telemetry.
|
|
mRequestedDllName.Truncate();
|
|
}
|
|
}
|
|
|
|
/* static */
|
|
Maybe<LONGLONG>
|
|
ProcessedModuleLoadEvent::ComputeQPCTimeStampForProcessCreation() {
|
|
// This is similar to the algorithm used by TimeStamp::ProcessCreation:
|
|
|
|
// 1. Get the process creation timestamp as FILETIME;
|
|
FILETIME creationTime, exitTime, kernelTime, userTime;
|
|
if (!::GetProcessTimes(::GetCurrentProcess(), &creationTime, &exitTime,
|
|
&kernelTime, &userTime)) {
|
|
return Nothing();
|
|
}
|
|
|
|
// 2. Get current timestamps as both QPC and FILETIME;
|
|
LARGE_INTEGER nowQPC;
|
|
::QueryPerformanceCounter(&nowQPC);
|
|
|
|
static const StaticDynamicallyLinkedFunctionPtr<void(WINAPI*)(LPFILETIME)>
|
|
pGetSystemTimePreciseAsFileTime(L"kernel32.dll",
|
|
"GetSystemTimePreciseAsFileTime");
|
|
|
|
FILETIME nowFile;
|
|
if (pGetSystemTimePreciseAsFileTime) {
|
|
pGetSystemTimePreciseAsFileTime(&nowFile);
|
|
} else {
|
|
::GetSystemTimeAsFileTime(&nowFile);
|
|
}
|
|
|
|
// 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) {
|
|
return false;
|
|
}
|
|
|
|
return mModule->IsXUL();
|
|
}
|
|
|
|
bool ProcessedModuleLoadEvent::IsTrusted() const {
|
|
if (!mModule) {
|
|
return false;
|
|
}
|
|
|
|
return mModule->IsTrusted();
|
|
}
|
|
|
|
void UntrustedModulesData::AddNewLoads(
|
|
const ModulesMap& aModules, UntrustedModuleLoadingEvents&& aEvents,
|
|
Vector<Telemetry::ProcessedStack>&& aStacks) {
|
|
MOZ_ASSERT(aEvents.length() == aStacks.length());
|
|
for (const auto& entry : aModules) {
|
|
if (entry.GetData()->IsTrusted()) {
|
|
// Filter out trusted module records
|
|
continue;
|
|
}
|
|
|
|
Unused << mModules.LookupOrInsert(entry.GetKey(), entry.GetData());
|
|
}
|
|
|
|
MOZ_ASSERT(mEvents.length() <= kMaxEvents);
|
|
|
|
mNumEvents += aStacks.length();
|
|
mEvents.extendBack(std::move(aEvents));
|
|
for (auto&& stack : aStacks) {
|
|
mStacks.AddStack(stack);
|
|
}
|
|
Truncate(false);
|
|
}
|
|
|
|
void UntrustedModulesData::MergeModules(UntrustedModulesData& aNewData) {
|
|
for (auto item : aNewData.mEvents) {
|
|
mModules.WithEntryHandle(item->mEvent.mModule->mResolvedNtName,
|
|
[&](auto&& addPtr) {
|
|
if (addPtr) {
|
|
// Even though the path of a ModuleRecord
|
|
// matches, the object of ModuleRecord can be
|
|
// different. Make sure the event's mModule
|
|
// points to an object in mModules.
|
|
item->mEvent.mModule = addPtr.Data();
|
|
} else {
|
|
addPtr.Insert(item->mEvent.mModule);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void UntrustedModulesData::Merge(UntrustedModulesData&& aNewData) {
|
|
// Don't merge loading events of a different process
|
|
MOZ_ASSERT((mProcessType == aNewData.mProcessType) &&
|
|
(mPid == aNewData.mPid));
|
|
|
|
UntrustedModulesData newData(std::move(aNewData));
|
|
|
|
if (!mNumEvents) {
|
|
mNumEvents = newData.mNumEvents;
|
|
mModules = std::move(newData.mModules);
|
|
mEvents = std::move(newData.mEvents);
|
|
mStacks = std::move(newData.mStacks);
|
|
return;
|
|
}
|
|
|
|
MergeModules(newData);
|
|
mNumEvents += newData.mNumEvents;
|
|
mEvents.extendBack(std::move(newData.mEvents));
|
|
mStacks.AddStacks(newData.mStacks);
|
|
Truncate(false);
|
|
}
|
|
|
|
void UntrustedModulesData::Truncate(bool aDropCallstackData) {
|
|
if (aDropCallstackData) {
|
|
mStacks.Clear();
|
|
}
|
|
|
|
if (mNumEvents <= kMaxEvents) {
|
|
return;
|
|
}
|
|
|
|
UntrustedModuleLoadingEvents events;
|
|
events.splice(0, mEvents, mNumEvents - kMaxEvents, kMaxEvents);
|
|
std::swap(events, mEvents);
|
|
mNumEvents = kMaxEvents;
|
|
// mStacks only keeps the latest kMaxEvents stacks, so mEvents will
|
|
// still be lined up with mStacks.
|
|
}
|
|
|
|
void UntrustedModulesData::MergeWithoutStacks(UntrustedModulesData&& aNewData) {
|
|
// Don't merge loading events of a different process
|
|
MOZ_ASSERT((mProcessType == aNewData.mProcessType) &&
|
|
(mPid == aNewData.mPid));
|
|
MOZ_ASSERT(!mStacks.GetStackCount());
|
|
|
|
UntrustedModulesData newData(std::move(aNewData));
|
|
|
|
if (mNumEvents > 0) {
|
|
MergeModules(newData);
|
|
} else {
|
|
mModules = std::move(newData.mModules);
|
|
}
|
|
|
|
mNumEvents += newData.mNumEvents;
|
|
mEvents.extendBack(std::move(newData.mEvents));
|
|
|
|
Truncate(true);
|
|
}
|
|
|
|
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);
|
|
std::swap(mNumEvents, aOther.mNumEvents);
|
|
std::swap(mEvents, 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
|