Bug 1686229 - Part1. Add a member to ModuleLoadInfo to indicate the status of DLL load. r=mhowell

We used to record a DLL loading event only when a module was loaded.
With this patch, we record an event for a module blocked by our DLL
blocklist as well as a loaded module.  It is achieved by calling
to `ModuleLoadFrame::NotifySectionMap` in `patched_NtMapViewOfSection`
regardless of the block action.

This patch introduces a new member `ModuleLoadInfo::mStatus` and
`ProcessedModuleLoadEvent::mLoadStatus` to keep the DLL loading status,
which will be added to the third-party-modules ping by a following patch.

Differential Revision: https://phabricator.services.mozilla.com/D102407
This commit is contained in:
Toshihito Kikuchi 2021-01-23 00:12:49 +00:00
parent 42abfd5b8c
commit 3aef28a656
7 changed files with 90 additions and 37 deletions

View File

@ -437,28 +437,41 @@ NTSTATUS NTAPI patched_NtMapViewOfSection(
resultView.isOk() ? &resultView.inspect()->mK32Exports : nullptr);
}
if (blockAction == BlockAction::Allow) {
if (nt::RtlGetProcessHeap()) {
ModuleLoadFrame::NotifySectionMap(
nt::AllocatedUnicodeString(sectionFileName), *aBaseAddress,
stubStatus);
}
return stubStatus;
ModuleLoadInfo::Status loadStatus = ModuleLoadInfo::Status::Blocked;
switch (blockAction) {
case BlockAction::Allow:
loadStatus = ModuleLoadInfo::Status::Loaded;
break;
case BlockAction::NoOpEntryPoint:
loadStatus = ModuleLoadInfo::Status::Redirected;
break;
case BlockAction::SubstituteLSP:
// The process heap needs to be available here because
// NotifyLSPSubstitutionRequired below copies a given string into
// the heap. We use a soft assert here, assuming LSP load always
// occurs after the heap is initialized.
MOZ_ASSERT(nt::RtlGetProcessHeap());
// Notify patched_LdrLoadDll that it will be necessary to perform
// a substitution before returning.
ModuleLoadFrame::NotifyLSPSubstitutionRequired(&leafOnStack);
break;
default:
break;
}
if (blockAction == BlockAction::SubstituteLSP) {
// The process heap needs to be available here because
// NotifyLSPSubstitutionRequired below copies a given string into the heap.
// We use a soft assert here, assuming LSP load always occurs after the heap
// is initialized.
MOZ_ASSERT(nt::RtlGetProcessHeap());
// Notify patched_LdrLoadDll that it will be necessary to perform a
// substitution before returning.
ModuleLoadFrame::NotifyLSPSubstitutionRequired(&leafOnStack);
if (nt::RtlGetProcessHeap()) {
ModuleLoadFrame::NotifySectionMap(
nt::AllocatedUnicodeString(sectionFileName), *aBaseAddress, stubStatus,
loadStatus);
}
if (blockAction == BlockAction::NoOpEntryPoint) {
if (loadStatus == ModuleLoadInfo::Status::Loaded ||
loadStatus == ModuleLoadInfo::Status::Redirected) {
return stubStatus;
}

View File

@ -24,12 +24,13 @@ ModuleLoadFrame::ModuleLoadFrame(PCUNICODE_STRING aRequestedDllName)
}
ModuleLoadFrame::ModuleLoadFrame(nt::AllocatedUnicodeString&& aSectionName,
const void* aMapBaseAddr, NTSTATUS aNtStatus)
const void* aMapBaseAddr, NTSTATUS aNtStatus,
ModuleLoadInfo::Status aLoadStatus)
: mPrev(sTopFrame.get()),
mContext(nullptr),
mLSPSubstitutionRequired(false),
mLoadNtStatus(aNtStatus),
mLoadInfo(std::move(aSectionName), aMapBaseAddr) {
mLoadInfo(std::move(aSectionName), aMapBaseAddr, aLoadStatus) {
sTopFrame.set(this);
gLoaderPrivateAPI.NotifyBeginDllLoad(&mContext, mLoadInfo.mSectionName);
@ -69,7 +70,7 @@ void ModuleLoadFrame::SetLSPSubstitutionRequired(PCUNICODE_STRING aLeafName) {
/* static */
void ModuleLoadFrame::NotifySectionMap(
nt::AllocatedUnicodeString&& aSectionName, const void* aMapBaseAddr,
NTSTATUS aMapNtStatus) {
NTSTATUS aMapNtStatus, ModuleLoadInfo::Status aLoadStatus) {
ModuleLoadFrame* topFrame = sTopFrame.get();
if (!topFrame) {
// The only time that this data is useful is during initial mapping of
@ -77,12 +78,14 @@ void ModuleLoadFrame::NotifySectionMap(
// IsDefaultObserver will return false, indicating that we are beyond
// initial process startup.
if (gLoaderPrivateAPI.IsDefaultObserver()) {
OnBareSectionMap(std::move(aSectionName), aMapBaseAddr, aMapNtStatus);
OnBareSectionMap(std::move(aSectionName), aMapBaseAddr, aMapNtStatus,
aLoadStatus);
}
return;
}
topFrame->OnSectionMap(std::move(aSectionName), aMapBaseAddr, aMapNtStatus);
topFrame->OnSectionMap(std::move(aSectionName), aMapBaseAddr, aMapNtStatus,
aLoadStatus);
}
/* static */
@ -90,24 +93,28 @@ bool ModuleLoadFrame::ExistsTopFrame() { return !!sTopFrame.get(); }
void ModuleLoadFrame::OnSectionMap(nt::AllocatedUnicodeString&& aSectionName,
const void* aMapBaseAddr,
NTSTATUS aMapNtStatus) {
NTSTATUS aMapNtStatus,
ModuleLoadInfo::Status aLoadStatus) {
if (mLoadInfo.mBaseAddr) {
// If mBaseAddr is not null then |this| has already seen a module load. This
// means that we are witnessing a bare section map.
OnBareSectionMap(std::move(aSectionName), aMapBaseAddr, aMapNtStatus);
OnBareSectionMap(std::move(aSectionName), aMapBaseAddr, aMapNtStatus,
aLoadStatus);
return;
}
mLoadInfo.mSectionName = std::move(aSectionName);
mLoadInfo.mBaseAddr = aMapBaseAddr;
mLoadInfo.mStatus = aLoadStatus;
}
/* static */
void ModuleLoadFrame::OnBareSectionMap(
nt::AllocatedUnicodeString&& aSectionName, const void* aMapBaseAddr,
NTSTATUS aMapNtStatus) {
NTSTATUS aMapNtStatus, ModuleLoadInfo::Status aLoadStatus) {
// We call the special constructor variant that is used for bare mappings.
ModuleLoadFrame frame(std::move(aSectionName), aMapBaseAddr, aMapNtStatus);
ModuleLoadFrame frame(std::move(aSectionName), aMapBaseAddr, aMapNtStatus,
aLoadStatus);
}
NTSTATUS ModuleLoadFrame::SetLoadStatus(NTSTATUS aNtStatus,

View File

@ -36,7 +36,8 @@ class MOZ_RAII ModuleLoadFrame final {
* This static method is called by the NtMapViewOfSection hook.
*/
static void NotifySectionMap(nt::AllocatedUnicodeString&& aSectionName,
const void* aMapBaseAddr, NTSTATUS aMapNtStatus);
const void* aMapBaseAddr, NTSTATUS aMapNtStatus,
ModuleLoadInfo::Status aLoadStatus);
static bool ExistsTopFrame();
/**
@ -55,11 +56,13 @@ class MOZ_RAII ModuleLoadFrame final {
* Called by OnBareSectionMap to construct a frame for a bare load.
*/
ModuleLoadFrame(nt::AllocatedUnicodeString&& aSectionName,
const void* aMapBaseAddr, NTSTATUS aNtStatus);
const void* aMapBaseAddr, NTSTATUS aNtStatus,
ModuleLoadInfo::Status aLoadStatus);
void SetLSPSubstitutionRequired(PCUNICODE_STRING aLeafName);
void OnSectionMap(nt::AllocatedUnicodeString&& aSectionName,
const void* aMapBaseAddr, NTSTATUS aMapNtStatus);
const void* aMapBaseAddr, NTSTATUS aMapNtStatus,
ModuleLoadInfo::Status aLoadStatus);
/**
* A "bare" section mapping is one that was mapped without the code passing
@ -67,7 +70,8 @@ class MOZ_RAII ModuleLoadFrame final {
* that condition.
*/
static void OnBareSectionMap(nt::AllocatedUnicodeString&& aSectionName,
const void* aMapBaseAddr, NTSTATUS aMapNtStatus);
const void* aMapBaseAddr, NTSTATUS aMapNtStatus,
ModuleLoadInfo::Status aLoadStatus);
private:
// Link to the previous frame

View File

@ -74,7 +74,10 @@ void LoaderObserver::OnEndDllLoad(void* aContext, NTSTATUS aNtStatus,
loadContext->mDynamicStringStorage.get()));
}
if (!NT_SUCCESS(aNtStatus) || !aModuleLoadInfo.WasMapped()) {
// We want to record a denied DLL load regardless of |aNtStatus| because
// |aNtStatus| is set to access-denied when DLL load was blocked.
if ((!NT_SUCCESS(aNtStatus) && !aModuleLoadInfo.WasDenied()) ||
!aModuleLoadInfo.WasMapped()) {
return;
}

View File

@ -14,6 +14,12 @@
namespace mozilla {
struct ModuleLoadInfo final {
enum class Status : uint32_t {
Loaded = 0,
Blocked,
Redirected,
};
// We do not provide these methods inside Gecko proper.
#if !defined(MOZILLA_INTERNAL_API)
@ -24,7 +30,8 @@ struct ModuleLoadInfo final {
: mLoadTimeInfo(),
mThreadId(nt::RtlGetCurrentThreadId()),
mRequestedDllName(aRequestedDllName),
mBaseAddr(nullptr) {
mBaseAddr(nullptr),
mStatus(Status::Loaded) {
# if defined(IMPL_MFBT)
::QueryPerformanceCounter(&mBeginTimestamp);
# else
@ -39,11 +46,12 @@ struct ModuleLoadInfo final {
* of another library.
*/
ModuleLoadInfo(nt::AllocatedUnicodeString&& aSectionName,
const void* aBaseAddr)
const void* aBaseAddr, Status aLoadStatus)
: mLoadTimeInfo(),
mThreadId(nt::RtlGetCurrentThreadId()),
mSectionName(std::move(aSectionName)),
mBaseAddr(aBaseAddr) {
mBaseAddr(aBaseAddr),
mStatus(aLoadStatus) {
# if defined(IMPL_MFBT)
::QueryPerformanceCounter(&mBeginTimestamp);
# else
@ -127,6 +135,14 @@ struct ModuleLoadInfo final {
*/
bool WasMapped() const { return !mSectionName.IsEmpty(); }
/**
* Returns true for DLL load which was denied by our blocklist.
*/
bool WasDenied() const {
return mStatus == ModuleLoadInfo::Status::Blocked ||
mStatus == ModuleLoadInfo::Status::Redirected;
}
// Timestamp for the creation of this event
LARGE_INTEGER mBeginTimestamp;
// Duration of the LdrLoadDll call
@ -143,6 +159,8 @@ struct ModuleLoadInfo final {
const void* mBaseAddr;
// If the module was successfully loaded, stack trace of the DLL load request
Vector<PVOID, 0, nt::RtlAllocPolicy> mBacktrace;
// The status of DLL load
Status mStatus;
};
using ModuleLoadInfoVec = Vector<ModuleLoadInfo, 0, nt::RtlAllocPolicy>;

View File

@ -200,7 +200,8 @@ ProcessedModuleLoadEvent::ProcessedModuleLoadEvent()
: mProcessUptimeMS(0ULL),
mThreadId(0UL),
mBaseAddress(0U),
mIsDependent(false) {}
mIsDependent(false),
mLoadStatus(0) {}
ProcessedModuleLoadEvent::ProcessedModuleLoadEvent(
glue::EnhancedModuleLoadInfo&& aModLoadInfo,
@ -213,7 +214,8 @@ ProcessedModuleLoadEvent::ProcessedModuleLoadEvent(
mBaseAddress(
reinterpret_cast<uintptr_t>(aModLoadInfo.mNtLoadInfo.mBaseAddr)),
mModule(std::move(aModuleRecord)),
mIsDependent(aIsDependent) {
mIsDependent(aIsDependent),
mLoadStatus(static_cast<uint32_t>(aModLoadInfo.mNtLoadInfo.mStatus)) {
if (!mModule || !(*mModule)) {
return;
}

View File

@ -145,6 +145,7 @@ class ProcessedModuleLoadEvent final {
uintptr_t mBaseAddress;
RefPtr<ModuleRecord> mModule;
bool mIsDependent;
uint32_t mLoadStatus; // corresponding to enum ModuleLoadInfo::Status
ProcessedModuleLoadEvent(const ProcessedModuleLoadEvent&) = delete;
ProcessedModuleLoadEvent& operator=(const ProcessedModuleLoadEvent&) = delete;
@ -515,6 +516,7 @@ struct ParamTraits<mozilla::UntrustedModulesData> {
WriteParam(aMsg, aParam.mRequestedDllName);
WriteParam(aMsg, aParam.mBaseAddress);
WriteParam(aMsg, aParam.mIsDependent);
WriteParam(aMsg, aParam.mLoadStatus);
// We don't write the ModuleRecord directly; we write its key into the
// UntrustedModulesData::mModules hash table.
@ -556,6 +558,10 @@ struct ParamTraits<mozilla::UntrustedModulesData> {
return false;
}
if (!ReadParam(aMsg, aIter, &aResult->mLoadStatus)) {
return false;
}
nsAutoString resolvedNtName;
if (!ReadParam(aMsg, aIter, &resolvedNtName)) {
return false;