diff options
Diffstat (limited to 'toolkit/xre/UntrustedModulesProcessor.cpp')
-rw-r--r-- | toolkit/xre/UntrustedModulesProcessor.cpp | 1042 |
1 files changed, 1042 insertions, 0 deletions
diff --git a/toolkit/xre/UntrustedModulesProcessor.cpp b/toolkit/xre/UntrustedModulesProcessor.cpp new file mode 100644 index 0000000000..96ce5b4f1d --- /dev/null +++ b/toolkit/xre/UntrustedModulesProcessor.cpp @@ -0,0 +1,1042 @@ +/* -*- 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 "UntrustedModulesProcessor.h" + +#include <windows.h> + +#include "mozilla/CmdLineAndEnvUtils.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/Likely.h" +#include "mozilla/RDDParent.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Services.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "ModuleEvaluator.h" +#include "nsCOMPtr.h" +#include "nsHashKeys.h" +#include "nsIObserverService.h" +#include "nsTHashtable.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "private/prpriv.h" // For PR_GetThreadID + +static DWORD ToWin32ThreadId(nsIThread* aThread) { + if (!aThread) { + return 0UL; + } + + PRThread* prThread; + nsresult rv = aThread->GetPRThread(&prThread); + if (NS_FAILED(rv)) { + // Possible when a LazyInitThread's underlying nsThread is not present + return 0UL; + } + + return DWORD(::PR_GetThreadID(prThread)); +} + +namespace mozilla { + +class MOZ_RAII BackgroundPriorityRegion final { + public: + BackgroundPriorityRegion() + : mIsBackground( + ::SetThreadPriority(::GetCurrentThread(), THREAD_PRIORITY_IDLE)) {} + + ~BackgroundPriorityRegion() { + if (!mIsBackground) { + return; + } + + Clear(::GetCurrentThread()); + } + + static void Clear(nsIThread* aThread) { + DWORD tid = ToWin32ThreadId(aThread); + if (!tid) { + return; + } + + nsAutoHandle thread( + ::OpenThread(THREAD_SET_LIMITED_INFORMATION, FALSE, tid)); + if (!thread) { + return; + } + + Clear(thread); + } + + BackgroundPriorityRegion(const BackgroundPriorityRegion&) = delete; + BackgroundPriorityRegion(BackgroundPriorityRegion&&) = delete; + BackgroundPriorityRegion& operator=(const BackgroundPriorityRegion&) = delete; + BackgroundPriorityRegion& operator=(BackgroundPriorityRegion&&) = delete; + + private: + static void Clear(HANDLE aThread) { + DebugOnly<BOOL> ok = ::SetThreadPriority(aThread, THREAD_PRIORITY_NORMAL); + MOZ_ASSERT(ok); + } + + private: + 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 */ +bool UntrustedModulesProcessor::IsSupportedProcessType() { + switch (XRE_GetProcessType()) { + case GeckoProcessType_Default: + case GeckoProcessType_Content: + return Telemetry::CanRecordReleaseData(); + case GeckoProcessType_RDD: + // For RDD process, we check the telemetry settings in RDDChild::Init() + // running in the browser process because CanRecordReleaseData() always + // returns false here. + return true; + default: + return false; + } +} + +/* static */ +RefPtr<UntrustedModulesProcessor> UntrustedModulesProcessor::Create() { + if (!IsSupportedProcessType()) { + return nullptr; + } + + RefPtr<UntrustedModulesProcessor> result(new UntrustedModulesProcessor()); + return result.forget(); +} + +NS_IMPL_ISUPPORTS(UntrustedModulesProcessor, nsIObserver) + +static const uint32_t kThreadTimeoutMS = 120000; // 2 minutes + +UntrustedModulesProcessor::UntrustedModulesProcessor() + : mThread(new LazyIdleThread(kThreadTimeoutMS, "Untrusted Modules"_ns, + LazyIdleThread::ManualShutdown)), + mUnprocessedMutex( + "mozilla::UntrustedModulesProcessor::mUnprocessedMutex"), + mAllowProcessing(true), + mIsFirstBatchProcessed(false) { + AddObservers(); +} + +void UntrustedModulesProcessor::AddObservers() { + nsCOMPtr<nsIObserverService> obsServ(services::GetObserverService()); + obsServ->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false); + obsServ->AddObserver(this, "xpcom-shutdown-threads", false); + if (XRE_IsContentProcess()) { + obsServ->AddObserver(this, "content-child-will-shutdown", false); + } +} + +void UntrustedModulesProcessor::Disable() { + // Ensure that mThread cannot run at low priority anymore + BackgroundPriorityRegion::Clear(mThread); + + // No more background processing allowed beyond this point + if (!mAllowProcessing.exchange(false)) { + return; + } + + MutexAutoLock lock(mUnprocessedMutex); + CancelScheduledProcessing(lock); +} + +NS_IMETHODIMP UntrustedModulesProcessor::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID) || + !strcmp(aTopic, "content-child-will-shutdown")) { + Disable(); + return NS_OK; + } + + if (!strcmp(aTopic, "xpcom-shutdown-threads")) { + Disable(); + mThread->Shutdown(); + + RemoveObservers(); + + mThread = nullptr; + return NS_OK; + } + + MOZ_ASSERT_UNREACHABLE("Not reachable"); + + return NS_OK; +} + +void UntrustedModulesProcessor::RemoveObservers() { + nsCOMPtr<nsIObserverService> obsServ(services::GetObserverService()); + obsServ->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID); + obsServ->RemoveObserver(this, "xpcom-shutdown-threads"); + if (XRE_IsContentProcess()) { + obsServ->RemoveObserver(this, "content-child-will-shutdown"); + } +} + +void UntrustedModulesProcessor::ScheduleNonEmptyQueueProcessing( + const MutexAutoLock& aProofOfLock) { + // In case something tried to load a DLL during shutdown + if (!mThread) { + return; + } + +#if defined(ENABLE_TESTS) + // Don't bother scheduling background processing in short-lived xpcshell + // processes; it makes the test suites take too long. + if (MOZ_UNLIKELY(mozilla::EnvHasValue("XPCSHELL_TEST_PROFILE_DIR"))) { + return; + } +#endif // defined(ENABLE_TESTS) + + if (mIdleRunnable) { + return; + } + + if (!mAllowProcessing) { + return; + } + + // Schedule a runnable to trigger background processing once the main thread + // has gone idle. We do it this way to ensure that we don't start doing a + // bunch of processing during periods of heavy main thread activity. + nsCOMPtr<nsIRunnable> idleRunnable(NewCancelableRunnableMethod( + "UntrustedModulesProcessor::DispatchBackgroundProcessing", this, + &UntrustedModulesProcessor::DispatchBackgroundProcessing)); + + if (NS_FAILED(NS_DispatchToMainThreadQueue(do_AddRef(idleRunnable), + EventQueuePriority::Idle))) { + return; + } + + mIdleRunnable = std::move(idleRunnable); +} + +void UntrustedModulesProcessor::CancelScheduledProcessing( + const MutexAutoLock& aProofOfLock) { + if (!mIdleRunnable) { + return; + } + + nsCOMPtr<nsICancelableRunnable> cancelable(do_QueryInterface(mIdleRunnable)); + if (cancelable) { + // Stop the pending idle runnable from doing anything + cancelable->Cancel(); + } + + mIdleRunnable = nullptr; +} + +void UntrustedModulesProcessor::DispatchBackgroundProcessing() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mAllowProcessing) { + return; + } + + nsCOMPtr<nsIRunnable> runnable(NewRunnableMethod( + "UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue", this, + &UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue)); + + mThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); +} + +void UntrustedModulesProcessor::Enqueue( + glue::EnhancedModuleLoadInfo&& aModLoadInfo) { + if (!mAllowProcessing) { + return; + } + + DWORD bgThreadId = ToWin32ThreadId(mThread); + if (aModLoadInfo.mNtLoadInfo.mThreadId == bgThreadId) { + // Exclude loads that were caused by our own background thread + return; + } + + MutexAutoLock lock(mUnprocessedMutex); + + Unused << mUnprocessedModuleLoads.emplaceBack(std::move(aModLoadInfo)); + + ScheduleNonEmptyQueueProcessing(lock); +} + +void UntrustedModulesProcessor::Enqueue(ModuleLoadInfoVec&& aEvents) { + if (!mAllowProcessing) { + return; + } + + // We do not need to attempt to exclude our background thread in this case + // because |aEvents| was accumulated prior to |mThread|'s existence. + + MutexAutoLock lock(mUnprocessedMutex); + + for (auto& event : aEvents) { + Unused << mUnprocessedModuleLoads.emplaceBack(std::move(event)); + } + + ScheduleNonEmptyQueueProcessing(lock); +} + +void UntrustedModulesProcessor::AssertRunningOnLazyIdleThread() { +#if defined(DEBUG) + PRThread* curThread; + PRThread* lazyIdleThread; + + MOZ_ASSERT(NS_SUCCEEDED(NS_GetCurrentThread()->GetPRThread(&curThread)) && + NS_SUCCEEDED(mThread->GetPRThread(&lazyIdleThread)) && + curThread == lazyIdleThread); +#endif // defined(DEBUG) +} + +RefPtr<UntrustedModulesPromise> UntrustedModulesProcessor::GetProcessedData() { + MOZ_ASSERT(NS_IsMainThread()); + + // Clear any background priority in case background processing is running. + BackgroundPriorityRegion::Clear(mThread); + + RefPtr<UntrustedModulesProcessor> self(this); + return InvokeAsync( + mThread->SerialEventTarget(), __func__, + [self = std::move(self)]() { return self->GetProcessedDataInternal(); }); +} + +RefPtr<ModulesTrustPromise> UntrustedModulesProcessor::GetModulesTrust( + ModulePaths&& aModPaths, bool aRunAtNormalPriority) { + MOZ_ASSERT(XRE_IsParentProcess() && NS_IsMainThread()); + + if (!mAllowProcessing) { + return ModulesTrustPromise::CreateAndReject( + NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); + } + + RefPtr<UntrustedModulesProcessor> self(this); + auto run = [self = std::move(self), modPaths = std::move(aModPaths), + runNormal = aRunAtNormalPriority]() mutable { + return self->GetModulesTrustInternal(std::move(modPaths), runNormal); + }; + + if (aRunAtNormalPriority) { + // Clear any background priority in case background processing is running. + BackgroundPriorityRegion::Clear(mThread); + + return InvokeAsync(mThread->SerialEventTarget(), __func__, std::move(run)); + } + + RefPtr<ModulesTrustPromise::Private> p( + new ModulesTrustPromise::Private(__func__)); + nsCOMPtr<nsISerialEventTarget> evtTarget(mThread->SerialEventTarget()); + const char* source = __func__; + + auto runWrap = [evtTarget = std::move(evtTarget), p, source, + run = std::move(run)]() mutable -> void { + InvokeAsync(evtTarget, source, std::move(run))->ChainTo(p.forget(), source); + }; + + nsCOMPtr<nsIRunnable> idleRunnable( + NS_NewRunnableFunction(source, std::move(runWrap))); + + nsresult rv = NS_DispatchToMainThreadQueue(idleRunnable.forget(), + EventQueuePriority::Idle); + if (NS_FAILED(rv)) { + p->Reject(rv, source); + } + + return p; +} + +RefPtr<UntrustedModulesPromise> +UntrustedModulesProcessor::GetProcessedDataInternal() { + AssertRunningOnLazyIdleThread(); + if (!XRE_IsParentProcess()) { + return GetProcessedDataInternalChildProcess(); + } + + ProcessModuleLoadQueue(); + + return GetAllProcessedData(__func__); +} + +RefPtr<UntrustedModulesPromise> UntrustedModulesProcessor::GetAllProcessedData( + const char* aSource) { + AssertRunningOnLazyIdleThread(); + + UntrustedModulesData result; + + if (!mProcessedModuleLoads) { + return UntrustedModulesPromise::CreateAndResolve(Nothing(), aSource); + } + + result.Swap(mProcessedModuleLoads); + + result.mElapsed = TimeStamp::Now() - TimeStamp::ProcessCreation(); + + return UntrustedModulesPromise::CreateAndResolve( + Some(UntrustedModulesData(std::move(result))), aSource); +} + +RefPtr<UntrustedModulesPromise> +UntrustedModulesProcessor::GetProcessedDataInternalChildProcess() { + AssertRunningOnLazyIdleThread(); + MOZ_ASSERT(!XRE_IsParentProcess()); + + RefPtr<GetModulesTrustPromise> whenProcessed( + ProcessModuleLoadQueueChildProcess(Priority::Default)); + + RefPtr<UntrustedModulesProcessor> self(this); + RefPtr<UntrustedModulesPromise::Private> p( + new UntrustedModulesPromise::Private(__func__)); + nsCOMPtr<nsISerialEventTarget> evtTarget(mThread->SerialEventTarget()); + + const char* source = __func__; + auto completionRoutine = [evtTarget = std::move(evtTarget), p, + self = std::move(self), source, + whenProcessed = std::move(whenProcessed)]() { + MOZ_ASSERT(NS_IsMainThread()); + if (!self->mAllowProcessing) { + // We can't do any more work, just reject all the things + whenProcessed->Then( + GetMainThreadSerialEventTarget(), source, + [p, source](Maybe<ModulesMapResultWithLoads>&& aResult) { + p->Reject(NS_ERROR_ILLEGAL_DURING_SHUTDOWN, source); + }, + [p, source](nsresult aRv) { p->Reject(aRv, source); }); + return; + } + + whenProcessed->Then( + evtTarget, source, + [p, self = std::move(self), + source](Maybe<ModulesMapResultWithLoads>&& aResult) mutable { + if (aResult.isSome()) { + self->CompleteProcessing(std::move(aResult.ref())); + } + self->GetAllProcessedData(source)->ChainTo(p.forget(), source); + }, + [p, source](nsresult aRv) { p->Reject(aRv, source); }); + }; + + // We always send |completionRoutine| on a trip through the main thread + // due to some subtlety with |mThread| being a LazyIdleThread: we can only + // Dispatch or Then to |mThread| from its creating thread, which is the + // main thread. Hopefully we can get rid of this in the future and just + // invoke whenProcessed->Then() directly. + nsresult rv = NS_DispatchToMainThread( + NS_NewRunnableFunction(__func__, std::move(completionRoutine))); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (NS_FAILED(rv)) { + p->Reject(rv, __func__); + } + + return p; +} + +void UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue() { + if (!mAllowProcessing) { + return; + } + + BackgroundPriorityRegion bgRgn; + + if (!mAllowProcessing) { + return; + } + + ProcessModuleLoadQueue(); +} + +RefPtr<ModuleRecord> UntrustedModulesProcessor::GetOrAddModuleRecord( + ModulesMap& aModules, const ModuleEvaluator& aModEval, + const glue::EnhancedModuleLoadInfo& aModLoadInfo) { + return GetOrAddModuleRecord(aModules, aModEval, + aModLoadInfo.mNtLoadInfo.mSectionName.AsString()); +} + +RefPtr<ModuleRecord> UntrustedModulesProcessor::GetOrAddModuleRecord( + ModulesMap& aModules, const ModuleEvaluator& aModEval, + const nsAString& aResolvedNtPath) { + MOZ_ASSERT(XRE_IsParentProcess()); + + auto addPtr = aModules.LookupForAdd(aResolvedNtPath); + if (addPtr) { + return addPtr.Data(); + } + + RefPtr<ModuleRecord> newMod(new ModuleRecord(aResolvedNtPath)); + if (!(*newMod)) { + addPtr.OrRemove(); + return nullptr; + } + + Maybe<ModuleTrustFlags> maybeTrust = aModEval.GetTrust(*newMod); + if (maybeTrust.isNothing()) { + addPtr.OrRemove(); + return nullptr; + } + + newMod->mTrustFlags = maybeTrust.value(); + + addPtr.OrInsert([newMod]() { return newMod; }); + + return newMod; +} + +RefPtr<ModuleRecord> UntrustedModulesProcessor::GetModuleRecord( + const ModulesMap& aModules, + const glue::EnhancedModuleLoadInfo& aModuleLoadInfo) { + MOZ_ASSERT(!XRE_IsParentProcess()); + + return aModules.Get(aModuleLoadInfo.mNtLoadInfo.mSectionName.AsString()); +} + +void UntrustedModulesProcessor::BackgroundProcessModuleLoadQueueChildProcess() { + RefPtr<GetModulesTrustPromise> whenProcessed( + ProcessModuleLoadQueueChildProcess(Priority::Background)); + + RefPtr<UntrustedModulesProcessor> self(this); + nsCOMPtr<nsISerialEventTarget> evtTarget(mThread->SerialEventTarget()); + + const char* source = __func__; + auto completionRoutine = [evtTarget = std::move(evtTarget), + self = std::move(self), source, + whenProcessed = std::move(whenProcessed)]() { + MOZ_ASSERT(NS_IsMainThread()); + if (!self->mAllowProcessing) { + // We can't do any more work, just no-op + whenProcessed->Then( + GetMainThreadSerialEventTarget(), source, + [](Maybe<ModulesMapResultWithLoads>&& aResult) {}, + [](nsresult aRv) {}); + return; + } + + whenProcessed->Then( + evtTarget, source, + [self = std::move(self)](Maybe<ModulesMapResultWithLoads>&& aResult) { + if (aResult.isNothing() || !self->mAllowProcessing) { + // Nothing to do + return; + } + + BackgroundPriorityRegion bgRgn; + self->CompleteProcessing(std::move(aResult.ref())); + }, + [](nsresult aRv) {}); + }; + + // We always send |completionRoutine| on a trip through the main thread + // due to some subtlety with |mThread| being a LazyIdleThread: we can only + // Dispatch or Then to |mThread| from its creating thread, which is the + // main thread. Hopefully we can get rid of this in the future and just + // invoke whenProcessed->Then() directly. + DebugOnly<nsresult> rv = NS_DispatchToMainThread( + NS_NewRunnableFunction(__func__, std::move(completionRoutine))); + MOZ_ASSERT(NS_SUCCEEDED(rv)); +} + +// This function contains multiple |mAllowProcessing| checks so that we can +// quickly bail out at the first sign of shutdown. This may be important when +// the current thread is running under background priority. +void UntrustedModulesProcessor::ProcessModuleLoadQueue() { + AssertRunningOnLazyIdleThread(); + if (!XRE_IsParentProcess()) { + BackgroundProcessModuleLoadQueueChildProcess(); + return; + } + + Vector<glue::EnhancedModuleLoadInfo> loadsToProcess; + + { // Scope for lock + MutexAutoLock lock(mUnprocessedMutex); + CancelScheduledProcessing(lock); + loadsToProcess.swap(mUnprocessedModuleLoads); + } + + if (!mAllowProcessing || loadsToProcess.empty()) { + return; + } + + ModuleEvaluator modEval; + MOZ_ASSERT(!!modEval); + if (!modEval) { + return; + } + + auto cleanup = MakeScopeExit([&]() { mIsFirstBatchProcessed = true; }); + + Telemetry::BatchProcessedStackGenerator stackProcessor; + ModulesMap modules; + DependentModules dependentModules; + + Maybe<double> maybeXulLoadDuration; + Vector<Telemetry::ProcessedStack> processedStacks; + Vector<ProcessedModuleLoadEvent> processedEvents; + uint32_t sanitizationFailures = 0; + uint32_t trustTestFailures = 0; + + for (glue::EnhancedModuleLoadInfo& entry : loadsToProcess) { + if (!mAllowProcessing) { + return; + } + + RefPtr<ModuleRecord> module(GetOrAddModuleRecord(modules, modEval, entry)); + if (!module) { + // We failed to obtain trust information about the module. + // Don't include test failures in the ping to avoid flooding it. + ++trustTestFailures; + continue; + } + + if (!mAllowProcessing) { + return; + } + + bool isDependent = mIsFirstBatchProcessed + ? false + : dependentModules.Lookup(module->mSanitizedDllName); + + if (!mAllowProcessing) { + return; + } + + glue::EnhancedModuleLoadInfo::BacktraceType backtrace = + std::move(entry.mNtLoadInfo.mBacktrace); + ProcessedModuleLoadEvent event(std::move(entry), std::move(module), + isDependent); + + if (!event) { + // We don't have a sanitized DLL path, so we cannot include this event + // for privacy reasons. + ++sanitizationFailures; + continue; + } + + if (!mAllowProcessing) { + return; + } + + if (event.IsTrusted()) { + if (event.IsXULLoad()) { + maybeXulLoadDuration = event.mLoadDurationMS; + } + + // Trusted modules are not included in the ping + continue; + } + + if (!mAllowProcessing) { + return; + } + + Telemetry::ProcessedStack processedStack = + stackProcessor.GetStackAndModules(backtrace); + + if (!mAllowProcessing) { + return; + } + + Unused << processedStacks.emplaceBack(std::move(processedStack)); + Unused << processedEvents.emplaceBack(std::move(event)); + } + + if (processedStacks.empty() && processedEvents.empty() && + !sanitizationFailures && !trustTestFailures) { + // Nothing to save + return; + } + + if (!mAllowProcessing) { + return; + } + + mProcessedModuleLoads.AddNewLoads(modules, std::move(processedEvents), + std::move(processedStacks)); + if (maybeXulLoadDuration) { + MOZ_ASSERT(!mProcessedModuleLoads.mXULLoadDurationMS); + mProcessedModuleLoads.mXULLoadDurationMS = maybeXulLoadDuration; + } + + mProcessedModuleLoads.mSanitizationFailures += sanitizationFailures; + mProcessedModuleLoads.mTrustTestFailures += trustTestFailures; +} + +template <typename ActorT> +static RefPtr<GetModulesTrustIpcPromise> SendGetModulesTrust( + ActorT* aActor, ModulePaths&& aModPaths, bool aRunAtNormalPriority) { + MOZ_ASSERT(NS_IsMainThread()); + return aActor->SendGetModulesTrust(std::move(aModPaths), + aRunAtNormalPriority); +} + +RefPtr<GetModulesTrustIpcPromise> +UntrustedModulesProcessor::SendGetModulesTrust(ModulePaths&& aModules, + Priority aPriority) { + MOZ_ASSERT(NS_IsMainThread()); + bool runNormal = aPriority == Priority::Default; + + switch (XRE_GetProcessType()) { + case GeckoProcessType_Content: { + return ::SendGetModulesTrust(dom::ContentChild::GetSingleton(), + std::move(aModules), runNormal); + } + case GeckoProcessType_RDD: { + return ::SendGetModulesTrust(RDDParent::GetSingleton(), + std::move(aModules), runNormal); + } + default: { + MOZ_ASSERT_UNREACHABLE("Unsupported process type"); + return GetModulesTrustIpcPromise::CreateAndReject( + ipc::ResponseRejectReason::SendError, __func__); + } + } +} + +/** + * This method works very similarly to ProcessModuleLoadQueue, with the + * exception that a sandboxed child process does not have sufficient rights to + * be able to evaluate a module's trustworthiness. Instead, we accumulate the + * resolved paths for all of the modules in this batch and send them to the + * parent to determine trustworthiness. + * + * The parent process returns a list of untrusted modules and invokes + * CompleteProcessing to handle the remainder of the process. + * + * By doing it this way, we minimize the amount of data that needs to be sent + * over IPC and avoid the need to process every load's metadata only + * to throw most of it away (since most modules will be trusted). + */ +RefPtr<UntrustedModulesProcessor::GetModulesTrustPromise> +UntrustedModulesProcessor::ProcessModuleLoadQueueChildProcess( + UntrustedModulesProcessor::Priority aPriority) { + AssertRunningOnLazyIdleThread(); + MOZ_ASSERT(!XRE_IsParentProcess()); + + Vector<glue::EnhancedModuleLoadInfo> loadsToProcess; + + { // Scope for lock + MutexAutoLock lock(mUnprocessedMutex); + CancelScheduledProcessing(lock); + loadsToProcess.swap(mUnprocessedModuleLoads); + } + + if (loadsToProcess.empty()) { + // Nothing to process + return GetModulesTrustPromise::CreateAndResolve(Nothing(), __func__); + } + + if (!mAllowProcessing) { + return GetModulesTrustPromise::CreateAndReject( + NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); + } + + nsTHashtable<nsStringCaseInsensitiveHashKey> moduleNtPathSet; + + // Build a set of modules to be processed by the parent + for (glue::EnhancedModuleLoadInfo& entry : loadsToProcess) { + if (!mAllowProcessing) { + return GetModulesTrustPromise::CreateAndReject( + NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); + } + + moduleNtPathSet.PutEntry(entry.mNtLoadInfo.mSectionName.AsString()); + } + + if (!mAllowProcessing) { + return GetModulesTrustPromise::CreateAndReject( + NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); + } + + MOZ_ASSERT(!moduleNtPathSet.IsEmpty()); + if (moduleNtPathSet.IsEmpty()) { + // Nothing to process + return GetModulesTrustPromise::CreateAndResolve(Nothing(), __func__); + } + + ModulePaths moduleNtPaths(std::move(moduleNtPathSet)); + + if (!mAllowProcessing) { + return GetModulesTrustPromise::CreateAndReject( + NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); + } + + RefPtr<UntrustedModulesProcessor> self(this); + + auto invoker = [self = std::move(self), + moduleNtPaths = std::move(moduleNtPaths), + priority = aPriority]() mutable { + return self->SendGetModulesTrust(std::move(moduleNtPaths), priority); + }; + + RefPtr<GetModulesTrustPromise::Private> p( + new GetModulesTrustPromise::Private(__func__)); + + if (!mAllowProcessing) { + p->Reject(NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); + return p; + } + + // Send the IPC request via the main thread + InvokeAsync(GetMainThreadSerialEventTarget(), __func__, std::move(invoker)) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [p, loads = std::move(loadsToProcess)]( + Maybe<ModulesMapResult>&& aResult) mutable { + ModulesMapResultWithLoads result(std::move(aResult), + std::move(loads)); + p->Resolve(Some(ModulesMapResultWithLoads(std::move(result))), + __func__); + }, + [p](ipc::ResponseRejectReason aReason) { + p->Reject(NS_ERROR_FAILURE, __func__); + }); + + return p; +} + +void UntrustedModulesProcessor::CompleteProcessing( + UntrustedModulesProcessor::ModulesMapResultWithLoads&& aModulesAndLoads) { + MOZ_ASSERT(!XRE_IsParentProcess()); + AssertRunningOnLazyIdleThread(); + + if (!mAllowProcessing) { + return; + } + + if (aModulesAndLoads.mModMapResult.isNothing()) { + // No untrusted modules in this batch, nothing to save. + return; + } + + // This map only contains information about modules deemed to be untrusted, + // plus xul.dll. Any module referenced by load requests that is *not* in the + // map is deemed to be trusted. + ModulesMap& modules = aModulesAndLoads.mModMapResult.ref().mModules; + const uint32_t& trustTestFailures = + aModulesAndLoads.mModMapResult.ref().mTrustTestFailures; + LoadsVec& loads = aModulesAndLoads.mLoads; + + if (modules.IsEmpty() && !trustTestFailures) { + // No data, nothing to save. + return; + } + + if (!mAllowProcessing) { + return; + } + + auto cleanup = MakeScopeExit([&]() { mIsFirstBatchProcessed = true; }); + + Telemetry::BatchProcessedStackGenerator stackProcessor; + DependentModules dependentModules; + + Maybe<double> maybeXulLoadDuration; + Vector<Telemetry::ProcessedStack> processedStacks; + Vector<ProcessedModuleLoadEvent> processedEvents; + uint32_t sanitizationFailures = 0; + + if (!modules.IsEmpty()) { + for (auto&& item : loads) { + if (!mAllowProcessing) { + return; + } + + RefPtr<ModuleRecord> module(GetModuleRecord(modules, item)); + if (!module) { + // If module is null then |item| is trusted + continue; + } + + if (!mAllowProcessing) { + return; + } + + bool isDependent = + mIsFirstBatchProcessed + ? false + : dependentModules.Lookup(module->mSanitizedDllName); + + if (!mAllowProcessing) { + return; + } + + glue::EnhancedModuleLoadInfo::BacktraceType backtrace = + std::move(item.mNtLoadInfo.mBacktrace); + ProcessedModuleLoadEvent event(std::move(item), std::move(module), + isDependent); + + if (!mAllowProcessing) { + return; + } + + if (!event) { + // We don't have a sanitized DLL path, so we cannot include this event + // for privacy reasons. + ++sanitizationFailures; + continue; + } + + if (!mAllowProcessing) { + return; + } + + if (event.IsXULLoad()) { + maybeXulLoadDuration = event.mLoadDurationMS; + // We saved the XUL load duration, but it is still trusted, so we + // continue. + continue; + } + + if (!mAllowProcessing) { + return; + } + + Telemetry::ProcessedStack processedStack = + stackProcessor.GetStackAndModules(backtrace); + + Unused << processedStacks.emplaceBack(std::move(processedStack)); + Unused << processedEvents.emplaceBack(std::move(event)); + } + } + + if (processedStacks.empty() && processedEvents.empty() && + !sanitizationFailures && !trustTestFailures) { + // Nothing to save + return; + } + + if (!mAllowProcessing) { + return; + } + + mProcessedModuleLoads.AddNewLoads(modules, std::move(processedEvents), + std::move(processedStacks)); + if (maybeXulLoadDuration) { + MOZ_ASSERT(!mProcessedModuleLoads.mXULLoadDurationMS); + mProcessedModuleLoads.mXULLoadDurationMS = maybeXulLoadDuration; + } + + mProcessedModuleLoads.mSanitizationFailures += sanitizationFailures; + mProcessedModuleLoads.mTrustTestFailures += trustTestFailures; +} + +// The thread priority of this job should match the priority that the child +// process is running with, as specified by |aRunAtNormalPriority|. +RefPtr<ModulesTrustPromise> UntrustedModulesProcessor::GetModulesTrustInternal( + ModulePaths&& aModPaths, bool aRunAtNormalPriority) { + MOZ_ASSERT(XRE_IsParentProcess()); + AssertRunningOnLazyIdleThread(); + + if (!mAllowProcessing) { + return ModulesTrustPromise::CreateAndReject( + NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); + } + + if (aRunAtNormalPriority) { + return GetModulesTrustInternal(std::move(aModPaths)); + } + + BackgroundPriorityRegion bgRgn; + return GetModulesTrustInternal(std::move(aModPaths)); +} + +// For each module in |aModPaths|, evaluate its trustworthiness and only send +// ModuleRecords for untrusted modules back to the child process. We also save +// XUL's ModuleRecord so that the child process may report XUL's load time. +RefPtr<ModulesTrustPromise> UntrustedModulesProcessor::GetModulesTrustInternal( + ModulePaths&& aModPaths) { + MOZ_ASSERT(XRE_IsParentProcess()); + AssertRunningOnLazyIdleThread(); + + ModulesMapResult result; + + ModulesMap& modMap = result.mModules; + uint32_t& trustTestFailures = result.mTrustTestFailures; + + ModuleEvaluator modEval; + MOZ_ASSERT(!!modEval); + if (!modEval) { + return ModulesTrustPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + // This map holds all modules regardless of trust status; we use this to + // filter any duplicates from the input. + ModulesMap modules; + + for (auto& resolvedNtPath : + aModPaths.mModuleNtPaths.as<ModulePaths::VecType>()) { + if (!mAllowProcessing) { + return ModulesTrustPromise::CreateAndReject( + NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); + } + + MOZ_ASSERT(!resolvedNtPath.IsEmpty()); + if (resolvedNtPath.IsEmpty()) { + continue; + } + + RefPtr<ModuleRecord> module( + GetOrAddModuleRecord(modules, modEval, resolvedNtPath)); + if (!module) { + // We failed to obtain trust information. + ++trustTestFailures; + continue; + } + + if (!mAllowProcessing) { + return ModulesTrustPromise::CreateAndReject( + NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); + } + + if (module->IsTrusted() && !module->IsXUL()) { + // If the module is trusted we exclude it from results, unless it's XUL. + // (We save XUL so that the child process may report XUL's load time) + continue; + } + + if (!mAllowProcessing) { + return ModulesTrustPromise::CreateAndReject( + NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); + } + + modMap.Put(resolvedNtPath, std::move(module)); + } + + return ModulesTrustPromise::CreateAndResolve(std::move(result), __func__); +} + +} // namespace mozilla |