mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-02 01:48:05 +00:00
13f66b51b6
We now record DLL load events along with stack trace and other data so we can later determine trustworthiness and report the DLL via telemetry. Differential Revision: https://phabricator.services.mozilla.com/D7175 --HG-- extra : moz-landing-system : lando
357 lines
9.5 KiB
C++
357 lines
9.5 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 http://mozilla.org/MPL/2.0/. */
|
|
|
|
#ifdef MOZ_MEMORY
|
|
#define MOZ_MEMORY_IMPL
|
|
#include "mozmemory_wrap.h"
|
|
#define MALLOC_FUNCS MALLOC_FUNCS_MALLOC
|
|
// See mozmemory_wrap.h for more details. This file is part of libmozglue, so
|
|
// it needs to use _impl suffixes.
|
|
#define MALLOC_DECL(name, return_type, ...) \
|
|
MOZ_MEMORY_API return_type name ## _impl(__VA_ARGS__);
|
|
#include "malloc_decls.h"
|
|
#include "mozilla/mozalloc.h"
|
|
#endif
|
|
|
|
#include "UntrustedDllsHandler.h"
|
|
|
|
#include <windows.h>
|
|
|
|
#include "mozilla/Atomics.h"
|
|
#include "mozilla/mozalloc.h"
|
|
#include "mozilla/RefPtr.h"
|
|
#include "mozilla/StaticPtr.h"
|
|
#include "mozilla/ThreadLocal.h"
|
|
#include "mozilla/TimeStamp.h"
|
|
#include "mozilla/Unused.h"
|
|
#include "nsWindowsHelpers.h" // For AutoCriticalSection
|
|
|
|
namespace mozilla {
|
|
namespace glue {
|
|
|
|
// Copies a null-terminated string. Upon error, returns nullptr.
|
|
static UniquePtr<wchar_t[]>
|
|
CopyString(const UniquePtr<wchar_t[]>& aOther)
|
|
{
|
|
if (!aOther) {
|
|
return nullptr;
|
|
}
|
|
size_t chars = wcslen(aOther.get());
|
|
auto ret = MakeUnique<wchar_t[]>(chars + 1);
|
|
if (wcsncpy_s(ret.get(), chars + 1, aOther.get(), chars)) {
|
|
return nullptr;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// Creates a UniquePtr<wchar_t[]> from a PCUNICODE_STRING string.
|
|
// Upon error, returns nullptr.
|
|
static UniquePtr<wchar_t[]>
|
|
CopyString(PCUNICODE_STRING aOther)
|
|
{
|
|
if (!aOther || !aOther->Buffer) {
|
|
return nullptr;
|
|
}
|
|
size_t chars = aOther->Length / sizeof(wchar_t);
|
|
auto ret = MakeUnique<wchar_t[]>(chars + 1);
|
|
if (wcsncpy_s(ret.get(), chars + 1, aOther->Buffer, chars)) {
|
|
return nullptr;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// Basic wrapper around ::GetModuleFileNameW.
|
|
// Returns the full path of the loaded module specified by aModuleBase.
|
|
// Upon error, returns nullptr.
|
|
static UniquePtr<wchar_t[]>
|
|
GetModuleFullPath(uintptr_t aModuleBase)
|
|
{
|
|
size_t allocated = MAX_PATH;
|
|
auto ret = MakeUnique<wchar_t[]>(allocated);
|
|
size_t len;
|
|
while (true) {
|
|
len = (size_t)::GetModuleFileNameW((HMODULE)aModuleBase, ret.get(),
|
|
allocated);
|
|
if (!len) {
|
|
return nullptr;
|
|
}
|
|
if (len == allocated && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
|
|
allocated *= 2;
|
|
ret = MakeUnique<wchar_t[]>(allocated);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
// The buffer may much bigger than needed. Return an efficiently-allocated
|
|
// buffer.
|
|
return CopyString(ret);
|
|
}
|
|
|
|
// To track call depth and recursively-loaded modules, we must store this data
|
|
// in thread local storage.
|
|
class TLSData
|
|
{
|
|
public:
|
|
Vector<ModuleLoadEvent::ModuleInfo, 0, InfallibleAllocPolicy> mModulesLoaded;
|
|
int mCallDepth = 0;
|
|
};
|
|
|
|
static MOZ_THREAD_LOCAL(TLSData*) sTlsData;
|
|
|
|
// This singleton class does the underlying work for UntrustedDllsHandler
|
|
class UntrustedDllsHandlerImpl
|
|
{
|
|
// Refcounting gives us a way to synchronize call lifetime vs object lifetime.
|
|
// We don't have access to NS_INLINE_DECL_THREADSAFE_REFCOUNTING from mozglue,
|
|
// but it's easy to roll our own.
|
|
Atomic<int32_t> mRefCnt;
|
|
|
|
// In order to prevent sInstance from being "woken back up" after it's been
|
|
// cleared on shutdown, this will let us know if sInstance is empty because
|
|
// it's not initialized yet, or because it's been cleared on shutdown.
|
|
static Atomic<bool> sInstanceHasBeenSet;
|
|
|
|
// Singleton reference
|
|
static StaticRefPtr<UntrustedDllsHandlerImpl> sInstance;
|
|
|
|
// Holds a list of module load events. This gets emptied upon calling
|
|
// UntrustedDllsHandler::TakePendingEvents
|
|
Vector<ModuleLoadEvent, 0, InfallibleAllocPolicy> mModuleLoadEvents;
|
|
|
|
// Holds a list of module full paths that we've already handled, so we can
|
|
// skip duplicates.
|
|
Vector<mozilla::UniquePtr<wchar_t[]>> mModuleHistory;
|
|
|
|
// This lock protects gModuleLoadEvents and gModuleHistory.
|
|
//
|
|
// You must only make trivial, loader-lock-friendly calls within the lock. We
|
|
// cannot risk re-entering the loader at this point.
|
|
CRITICAL_SECTION mDataLock;
|
|
|
|
UntrustedDllsHandlerImpl()
|
|
{
|
|
InitializeCriticalSection(&mDataLock);
|
|
}
|
|
|
|
~UntrustedDllsHandlerImpl()
|
|
{
|
|
{ // Scope for lock
|
|
// Ensure pending ops are complete.
|
|
AutoCriticalSection lock(&mDataLock);
|
|
}
|
|
DeleteCriticalSection(&mDataLock);
|
|
}
|
|
|
|
public:
|
|
static RefPtr<UntrustedDllsHandlerImpl> GetInstance()
|
|
{
|
|
if (sInstanceHasBeenSet) {
|
|
return sInstance;
|
|
}
|
|
|
|
sInstance = new UntrustedDllsHandlerImpl();
|
|
sInstanceHasBeenSet = true;
|
|
return sInstance;
|
|
}
|
|
|
|
static void Shutdown()
|
|
{
|
|
sInstance = nullptr;
|
|
}
|
|
|
|
int32_t AddRef()
|
|
{
|
|
return ++mRefCnt;
|
|
}
|
|
|
|
int32_t Release()
|
|
{
|
|
int32_t ret = --mRefCnt;
|
|
if (!ret) {
|
|
delete this;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// Called after a successful module load at the top level. Now we are safe
|
|
// to package up the event and save for later processing.
|
|
void OnAfterTopLevelModuleLoad()
|
|
{
|
|
// Hold a reference to ensure we don't get deleted during this call.
|
|
RefPtr<UntrustedDllsHandlerImpl> refHolder(this);
|
|
if (!refHolder) {
|
|
return;
|
|
}
|
|
|
|
ModuleLoadEvent thisEvent;
|
|
|
|
TLSData* tlsData = sTlsData.get();
|
|
if (!tlsData) {
|
|
return;
|
|
}
|
|
|
|
{ // Scope for lock
|
|
// Lock around gModuleHistory to prune out modules we've handled before.
|
|
// Only trivial calls allowed during lock (don't invoke the loader)
|
|
AutoCriticalSection lock(&mDataLock);
|
|
|
|
// There will rarely be more than a couple items in tlsData->mModulesLoaded,
|
|
// so this is efficient enough.
|
|
for (auto& module : tlsData->mModulesLoaded) {
|
|
if (module.mFullPath && wcslen(module.mFullPath.get())) {
|
|
bool foundInHistory = false;
|
|
for (auto& h : mModuleHistory) {
|
|
if (!wcsicmp(h.get(), module.mFullPath.get())) {
|
|
foundInHistory = true;
|
|
break;
|
|
}
|
|
}
|
|
if (foundInHistory) {
|
|
continue;
|
|
}
|
|
Unused << mModuleHistory.append(CopyString(module.mFullPath));
|
|
}
|
|
Unused << thisEvent.mModules.emplaceBack(std::move(module));
|
|
}
|
|
}
|
|
|
|
tlsData->mModulesLoaded.clear();
|
|
|
|
if (thisEvent.mModules.empty()) {
|
|
return; // All modules have been filtered out; nothing further to do.
|
|
}
|
|
|
|
thisEvent.mThreadID = GetCurrentThreadId();
|
|
|
|
TimeStamp processCreation = TimeStamp::ProcessCreation();
|
|
TimeDuration td = TimeStamp::Now() - processCreation;
|
|
thisEvent.mProcessUptimeMS = (uint64_t)td.ToMilliseconds();
|
|
|
|
static const uint32_t kMaxFrames = 500;
|
|
auto frames = MakeUnique<void*[]>(kMaxFrames);
|
|
|
|
// Setting FramesToSkip to 1 so the caller will see itself as the top frame.
|
|
USHORT frameCount = CaptureStackBackTrace(1, kMaxFrames, frames.get(), nullptr);
|
|
if (thisEvent.mStack.reserve(frameCount)) {
|
|
for (size_t i = 0; i < frameCount; ++i) {
|
|
Unused << thisEvent.mStack.append((uintptr_t)frames[i]);
|
|
}
|
|
}
|
|
|
|
// Lock in order to store the new event.
|
|
// Only trivial calls allowed during lock (don't invoke the loader)
|
|
AutoCriticalSection lock(&mDataLock);
|
|
Unused << mModuleLoadEvents.emplaceBack(std::move(thisEvent));
|
|
}
|
|
|
|
// Returns true if aOut is no longer empty.
|
|
bool TakePendingEvents(Vector<ModuleLoadEvent, 0, InfallibleAllocPolicy>& aOut)
|
|
{
|
|
// Hold a reference to ensure we don't get deleted during this call.
|
|
RefPtr<UntrustedDllsHandlerImpl> refHolder(this);
|
|
if (!refHolder) {
|
|
return false;
|
|
}
|
|
|
|
AutoCriticalSection lock(&mDataLock);
|
|
mModuleLoadEvents.swap(aOut);
|
|
return !aOut.empty();
|
|
}
|
|
};
|
|
|
|
Atomic<bool> UntrustedDllsHandlerImpl::sInstanceHasBeenSet;
|
|
StaticRefPtr<UntrustedDllsHandlerImpl> UntrustedDllsHandlerImpl::sInstance;
|
|
|
|
/* static */
|
|
void
|
|
UntrustedDllsHandler::Init()
|
|
{
|
|
Unused << sTlsData.init();
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
/* static */
|
|
void
|
|
UntrustedDllsHandler::Shutdown()
|
|
{
|
|
UntrustedDllsHandlerImpl::Shutdown();
|
|
}
|
|
#endif // DEBUG
|
|
|
|
/* static */
|
|
void
|
|
UntrustedDllsHandler::EnterLoaderCall()
|
|
{
|
|
if (!sTlsData.initialized()) {
|
|
return;
|
|
}
|
|
if (!sTlsData.get()) {
|
|
sTlsData.set(new TLSData());
|
|
}
|
|
sTlsData.get()->mCallDepth++;
|
|
}
|
|
|
|
/* static */
|
|
void
|
|
UntrustedDllsHandler::ExitLoaderCall()
|
|
{
|
|
if (!sTlsData.initialized()) {
|
|
return;
|
|
}
|
|
if (!--(sTlsData.get()->mCallDepth)) {
|
|
delete sTlsData.get();
|
|
sTlsData.set(nullptr);
|
|
}
|
|
}
|
|
|
|
/* static */
|
|
void
|
|
UntrustedDllsHandler::OnAfterModuleLoad(uintptr_t aBaseAddr,
|
|
PUNICODE_STRING aLdrModuleName)
|
|
{
|
|
RefPtr<UntrustedDllsHandlerImpl> p(UntrustedDllsHandlerImpl::GetInstance());
|
|
if (!p) {
|
|
return;
|
|
}
|
|
|
|
if (!sTlsData.initialized()) {
|
|
return;
|
|
}
|
|
TLSData* tlsData = sTlsData.get();
|
|
if (!tlsData) {
|
|
return;
|
|
}
|
|
|
|
ModuleLoadEvent::ModuleInfo moduleInfo;
|
|
moduleInfo.mLdrName = CopyString(aLdrModuleName);
|
|
moduleInfo.mBase = aBaseAddr;
|
|
moduleInfo.mFullPath = GetModuleFullPath(aBaseAddr);
|
|
|
|
Unused << tlsData->mModulesLoaded.emplaceBack(std::move(moduleInfo));
|
|
|
|
if (tlsData->mCallDepth > 1) {
|
|
// Recursive call; bail and wait until top-level call can proceed.
|
|
return;
|
|
}
|
|
|
|
p->OnAfterTopLevelModuleLoad();
|
|
}
|
|
|
|
/* static */
|
|
bool
|
|
UntrustedDllsHandler::TakePendingEvents(
|
|
Vector<ModuleLoadEvent, 0, InfallibleAllocPolicy>& aOut)
|
|
{
|
|
RefPtr<UntrustedDllsHandlerImpl> p(UntrustedDllsHandlerImpl::GetInstance());
|
|
if (!p) {
|
|
return false;
|
|
}
|
|
return p->TakePendingEvents(aOut);
|
|
}
|
|
|
|
} // namespace glue
|
|
} // namespace mozilla
|