forked from mirrors/gecko-dev
All present uses of the call-site arguments to MozPromise's methods supply static strings. However, this is nowhere enforced. Do so. Additionally, since this is the third or fourth time the present author alone has personally implemented such an enforcement mechanism, create a helper class to simplify doing so. No functional changes. Differential Revision: https://phabricator.services.mozilla.com/D207462
1075 lines
34 KiB
C++
1075 lines
34 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 "GMPPlatform.h"
|
|
#include "GMPServiceParent.h"
|
|
#include "mozilla/CmdLineAndEnvUtils.h"
|
|
#include "mozilla/DebugOnly.h"
|
|
#include "mozilla/dom/ContentChild.h"
|
|
#include "mozilla/dom/ContentParent.h"
|
|
#include "mozilla/Likely.h"
|
|
#include "mozilla/net/SocketProcessChild.h"
|
|
#include "mozilla/net/SocketProcessParent.h"
|
|
#include "mozilla/ipc/UtilityProcessParent.h"
|
|
#include "mozilla/ipc/UtilityProcessChild.h"
|
|
#include "mozilla/RDDChild.h"
|
|
#include "mozilla/RDDParent.h"
|
|
#include "mozilla/RDDProcessManager.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
|
|
|
|
namespace mozilla {
|
|
|
|
class MOZ_RAII BackgroundPriorityRegion final {
|
|
public:
|
|
BackgroundPriorityRegion()
|
|
: mIsBackground(
|
|
::SetThreadPriority(::GetCurrentThread(), THREAD_PRIORITY_IDLE)) {}
|
|
|
|
~BackgroundPriorityRegion() {
|
|
if (!mIsBackground) {
|
|
return;
|
|
}
|
|
|
|
Clear(::GetCurrentThread());
|
|
}
|
|
|
|
static void Clear(const nsAutoHandle& aThread) {
|
|
if (!aThread) {
|
|
return;
|
|
}
|
|
|
|
Clear(aThread.get());
|
|
}
|
|
|
|
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;
|
|
};
|
|
|
|
/* static */
|
|
bool UntrustedModulesProcessor::IsSupportedProcessType() {
|
|
switch (XRE_GetProcessType()) {
|
|
case GeckoProcessType_Default:
|
|
case GeckoProcessType_Content:
|
|
case GeckoProcessType_Socket:
|
|
return Telemetry::CanRecordReleaseData();
|
|
case GeckoProcessType_RDD:
|
|
case GeckoProcessType_Utility:
|
|
case GeckoProcessType_GMPlugin:
|
|
// For GMPlugin, RDD and Utility process, we check the telemetry settings
|
|
// in RDDChild::Init() / UtilityProcessChild::Init() / GMPChild::Init()
|
|
// running in the browser process because CanRecordReleaseData() always
|
|
// returns false here.
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* static */
|
|
RefPtr<UntrustedModulesProcessor> UntrustedModulesProcessor::Create(
|
|
bool aIsReadyForBackgroundProcessing) {
|
|
if (!IsSupportedProcessType()) {
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<UntrustedModulesProcessor> result(
|
|
new UntrustedModulesProcessor(aIsReadyForBackgroundProcessing));
|
|
return result.forget();
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(UntrustedModulesProcessor, nsIObserver, nsIThreadPoolListener)
|
|
|
|
static const uint32_t kThreadTimeoutMS = 120000; // 2 minutes
|
|
|
|
UntrustedModulesProcessor::UntrustedModulesProcessor(
|
|
bool aIsReadyForBackgroundProcessing)
|
|
: mThread(new LazyIdleThread(kThreadTimeoutMS, "Untrusted Modules",
|
|
LazyIdleThread::ManualShutdown)),
|
|
mThreadHandleMutex(
|
|
"mozilla::UntrustedModulesProcessor::mThreadHandleMutex"),
|
|
mUnprocessedMutex(
|
|
"mozilla::UntrustedModulesProcessor::mUnprocessedMutex"),
|
|
mModuleCacheMutex(
|
|
"mozilla::UntrustedModulesProcessor::mModuleCacheMutex"),
|
|
mStatus(aIsReadyForBackgroundProcessing ? Status::Allowed
|
|
: Status::StartingUp) {
|
|
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);
|
|
obsServ->AddObserver(this, "unblock-untrusted-modules-thread", false);
|
|
if (XRE_IsContentProcess()) {
|
|
obsServ->AddObserver(this, "content-child-will-shutdown", false);
|
|
}
|
|
mThread->SetListener(this);
|
|
}
|
|
|
|
bool UntrustedModulesProcessor::IsReadyForBackgroundProcessing() const {
|
|
return mStatus == Status::Allowed;
|
|
}
|
|
|
|
void UntrustedModulesProcessor::Disable() {
|
|
// Ensure that mThread cannot run at low priority anymore
|
|
{
|
|
MutexAutoLock lock(mThreadHandleMutex);
|
|
BackgroundPriorityRegion::Clear(mThreadHandle);
|
|
}
|
|
|
|
// No more background processing allowed beyond this point
|
|
if (mStatus.exchange(Status::ShuttingDown) != Status::Allowed) {
|
|
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;
|
|
}
|
|
|
|
if (!strcmp(aTopic, "unblock-untrusted-modules-thread")) {
|
|
nsCOMPtr<nsIObserverService> obs(services::GetObserverService());
|
|
obs->RemoveObserver(this, "unblock-untrusted-modules-thread");
|
|
|
|
mStatus.compareExchange(Status::StartingUp, Status::Allowed);
|
|
|
|
if (!IsReadyForBackgroundProcessing()) {
|
|
// If we're shutting down, stop here.
|
|
return NS_OK;
|
|
}
|
|
|
|
if (XRE_IsParentProcess()) {
|
|
// Propagate notification to child processes
|
|
nsTArray<dom::ContentParent*> contentProcesses;
|
|
dom::ContentParent::GetAll(contentProcesses);
|
|
for (auto* proc : contentProcesses) {
|
|
Unused << proc->SendUnblockUntrustedModulesThread();
|
|
}
|
|
if (auto* proc = net::SocketProcessParent::GetSingleton()) {
|
|
Unused << proc->SendUnblockUntrustedModulesThread();
|
|
}
|
|
if (auto* rddMgr = RDDProcessManager::Get()) {
|
|
if (auto* proc = rddMgr->GetRDDChild()) {
|
|
Unused << proc->SendUnblockUntrustedModulesThread();
|
|
}
|
|
}
|
|
if (RefPtr<gmp::GeckoMediaPluginServiceParent> gmps =
|
|
gmp::GeckoMediaPluginServiceParent::GetSingleton()) {
|
|
gmps->SendUnblockUntrustedModulesThread();
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_ASSERT_UNREACHABLE("Not reachable");
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP UntrustedModulesProcessor::OnThreadCreated() {
|
|
// Whenever a backing lazy thread is created, record a thread handle to it.
|
|
HANDLE threadHandle;
|
|
if (!::DuplicateHandle(
|
|
::GetCurrentProcess(), ::GetCurrentThread(), ::GetCurrentProcess(),
|
|
&threadHandle,
|
|
THREAD_QUERY_LIMITED_INFORMATION | THREAD_SET_LIMITED_INFORMATION,
|
|
FALSE, 0)) {
|
|
MOZ_ASSERT_UNREACHABLE("DuplicateHandle failed on GetCurrentThread()?");
|
|
threadHandle = nullptr;
|
|
}
|
|
MutexAutoLock lock(mThreadHandleMutex);
|
|
mThreadHandle.own(threadHandle);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP UntrustedModulesProcessor::OnThreadShuttingDown() {
|
|
// When a lazy thread shuts down, clean up the thread handle reference unless
|
|
// it's already been replaced.
|
|
MutexAutoLock lock(mThreadHandleMutex);
|
|
if (mThreadHandle && ::GetCurrentThreadId() == ::GetThreadId(mThreadHandle)) {
|
|
mThreadHandle.reset();
|
|
}
|
|
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");
|
|
obsServ->RemoveObserver(this, "unblock-untrusted-modules-thread");
|
|
if (XRE_IsContentProcess()) {
|
|
obsServ->RemoveObserver(this, "content-child-will-shutdown");
|
|
}
|
|
mThread->SetListener(nullptr);
|
|
}
|
|
|
|
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 (!IsReadyForBackgroundProcessing()) {
|
|
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 (!IsReadyForBackgroundProcessing()) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIRunnable> runnable(NewRunnableMethod(
|
|
"UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue", this,
|
|
&UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue));
|
|
|
|
mThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
|
|
}
|
|
|
|
void UntrustedModulesProcessor::Enqueue(
|
|
glue::EnhancedModuleLoadInfo&& aModLoadInfo) {
|
|
if (mStatus == Status::ShuttingDown) {
|
|
return;
|
|
}
|
|
|
|
{
|
|
MutexAutoLock lock(mThreadHandleMutex);
|
|
DWORD bgThreadId = ::GetThreadId(mThreadHandle);
|
|
if (aModLoadInfo.mNtLoadInfo.mThreadId == bgThreadId) {
|
|
// Exclude loads that were caused by our own background thread
|
|
return;
|
|
}
|
|
}
|
|
|
|
MutexAutoLock lock(mUnprocessedMutex);
|
|
|
|
mUnprocessedModuleLoads.insertBack(
|
|
new UnprocessedModuleLoadInfoContainer(std::move(aModLoadInfo)));
|
|
|
|
ScheduleNonEmptyQueueProcessing(lock);
|
|
}
|
|
|
|
void UntrustedModulesProcessor::Enqueue(ModuleLoadInfoVec&& aEvents) {
|
|
if (mStatus == Status::ShuttingDown) {
|
|
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) {
|
|
mUnprocessedModuleLoads.insertBack(
|
|
new UnprocessedModuleLoadInfoContainer(std::move(event)));
|
|
}
|
|
|
|
ScheduleNonEmptyQueueProcessing(lock);
|
|
}
|
|
|
|
void UntrustedModulesProcessor::AssertRunningOnLazyIdleThread() {
|
|
#if defined(DEBUG)
|
|
MOZ_ASSERT(mThread->IsOnCurrentThread());
|
|
#endif // defined(DEBUG)
|
|
}
|
|
|
|
RefPtr<UntrustedModulesPromise> UntrustedModulesProcessor::GetProcessedData() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// Clear any background priority in case background processing is running.
|
|
{
|
|
MutexAutoLock lock(mThreadHandleMutex);
|
|
BackgroundPriorityRegion::Clear(mThreadHandle);
|
|
}
|
|
|
|
RefPtr<UntrustedModulesProcessor> self(this);
|
|
return InvokeAsync(mThread, __func__, [self = std::move(self)]() {
|
|
return self->GetProcessedDataInternal();
|
|
});
|
|
}
|
|
|
|
RefPtr<ModulesTrustPromise> UntrustedModulesProcessor::GetModulesTrust(
|
|
ModulePaths&& aModPaths, bool aRunAtNormalPriority) {
|
|
MOZ_ASSERT(XRE_IsParentProcess() && NS_IsMainThread());
|
|
|
|
if (!IsReadyForBackgroundProcessing()) {
|
|
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.
|
|
{
|
|
MutexAutoLock lock(mThreadHandleMutex);
|
|
BackgroundPriorityRegion::Clear(mThreadHandle);
|
|
}
|
|
|
|
return InvokeAsync(mThread, __func__, std::move(run));
|
|
}
|
|
|
|
RefPtr<ModulesTrustPromise::Private> p(
|
|
new ModulesTrustPromise::Private(__func__));
|
|
nsCOMPtr<nsISerialEventTarget> evtTarget(mThread);
|
|
StaticString 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(
|
|
StaticString 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);
|
|
|
|
StaticString source = __func__;
|
|
auto completionRoutine = [evtTarget = std::move(evtTarget), p,
|
|
self = std::move(self), source,
|
|
whenProcessed = std::move(whenProcessed)]() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
if (!self->IsReadyForBackgroundProcessing()) {
|
|
// 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 (!IsReadyForBackgroundProcessing()) {
|
|
return;
|
|
}
|
|
|
|
BackgroundPriorityRegion bgRgn;
|
|
|
|
if (!IsReadyForBackgroundProcessing()) {
|
|
return;
|
|
}
|
|
|
|
ProcessModuleLoadQueue();
|
|
}
|
|
|
|
RefPtr<ModuleRecord> UntrustedModulesProcessor::GetOrAddModuleRecord(
|
|
const ModuleEvaluator& aModEval, const nsAString& aResolvedNtPath) {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
|
|
MutexAutoLock lock(mModuleCacheMutex);
|
|
return mGlobalModuleCache.WithEntryHandle(
|
|
aResolvedNtPath, [&](auto&& addPtr) -> RefPtr<ModuleRecord> {
|
|
if (addPtr) {
|
|
return addPtr.Data();
|
|
}
|
|
|
|
RefPtr<ModuleRecord> newMod(new ModuleRecord(aResolvedNtPath));
|
|
if (!(*newMod)) {
|
|
return nullptr;
|
|
}
|
|
|
|
Maybe<ModuleTrustFlags> maybeTrust = aModEval.GetTrust(*newMod);
|
|
if (maybeTrust.isNothing()) {
|
|
return nullptr;
|
|
}
|
|
|
|
newMod->mTrustFlags = maybeTrust.value();
|
|
|
|
return addPtr.Insert(std::move(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);
|
|
|
|
constexpr StaticString const source = __func__;
|
|
auto completionRoutine = [evtTarget = std::move(evtTarget),
|
|
self = std::move(self), source,
|
|
whenProcessed = std::move(whenProcessed)]() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
if (!self->IsReadyForBackgroundProcessing()) {
|
|
// 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->IsReadyForBackgroundProcessing()) {
|
|
// 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));
|
|
}
|
|
|
|
UnprocessedModuleLoads UntrustedModulesProcessor::ExtractLoadingEventsToProcess(
|
|
size_t aMaxLength) {
|
|
UnprocessedModuleLoads loadsToProcess;
|
|
|
|
MutexAutoLock lock(mUnprocessedMutex);
|
|
CancelScheduledProcessing(lock);
|
|
|
|
loadsToProcess.splice(0, mUnprocessedModuleLoads, 0, aMaxLength);
|
|
return loadsToProcess;
|
|
}
|
|
|
|
// This function contains multiple IsReadyForBackgroundProcessing() 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;
|
|
}
|
|
|
|
UnprocessedModuleLoads loadsToProcess =
|
|
ExtractLoadingEventsToProcess(UntrustedModulesData::kMaxEvents);
|
|
if (!IsReadyForBackgroundProcessing() || loadsToProcess.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
ModuleEvaluator modEval;
|
|
MOZ_ASSERT(!!modEval);
|
|
if (!modEval) {
|
|
return;
|
|
}
|
|
|
|
Telemetry::BatchProcessedStackGenerator stackProcessor;
|
|
Maybe<double> maybeXulLoadDuration;
|
|
Vector<Telemetry::ProcessedStack> processedStacks;
|
|
UntrustedModuleLoadingEvents processedEvents;
|
|
uint32_t sanitizationFailures = 0;
|
|
uint32_t trustTestFailures = 0;
|
|
|
|
for (UnprocessedModuleLoadInfoContainer* container : loadsToProcess) {
|
|
glue::EnhancedModuleLoadInfo& entry = container->mInfo;
|
|
|
|
if (!IsReadyForBackgroundProcessing()) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<ModuleRecord> module(GetOrAddModuleRecord(
|
|
modEval, entry.mNtLoadInfo.mSectionName.AsString()));
|
|
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 (!IsReadyForBackgroundProcessing()) {
|
|
return;
|
|
}
|
|
|
|
glue::EnhancedModuleLoadInfo::BacktraceType backtrace =
|
|
std::move(entry.mNtLoadInfo.mBacktrace);
|
|
ProcessedModuleLoadEvent event(std::move(entry), std::move(module));
|
|
|
|
if (!event) {
|
|
// We don't have a sanitized DLL path, so we cannot include this event
|
|
// for privacy reasons.
|
|
++sanitizationFailures;
|
|
continue;
|
|
}
|
|
|
|
if (!IsReadyForBackgroundProcessing()) {
|
|
return;
|
|
}
|
|
|
|
if (event.IsTrusted()) {
|
|
if (event.IsXULLoad()) {
|
|
maybeXulLoadDuration = event.mLoadDurationMS;
|
|
}
|
|
|
|
// Trusted modules are not included in the ping
|
|
continue;
|
|
}
|
|
|
|
mProcessedModuleLoads.mModules.LookupOrInsert(
|
|
event.mModule->mResolvedNtName, event.mModule);
|
|
|
|
if (!IsReadyForBackgroundProcessing()) {
|
|
return;
|
|
}
|
|
|
|
Telemetry::ProcessedStack processedStack =
|
|
stackProcessor.GetStackAndModules(backtrace);
|
|
|
|
if (!IsReadyForBackgroundProcessing()) {
|
|
return;
|
|
}
|
|
|
|
Unused << processedStacks.emplaceBack(std::move(processedStack));
|
|
processedEvents.insertBack(
|
|
new ProcessedModuleLoadEventContainer(std::move(event)));
|
|
}
|
|
|
|
if (processedStacks.empty() && processedEvents.isEmpty() &&
|
|
!sanitizationFailures && !trustTestFailures) {
|
|
// Nothing to save
|
|
return;
|
|
}
|
|
|
|
if (!IsReadyForBackgroundProcessing()) {
|
|
return;
|
|
}
|
|
|
|
// Modules have been added to mProcessedModuleLoads.mModules
|
|
// in the loop above. Passing an empty ModulesMap to AddNewLoads.
|
|
mProcessedModuleLoads.AddNewLoads(ModulesMap{}, 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 ::mozilla::SendGetModulesTrust(dom::ContentChild::GetSingleton(),
|
|
std::move(aModules), runNormal);
|
|
}
|
|
case GeckoProcessType_RDD: {
|
|
return ::mozilla::SendGetModulesTrust(RDDParent::GetSingleton(),
|
|
std::move(aModules), runNormal);
|
|
}
|
|
case GeckoProcessType_Socket: {
|
|
return ::mozilla::SendGetModulesTrust(
|
|
net::SocketProcessChild::GetSingleton(), std::move(aModules),
|
|
runNormal);
|
|
}
|
|
case GeckoProcessType_Utility: {
|
|
return ::mozilla::SendGetModulesTrust(
|
|
ipc::UtilityProcessChild::GetSingleton().get(), std::move(aModules),
|
|
runNormal);
|
|
}
|
|
case GeckoProcessType_GMPlugin: {
|
|
return ::mozilla::gmp::SendGetModulesTrust(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());
|
|
|
|
UnprocessedModuleLoads loadsToProcess =
|
|
ExtractLoadingEventsToProcess(UntrustedModulesData::kMaxEvents);
|
|
if (loadsToProcess.isEmpty()) {
|
|
// Nothing to process
|
|
return GetModulesTrustPromise::CreateAndResolve(Nothing(), __func__);
|
|
}
|
|
|
|
if (!IsReadyForBackgroundProcessing()) {
|
|
return GetModulesTrustPromise::CreateAndReject(
|
|
NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
|
|
}
|
|
|
|
nsTHashtable<nsStringCaseInsensitiveHashKey> moduleNtPathSet;
|
|
|
|
// Build a set of modules to be processed by the parent
|
|
for (UnprocessedModuleLoadInfoContainer* container : loadsToProcess) {
|
|
glue::EnhancedModuleLoadInfo& entry = container->mInfo;
|
|
|
|
if (!IsReadyForBackgroundProcessing()) {
|
|
return GetModulesTrustPromise::CreateAndReject(
|
|
NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
|
|
}
|
|
|
|
moduleNtPathSet.PutEntry(entry.mNtLoadInfo.mSectionName.AsString());
|
|
}
|
|
|
|
if (!IsReadyForBackgroundProcessing()) {
|
|
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 (!IsReadyForBackgroundProcessing()) {
|
|
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 (!IsReadyForBackgroundProcessing()) {
|
|
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 (!IsReadyForBackgroundProcessing()) {
|
|
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;
|
|
UnprocessedModuleLoads& loads = aModulesAndLoads.mLoads;
|
|
|
|
if (modules.IsEmpty() && !trustTestFailures) {
|
|
// No data, nothing to save.
|
|
return;
|
|
}
|
|
|
|
if (!IsReadyForBackgroundProcessing()) {
|
|
return;
|
|
}
|
|
|
|
Telemetry::BatchProcessedStackGenerator stackProcessor;
|
|
|
|
Maybe<double> maybeXulLoadDuration;
|
|
Vector<Telemetry::ProcessedStack> processedStacks;
|
|
UntrustedModuleLoadingEvents processedEvents;
|
|
uint32_t sanitizationFailures = 0;
|
|
|
|
if (!modules.IsEmpty()) {
|
|
for (UnprocessedModuleLoadInfoContainer* container : loads) {
|
|
glue::EnhancedModuleLoadInfo& item = container->mInfo;
|
|
if (!IsReadyForBackgroundProcessing()) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<ModuleRecord> module(GetModuleRecord(modules, item));
|
|
if (!module) {
|
|
// If module is null then |item| is trusted
|
|
continue;
|
|
}
|
|
|
|
if (!IsReadyForBackgroundProcessing()) {
|
|
return;
|
|
}
|
|
|
|
glue::EnhancedModuleLoadInfo::BacktraceType backtrace =
|
|
std::move(item.mNtLoadInfo.mBacktrace);
|
|
ProcessedModuleLoadEvent event(std::move(item), std::move(module));
|
|
|
|
if (!IsReadyForBackgroundProcessing()) {
|
|
return;
|
|
}
|
|
|
|
if (!event) {
|
|
// We don't have a sanitized DLL path, so we cannot include this event
|
|
// for privacy reasons.
|
|
++sanitizationFailures;
|
|
continue;
|
|
}
|
|
|
|
if (!IsReadyForBackgroundProcessing()) {
|
|
return;
|
|
}
|
|
|
|
if (event.IsXULLoad()) {
|
|
maybeXulLoadDuration = event.mLoadDurationMS;
|
|
// We saved the XUL load duration, but it is still trusted, so we
|
|
// continue.
|
|
continue;
|
|
}
|
|
|
|
if (!IsReadyForBackgroundProcessing()) {
|
|
return;
|
|
}
|
|
|
|
Telemetry::ProcessedStack processedStack =
|
|
stackProcessor.GetStackAndModules(backtrace);
|
|
|
|
Unused << processedStacks.emplaceBack(std::move(processedStack));
|
|
processedEvents.insertBack(
|
|
new ProcessedModuleLoadEventContainer(std::move(event)));
|
|
}
|
|
}
|
|
|
|
if (processedStacks.empty() && processedEvents.isEmpty() &&
|
|
!sanitizationFailures && !trustTestFailures) {
|
|
// Nothing to save
|
|
return;
|
|
}
|
|
|
|
if (!IsReadyForBackgroundProcessing()) {
|
|
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 (!IsReadyForBackgroundProcessing()) {
|
|
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__);
|
|
}
|
|
|
|
for (auto& resolvedNtPath :
|
|
aModPaths.mModuleNtPaths.as<ModulePaths::VecType>()) {
|
|
if (!IsReadyForBackgroundProcessing()) {
|
|
return ModulesTrustPromise::CreateAndReject(
|
|
NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
|
|
}
|
|
|
|
MOZ_ASSERT(!resolvedNtPath.IsEmpty());
|
|
if (resolvedNtPath.IsEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
RefPtr<ModuleRecord> module(GetOrAddModuleRecord(modEval, resolvedNtPath));
|
|
if (!module) {
|
|
// We failed to obtain trust information.
|
|
++trustTestFailures;
|
|
continue;
|
|
}
|
|
|
|
if (!IsReadyForBackgroundProcessing()) {
|
|
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 (!IsReadyForBackgroundProcessing()) {
|
|
return ModulesTrustPromise::CreateAndReject(
|
|
NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
|
|
}
|
|
|
|
modMap.InsertOrUpdate(resolvedNtPath, std::move(module));
|
|
}
|
|
|
|
return ModulesTrustPromise::CreateAndResolve(std::move(result), __func__);
|
|
}
|
|
|
|
} // namespace mozilla
|