зеркало из https://github.com/mozilla/gecko-dev.git
1042 строки
31 KiB
C++
1042 строки
31 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 "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/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
|