зеркало из https://github.com/mozilla/gecko-dev.git
425 строки
15 KiB
C++
425 строки
15 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 "mozilla/WinDllServices.h"
|
|
|
|
#include <windows.h>
|
|
#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/Unused.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsString.h"
|
|
#include "WinUtils.h"
|
|
|
|
namespace mozilla {
|
|
|
|
// Returns a Vector of currently-loaded module base addresses. Basically this
|
|
// is a wrapper around EnumProcessModulesEx()
|
|
// In case of error, returns an empty Vector.
|
|
static Vector<uintptr_t, 0, InfallibleAllocPolicy> GetProcessModuleBases() {
|
|
Vector<uintptr_t, 0, InfallibleAllocPolicy> ret;
|
|
// At the time this is called, we are far into process execution so we can
|
|
// expect quite a few modules to be loaded. 100 seems reasonable to start.
|
|
static const int kProcessModulesInitialCapacity = 100;
|
|
Unused << ret.resize(kProcessModulesInitialCapacity);
|
|
DWORD cbNeeded = 0;
|
|
while (true) {
|
|
if (!EnumProcessModulesEx(GetCurrentProcess(), (HMODULE*)ret.begin(),
|
|
ret.length() * sizeof(uintptr_t), &cbNeeded,
|
|
LIST_MODULES_ALL)) {
|
|
// If it fails, return empty. There's no way to guarantee the partial
|
|
// data is still good.
|
|
return Vector<uintptr_t, 0, InfallibleAllocPolicy>();
|
|
}
|
|
size_t elementsNeeded = cbNeeded / sizeof(HMODULE);
|
|
if (elementsNeeded <= ret.length()) {
|
|
// Success; resize to the real number of elements.
|
|
Unused << ret.resize(elementsNeeded);
|
|
return ret;
|
|
}
|
|
// Increase the size of ret and try again.
|
|
Unused << ret.resize(elementsNeeded);
|
|
}
|
|
}
|
|
|
|
// This class keeps track of incoming module load events, and takes
|
|
// care of processing these events, weeding out trusted DLLs and filling in
|
|
// remaining data.
|
|
class UntrustedModulesManager {
|
|
// This mutex does synchronization for all members.
|
|
//
|
|
// WARNING: This mutex locks during the Windows loader, which means you must
|
|
// never invoke the loader from within this lock, even if you're locking from
|
|
// outside the loader.
|
|
Mutex mMutex;
|
|
|
|
// We want to only process startup modules once, so keep track of that here.
|
|
bool mHasProcessedStartupModules = false;
|
|
|
|
ModuleEvaluator mEvaluator;
|
|
int mErrorModules = 0;
|
|
|
|
// 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) {
|
|
// Hold a reference to DllServices to ensure the object doesn't get deleted
|
|
// during this call.
|
|
RefPtr<DllServices> dllSvcRef(DllServices::Get());
|
|
if (!dllSvcRef) {
|
|
return;
|
|
}
|
|
// Prevent static analysis build warnings about unused "kungFuDeathGrip"
|
|
Unused << dllSvcRef;
|
|
|
|
// 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());
|
|
|
|
// 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);
|
|
}
|
|
|
|
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) {
|
|
Maybe<bool> ret =
|
|
mEvaluator.IsModuleTrusted(m, eventCopy, dllSvcRef.get());
|
|
if (ret.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 (*ret) {
|
|
// 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;
|
|
mi.mBase = 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]);
|
|
widget::WinUtils::GetModuleFullPath((HMODULE)mi.mBase, mi.mLdrName);
|
|
Unused << NS_NewLocalFile(mi.mLdrName, false, getter_AddRefs(mi.mFile));
|
|
}
|
|
|
|
// 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;
|
|
|
|
// 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";
|
|
|
|
// In order to prevent sInstance from being "woken back up" after it's been
|
|
// cleared on shutdown, this will let us know if sInstance is empty because
|
|
// it's not initialized yet, or because it's been cleared on shutdown.
|
|
static Atomic<bool> sDllServicesHasBeenSet;
|
|
static StaticRefPtr<DllServices> sInstance;
|
|
|
|
DllServices* DllServices::Get() {
|
|
if (sDllServicesHasBeenSet) {
|
|
return sInstance;
|
|
}
|
|
|
|
sInstance = new DllServices();
|
|
sDllServicesHasBeenSet = true;
|
|
|
|
// Enable() winds up calling NotifyUntrustedModuleLoads which requires
|
|
// sInstance to be valid. So we must call Enable() here rather than the
|
|
// DllServices constructor.
|
|
sInstance->Enable();
|
|
ClearOnShutdown(&sInstance);
|
|
return sInstance;
|
|
}
|
|
|
|
DllServices::DllServices()
|
|
: mUntrustedModulesManager(new UntrustedModulesManager()) {}
|
|
|
|
bool DllServices::GetUntrustedModuleTelemetryData(
|
|
UntrustedModuleLoadTelemetryData& aOut) {
|
|
return mUntrustedModulesManager->GetTelemetryData(aOut);
|
|
}
|
|
|
|
void DllServices::NotifyDllLoad(const bool aIsMainThread,
|
|
const nsString& aDllName) {
|
|
const char* topic;
|
|
|
|
if (aIsMainThread) {
|
|
topic = kTopicDllLoadedMainThread;
|
|
} else {
|
|
topic = kTopicDllLoadedNonMainThread;
|
|
}
|
|
|
|
nsCOMPtr<nsIObserverService> obsServ(mozilla::services::GetObserverService());
|
|
obsServ->NotifyObservers(nullptr, topic, aDllName.get());
|
|
}
|
|
|
|
void DllServices::NotifyUntrustedModuleLoads(
|
|
const Vector<glue::ModuleLoadEvent, 0, InfallibleAllocPolicy>& aEvents) {
|
|
mUntrustedModulesManager->OnNewEvents(aEvents);
|
|
}
|
|
|
|
} // namespace mozilla
|