Bug 1705278 - Remove DependentModules from UntrustedModulesProcessor.cpp. r=aklotz

Bug 1620118 added a new field `isDependent` in the third-party-module ping
which is calculated in `UntrustedModulesProcessor`.  However, bug 1684532
revealed it was not accurate because some third-party applications revert
the import table to the original state immediately after their module was
loaded.

Now that we have a logic to determine `isDependent` in `NtMapViewOfSection`
to automatically block a module injected through the import table, we can
pass that value to the ping and remove the original logic in `UntrustedModulesProcessor`.

Differential Revision: https://phabricator.services.mozilla.com/D112227
This commit is contained in:
Toshihito Kikuchi 2021-04-16 19:35:55 +00:00
parent 4d1c01d79d
commit ebb9e9f364
10 changed files with 45 additions and 104 deletions

View File

@ -467,7 +467,7 @@ NTSTATUS NTAPI patched_NtMapViewOfSection(
if (nt::RtlGetProcessHeap()) { if (nt::RtlGetProcessHeap()) {
ModuleLoadFrame::NotifySectionMap( ModuleLoadFrame::NotifySectionMap(
nt::AllocatedUnicodeString(sectionFileName), *aBaseAddress, stubStatus, nt::AllocatedUnicodeString(sectionFileName), *aBaseAddress, stubStatus,
loadStatus); loadStatus, isDependent);
} }
if (loadStatus == ModuleLoadInfo::Status::Loaded || if (loadStatus == ModuleLoadInfo::Status::Loaded ||

View File

@ -25,12 +25,14 @@ ModuleLoadFrame::ModuleLoadFrame(PCUNICODE_STRING aRequestedDllName)
ModuleLoadFrame::ModuleLoadFrame(nt::AllocatedUnicodeString&& aSectionName, ModuleLoadFrame::ModuleLoadFrame(nt::AllocatedUnicodeString&& aSectionName,
const void* aMapBaseAddr, NTSTATUS aNtStatus, const void* aMapBaseAddr, NTSTATUS aNtStatus,
ModuleLoadInfo::Status aLoadStatus) ModuleLoadInfo::Status aLoadStatus,
bool aIsDependent)
: mPrev(sTopFrame.get()), : mPrev(sTopFrame.get()),
mContext(nullptr), mContext(nullptr),
mLSPSubstitutionRequired(false), mLSPSubstitutionRequired(false),
mLoadNtStatus(aNtStatus), mLoadNtStatus(aNtStatus),
mLoadInfo(std::move(aSectionName), aMapBaseAddr, aLoadStatus) { mLoadInfo(std::move(aSectionName), aMapBaseAddr, aLoadStatus,
aIsDependent) {
sTopFrame.set(this); sTopFrame.set(this);
gLoaderPrivateAPI.NotifyBeginDllLoad(&mContext, mLoadInfo.mSectionName); gLoaderPrivateAPI.NotifyBeginDllLoad(&mContext, mLoadInfo.mSectionName);
@ -70,7 +72,8 @@ void ModuleLoadFrame::SetLSPSubstitutionRequired(PCUNICODE_STRING aLeafName) {
/* static */ /* static */
void ModuleLoadFrame::NotifySectionMap( void ModuleLoadFrame::NotifySectionMap(
nt::AllocatedUnicodeString&& aSectionName, const void* aMapBaseAddr, nt::AllocatedUnicodeString&& aSectionName, const void* aMapBaseAddr,
NTSTATUS aMapNtStatus, ModuleLoadInfo::Status aLoadStatus) { NTSTATUS aMapNtStatus, ModuleLoadInfo::Status aLoadStatus,
bool aIsDependent) {
ModuleLoadFrame* topFrame = sTopFrame.get(); ModuleLoadFrame* topFrame = sTopFrame.get();
if (!topFrame) { if (!topFrame) {
// The only time that this data is useful is during initial mapping of // The only time that this data is useful is during initial mapping of
@ -79,13 +82,13 @@ void ModuleLoadFrame::NotifySectionMap(
// initial process startup. // initial process startup.
if (gLoaderPrivateAPI.IsDefaultObserver()) { if (gLoaderPrivateAPI.IsDefaultObserver()) {
OnBareSectionMap(std::move(aSectionName), aMapBaseAddr, aMapNtStatus, OnBareSectionMap(std::move(aSectionName), aMapBaseAddr, aMapNtStatus,
aLoadStatus); aLoadStatus, aIsDependent);
} }
return; return;
} }
topFrame->OnSectionMap(std::move(aSectionName), aMapBaseAddr, aMapNtStatus, topFrame->OnSectionMap(std::move(aSectionName), aMapBaseAddr, aMapNtStatus,
aLoadStatus); aLoadStatus, aIsDependent);
} }
/* static */ /* static */
@ -94,12 +97,13 @@ bool ModuleLoadFrame::ExistsTopFrame() { return !!sTopFrame.get(); }
void ModuleLoadFrame::OnSectionMap(nt::AllocatedUnicodeString&& aSectionName, void ModuleLoadFrame::OnSectionMap(nt::AllocatedUnicodeString&& aSectionName,
const void* aMapBaseAddr, const void* aMapBaseAddr,
NTSTATUS aMapNtStatus, NTSTATUS aMapNtStatus,
ModuleLoadInfo::Status aLoadStatus) { ModuleLoadInfo::Status aLoadStatus,
bool aIsDependent) {
if (mLoadInfo.mBaseAddr) { if (mLoadInfo.mBaseAddr) {
// If mBaseAddr is not null then |this| has already seen a module load. This // If mBaseAddr is not null then |this| has already seen a module load. This
// means that we are witnessing a bare section map. // means that we are witnessing a bare section map.
OnBareSectionMap(std::move(aSectionName), aMapBaseAddr, aMapNtStatus, OnBareSectionMap(std::move(aSectionName), aMapBaseAddr, aMapNtStatus,
aLoadStatus); aLoadStatus, aIsDependent);
return; return;
} }
@ -111,10 +115,11 @@ void ModuleLoadFrame::OnSectionMap(nt::AllocatedUnicodeString&& aSectionName,
/* static */ /* static */
void ModuleLoadFrame::OnBareSectionMap( void ModuleLoadFrame::OnBareSectionMap(
nt::AllocatedUnicodeString&& aSectionName, const void* aMapBaseAddr, nt::AllocatedUnicodeString&& aSectionName, const void* aMapBaseAddr,
NTSTATUS aMapNtStatus, ModuleLoadInfo::Status aLoadStatus) { NTSTATUS aMapNtStatus, ModuleLoadInfo::Status aLoadStatus,
bool aIsDependent) {
// We call the special constructor variant that is used for bare mappings. // 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); aLoadStatus, aIsDependent);
} }
NTSTATUS ModuleLoadFrame::SetLoadStatus(NTSTATUS aNtStatus, NTSTATUS ModuleLoadFrame::SetLoadStatus(NTSTATUS aNtStatus,

View File

@ -37,7 +37,8 @@ class MOZ_RAII ModuleLoadFrame final {
*/ */
static void NotifySectionMap(nt::AllocatedUnicodeString&& aSectionName, static void NotifySectionMap(nt::AllocatedUnicodeString&& aSectionName,
const void* aMapBaseAddr, NTSTATUS aMapNtStatus, const void* aMapBaseAddr, NTSTATUS aMapNtStatus,
ModuleLoadInfo::Status aLoadStatus); ModuleLoadInfo::Status aLoadStatus,
bool aIsDependent);
static bool ExistsTopFrame(); static bool ExistsTopFrame();
/** /**
@ -57,12 +58,12 @@ class MOZ_RAII ModuleLoadFrame final {
*/ */
ModuleLoadFrame(nt::AllocatedUnicodeString&& aSectionName, ModuleLoadFrame(nt::AllocatedUnicodeString&& aSectionName,
const void* aMapBaseAddr, NTSTATUS aNtStatus, const void* aMapBaseAddr, NTSTATUS aNtStatus,
ModuleLoadInfo::Status aLoadStatus); ModuleLoadInfo::Status aLoadStatus, bool aIsDependent);
void SetLSPSubstitutionRequired(PCUNICODE_STRING aLeafName); void SetLSPSubstitutionRequired(PCUNICODE_STRING aLeafName);
void OnSectionMap(nt::AllocatedUnicodeString&& aSectionName, void OnSectionMap(nt::AllocatedUnicodeString&& aSectionName,
const void* aMapBaseAddr, NTSTATUS aMapNtStatus, const void* aMapBaseAddr, NTSTATUS aMapNtStatus,
ModuleLoadInfo::Status aLoadStatus); ModuleLoadInfo::Status aLoadStatus, bool aIsDependent);
/** /**
* A "bare" section mapping is one that was mapped without the code passing * A "bare" section mapping is one that was mapped without the code passing
@ -71,7 +72,8 @@ class MOZ_RAII ModuleLoadFrame final {
*/ */
static void OnBareSectionMap(nt::AllocatedUnicodeString&& aSectionName, static void OnBareSectionMap(nt::AllocatedUnicodeString&& aSectionName,
const void* aMapBaseAddr, NTSTATUS aMapNtStatus, const void* aMapBaseAddr, NTSTATUS aMapNtStatus,
ModuleLoadInfo::Status aLoadStatus); ModuleLoadInfo::Status aLoadStatus,
bool aIsDependent);
private: private:
// Link to the previous frame // Link to the previous frame

View File

@ -34,7 +34,8 @@ struct ModuleLoadInfo final {
mThreadId(nt::RtlGetCurrentThreadId()), mThreadId(nt::RtlGetCurrentThreadId()),
mRequestedDllName(aRequestedDllName), mRequestedDllName(aRequestedDllName),
mBaseAddr(nullptr), mBaseAddr(nullptr),
mStatus(Status::Loaded) { mStatus(Status::Loaded),
mIsDependent(false) {
# if defined(IMPL_MFBT) # if defined(IMPL_MFBT)
::QueryPerformanceCounter(&mBeginTimestamp); ::QueryPerformanceCounter(&mBeginTimestamp);
# else # else
@ -49,12 +50,13 @@ struct ModuleLoadInfo final {
* of another library. * of another library.
*/ */
ModuleLoadInfo(nt::AllocatedUnicodeString&& aSectionName, ModuleLoadInfo(nt::AllocatedUnicodeString&& aSectionName,
const void* aBaseAddr, Status aLoadStatus) const void* aBaseAddr, Status aLoadStatus, bool aIsDependent)
: mLoadTimeInfo(), : mLoadTimeInfo(),
mThreadId(nt::RtlGetCurrentThreadId()), mThreadId(nt::RtlGetCurrentThreadId()),
mSectionName(std::move(aSectionName)), mSectionName(std::move(aSectionName)),
mBaseAddr(aBaseAddr), mBaseAddr(aBaseAddr),
mStatus(aLoadStatus) { mStatus(aLoadStatus),
mIsDependent(aIsDependent) {
# if defined(IMPL_MFBT) # if defined(IMPL_MFBT)
::QueryPerformanceCounter(&mBeginTimestamp); ::QueryPerformanceCounter(&mBeginTimestamp);
# else # else
@ -164,6 +166,8 @@ struct ModuleLoadInfo final {
Vector<PVOID, 0, nt::RtlAllocPolicy> mBacktrace; Vector<PVOID, 0, nt::RtlAllocPolicy> mBacktrace;
// The status of DLL load // The status of DLL load
Status mStatus; Status mStatus;
// Whether the module is one of the executables's dependent modules or not
bool mIsDependent;
}; };
using ModuleLoadInfoVec = Vector<ModuleLoadInfo, 0, nt::RtlAllocPolicy>; using ModuleLoadInfoVec = Vector<ModuleLoadInfo, 0, nt::RtlAllocPolicy>;

View File

@ -26,9 +26,7 @@
#include "mozilla/interceptor/TargetFunction.h" #include "mozilla/interceptor/TargetFunction.h"
#if defined(MOZILLA_INTERNAL_API) #if defined(MOZILLA_INTERNAL_API)
# include "nsHashKeys.h"
# include "nsString.h" # include "nsString.h"
# include "nsTHashtable.h"
#endif // defined(MOZILLA_INTERNAL_API) #endif // defined(MOZILLA_INTERNAL_API)
// The declarations within this #if block are intended to be used for initial // The declarations within this #if block are intended to be used for initial
@ -743,17 +741,6 @@ class MOZ_RAII PEHeaders final {
} }
} }
#if defined(MOZILLA_INTERNAL_API)
nsTHashtable<nsStringCaseInsensitiveHashKey> GenerateDependentModuleSet()
const {
nsTHashtable<nsStringCaseInsensitiveHashKey> dependentModuleSet;
EnumImportChunks([&dependentModuleSet](const char* aModule) {
dependentModuleSet.PutEntry(GetLeafName(NS_ConvertASCIItoUTF16(aModule)));
});
return dependentModuleSet;
}
#endif // defined(MOZILLA_INTERNAL_API)
/** /**
* If |aBoundaries| is given, this method checks whether each IAT entry is * If |aBoundaries| is given, this method checks whether each IAT entry is
* within the given range, and if any entry is out of the range, we return * within the given range, and if any entry is out of the range, we return

View File

@ -9,12 +9,19 @@
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "mozilla/NativeNt.h" #include "mozilla/NativeNt.h"
#include "nsHashKeys.h"
#include "nsTHashSet.h"
TEST(TestNativeNtGTest, GenerateDependentModuleSet) TEST(TestNativeNtGTest, GenerateDependentModuleSet)
{ {
mozilla::nt::PEHeaders executable(::GetModuleHandleW(nullptr)); mozilla::nt::PEHeaders executable(::GetModuleHandleW(nullptr));
auto dependentModules = executable.GenerateDependentModuleSet(); nsTHashSet<nsStringCaseInsensitiveHashKey> dependentModules;
EXPECT_NE(dependentModules.GetEntry(u"mozglue.dll"_ns), nullptr); executable.EnumImportChunks([&](const char* aModule) {
EXPECT_NE(dependentModules.GetEntry(u"MOZGLUE.dll"_ns), nullptr); dependentModules.Insert(
EXPECT_EQ(dependentModules.GetEntry(u"xxx.dll"_ns), nullptr); mozilla::nt::GetLeafName(NS_ConvertASCIItoUTF16(aModule)));
});
EXPECT_TRUE(dependentModules.Contains(u"mozglue.dll"_ns));
EXPECT_TRUE(dependentModules.Contains(u"MOZGLUE.dll"_ns));
EXPECT_FALSE(dependentModules.Contains(u"xxx.dll"_ns));
} }

View File

@ -205,7 +205,7 @@ ProcessedModuleLoadEvent::ProcessedModuleLoadEvent()
ProcessedModuleLoadEvent::ProcessedModuleLoadEvent( ProcessedModuleLoadEvent::ProcessedModuleLoadEvent(
glue::EnhancedModuleLoadInfo&& aModLoadInfo, glue::EnhancedModuleLoadInfo&& aModLoadInfo,
RefPtr<ModuleRecord>&& aModuleRecord, bool aIsDependent) RefPtr<ModuleRecord>&& aModuleRecord)
: mProcessUptimeMS(QPCTimeStampToProcessUptimeMilliseconds( : mProcessUptimeMS(QPCTimeStampToProcessUptimeMilliseconds(
aModLoadInfo.mNtLoadInfo.mBeginTimestamp)), aModLoadInfo.mNtLoadInfo.mBeginTimestamp)),
mLoadDurationMS(QPCLoadDurationToMilliseconds(aModLoadInfo.mNtLoadInfo)), mLoadDurationMS(QPCLoadDurationToMilliseconds(aModLoadInfo.mNtLoadInfo)),
@ -214,7 +214,7 @@ ProcessedModuleLoadEvent::ProcessedModuleLoadEvent(
mBaseAddress( mBaseAddress(
reinterpret_cast<uintptr_t>(aModLoadInfo.mNtLoadInfo.mBaseAddr)), reinterpret_cast<uintptr_t>(aModLoadInfo.mNtLoadInfo.mBaseAddr)),
mModule(std::move(aModuleRecord)), mModule(std::move(aModuleRecord)),
mIsDependent(aIsDependent), mIsDependent(aModLoadInfo.mNtLoadInfo.mIsDependent),
mLoadStatus(static_cast<uint32_t>(aModLoadInfo.mNtLoadInfo.mStatus)) { mLoadStatus(static_cast<uint32_t>(aModLoadInfo.mNtLoadInfo.mStatus)) {
if (!mModule || !(*mModule)) { if (!mModule || !(*mModule)) {
return; return;

View File

@ -127,8 +127,7 @@ class ProcessedModuleLoadEvent final {
public: public:
ProcessedModuleLoadEvent(); ProcessedModuleLoadEvent();
ProcessedModuleLoadEvent(glue::EnhancedModuleLoadInfo&& aModLoadInfo, ProcessedModuleLoadEvent(glue::EnhancedModuleLoadInfo&& aModLoadInfo,
RefPtr<ModuleRecord>&& aModuleRecord, RefPtr<ModuleRecord>&& aModuleRecord);
bool aIsDependent);
explicit operator bool() const { return mModule && *mModule; } explicit operator bool() const { return mModule && *mModule; }
bool IsXULLoad() const; bool IsXULLoad() const;

View File

@ -87,34 +87,6 @@ class MOZ_RAII BackgroundPriorityRegion final {
const BOOL mIsBackground; const BOOL mIsBackground;
}; };
// This class wraps a set of the executables's dependent modules
// that is delay-initialized the first time Lookup() is called.
class DependentModules final {
Maybe<nsTHashtable<nsStringCaseInsensitiveHashKey>> mDependentModules;
public:
bool Lookup(const nsString& aModulePath) {
if (aModulePath.IsEmpty()) {
return false;
}
if (mDependentModules.isNothing()) {
nt::PEHeaders executable(::GetModuleHandleW(nullptr));
// We generate a hash table only when the executable's import table is
// tampered. If the import table is intact, all dependent modules are
// legit and we're not interested in any of them. In such a case, we
// set an empty table so that this function returns false.
mDependentModules =
Some(executable.IsImportDirectoryTampered()
? executable.GenerateDependentModuleSet()
: nsTHashtable<nsStringCaseInsensitiveHashKey>());
}
return !!mDependentModules.ref().GetEntry(nt::GetLeafName(aModulePath));
}
};
/* static */ /* static */
bool UntrustedModulesProcessor::IsSupportedProcessType() { bool UntrustedModulesProcessor::IsSupportedProcessType() {
switch (XRE_GetProcessType()) { switch (XRE_GetProcessType()) {
@ -150,8 +122,7 @@ UntrustedModulesProcessor::UntrustedModulesProcessor()
LazyIdleThread::ManualShutdown)), LazyIdleThread::ManualShutdown)),
mUnprocessedMutex( mUnprocessedMutex(
"mozilla::UntrustedModulesProcessor::mUnprocessedMutex"), "mozilla::UntrustedModulesProcessor::mUnprocessedMutex"),
mAllowProcessing(true), mAllowProcessing(true) {
mIsFirstBatchProcessed(false) {
AddObservers(); AddObservers();
} }
@ -595,11 +566,8 @@ void UntrustedModulesProcessor::ProcessModuleLoadQueue() {
return; return;
} }
auto cleanup = MakeScopeExit([&]() { mIsFirstBatchProcessed = true; });
Telemetry::BatchProcessedStackGenerator stackProcessor; Telemetry::BatchProcessedStackGenerator stackProcessor;
ModulesMap modules; ModulesMap modules;
DependentModules dependentModules;
Maybe<double> maybeXulLoadDuration; Maybe<double> maybeXulLoadDuration;
Vector<Telemetry::ProcessedStack> processedStacks; Vector<Telemetry::ProcessedStack> processedStacks;
@ -624,18 +592,9 @@ void UntrustedModulesProcessor::ProcessModuleLoadQueue() {
return; return;
} }
bool isDependent = mIsFirstBatchProcessed
? false
: dependentModules.Lookup(module->mSanitizedDllName);
if (!mAllowProcessing) {
return;
}
glue::EnhancedModuleLoadInfo::BacktraceType backtrace = glue::EnhancedModuleLoadInfo::BacktraceType backtrace =
std::move(entry.mNtLoadInfo.mBacktrace); std::move(entry.mNtLoadInfo.mBacktrace);
ProcessedModuleLoadEvent event(std::move(entry), std::move(module), ProcessedModuleLoadEvent event(std::move(entry), std::move(module));
isDependent);
if (!event) { if (!event) {
// We don't have a sanitized DLL path, so we cannot include this event // We don't have a sanitized DLL path, so we cannot include this event
@ -857,10 +816,7 @@ void UntrustedModulesProcessor::CompleteProcessing(
return; return;
} }
auto cleanup = MakeScopeExit([&]() { mIsFirstBatchProcessed = true; });
Telemetry::BatchProcessedStackGenerator stackProcessor; Telemetry::BatchProcessedStackGenerator stackProcessor;
DependentModules dependentModules;
Maybe<double> maybeXulLoadDuration; Maybe<double> maybeXulLoadDuration;
Vector<Telemetry::ProcessedStack> processedStacks; Vector<Telemetry::ProcessedStack> processedStacks;
@ -883,19 +839,9 @@ void UntrustedModulesProcessor::CompleteProcessing(
return; return;
} }
bool isDependent =
mIsFirstBatchProcessed
? false
: dependentModules.Lookup(module->mSanitizedDllName);
if (!mAllowProcessing) {
return;
}
glue::EnhancedModuleLoadInfo::BacktraceType backtrace = glue::EnhancedModuleLoadInfo::BacktraceType backtrace =
std::move(item.mNtLoadInfo.mBacktrace); std::move(item.mNtLoadInfo.mBacktrace);
ProcessedModuleLoadEvent event(std::move(item), std::move(module), ProcessedModuleLoadEvent event(std::move(item), std::move(module));
isDependent);
if (!mAllowProcessing) { if (!mAllowProcessing) {
return; return;

View File

@ -141,15 +141,6 @@ class UntrustedModulesProcessor final : public nsIObserver {
// This member may be touched by any thread // This member may be touched by any thread
Atomic<bool> mAllowProcessing; Atomic<bool> mAllowProcessing;
// This member must only be touched on mThread, making sure a hash table of
// dependent modules is initialized once in a process when the first batch
// of queued events is processed. We don't need the hash table to process
// subsequent queues because we're interested in modules imported via the
// executable's Import Table which are all expected to be in the first queue.
// A boolean flag is enough to control initialization because tasks of
// processing queues are serialized.
bool mIsFirstBatchProcessed;
}; };
} // namespace mozilla } // namespace mozilla