Bug 1522830: Part 8 - Update UntrustedModulesProcessor to support processing child processes; r=mhowell

This patch contains the core changes to make this all work across e10s:

* We clarify the naming of path variables to be more specific as to whether they are NT paths or DOS paths;
* We add IPC `ParamTraits` that are necessary for `UntrustedModulesData` types;
* We implement `ProcessModuleLoadQueue` for child processes. Because of sandboxing, we need to split this sequence into multiple async operations:
  ** Initial queue processing;
  ** Sending the list of modules to the parent process to determine trustworthiness (via `GetModulesTrust`);
  ** Receiving the results from the parent process and producing a final result (via `CompleteProcessing`).
* We implement the `GetModulesTrust` function for the parent process, which evaluates the trust of child process modules;
* We change all hash tables to be keyed using NT paths. Because resolving DOS paths may not be permitted in sandboxed processes,
  we need to standardize on NT paths as the "universal path" across processes.
* We add `WinDllServices::StartUntrustedModulesProcessor` to separate untrusted modules startup from `WinDllServices` construction:
  ** While we now start `WinDllServices` across all child process types, only specific process types will support untrusted modules.
  ** Furthermore, untrusted modules must be started at a very specific point that is dependent on the type of child process.
  ** We add those calls to `StartUntrustedModulesProcessor` in subsequent patches.

Differential Revision: https://phabricator.services.mozilla.com/D53680

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Aaron Klotz 2019-12-06 22:06:26 +00:00
parent c04875c1e5
commit 23e61114cf
12 changed files with 1227 additions and 97 deletions

View File

@ -1338,6 +1338,7 @@ int XRE_XPCShellMain(int argc, char** argv, char** envp,
// Ensure that DLL Services are running
RefPtr<DllServices> dllSvc(DllServices::Get());
dllSvc->StartUntrustedModulesProcessor();
auto dllServicesDisable =
MakeScopeExit([&dllSvc]() { dllSvc->DisableFull(); });

View File

@ -90,7 +90,7 @@ class DllServicesBase : public Authenticode {
return mAuthenticode->GetBinaryOrgName(aFilePath, aFlags);
}
void DisableFull() { DllBlocklist_SetFullDllServices(nullptr); }
virtual void DisableFull() { DllBlocklist_SetFullDllServices(nullptr); }
DllServicesBase(const DllServicesBase&) = delete;
DllServicesBase(DllServicesBase&&) = delete;

View File

@ -91,6 +91,8 @@ bool ModuleEvaluator::ResolveKnownFolder(REFKNOWNFOLDERID aFolderId,
ModuleEvaluator::ModuleEvaluator()
: mKeyboardLayoutDlls(GetKeyboardLayoutDlls()) {
MOZ_ASSERT(XRE_IsParentProcess());
#if defined(_M_IX86)
// We want to resolve to SYSWOW64 when applicable
REFKNOWNFOLDERID systemFolderId = FOLDERID_SystemX86;
@ -155,6 +157,8 @@ ModuleEvaluator::operator bool() const {
Maybe<ModuleTrustFlags> ModuleEvaluator::GetTrust(
const ModuleRecord& aModuleRecord) const {
MOZ_ASSERT(XRE_IsParentProcess());
// We start by checking authenticode signatures, as the presence of any
// signature will produce an immediate pass/fail.
if (aModuleRecord.mVendorInfo.isSome() &&
@ -175,7 +179,11 @@ Maybe<ModuleTrustFlags> ModuleEvaluator::GetTrust(
}
}
const nsCOMPtr<nsIFile>& dllFile = aModuleRecord.mResolvedDllName;
const nsCOMPtr<nsIFile>& dllFile = aModuleRecord.mResolvedDosName;
MOZ_ASSERT(!!dllFile);
if (!dllFile) {
return Nothing();
}
nsAutoString dllLeafLower;
if (NS_FAILED(dllFile->GetLeafName(dllLeafLower))) {

View File

@ -17,6 +17,7 @@
#include "mozilla/WinDllServices.h"
#include "ModuleEvaluator.h"
#include "nsCOMPtr.h"
#include "nsDebug.h"
#include "nsXULAppAPI.h"
#include "WinUtils.h"
@ -71,23 +72,39 @@ static Maybe<double> QPCLoadDurationToMilliseconds(
namespace mozilla {
ModuleRecord::ModuleRecord(const nsAString& aResolvedPath)
: mTrustFlags(ModuleTrustFlags::None) {
if (aResolvedPath.IsEmpty()) {
ModuleRecord::ModuleRecord() : mTrustFlags(ModuleTrustFlags::None) {}
ModuleRecord::ModuleRecord(const nsAString& aResolvedNtPath)
: mResolvedNtName(aResolvedNtPath), mTrustFlags(ModuleTrustFlags::None) {
if (aResolvedNtPath.IsEmpty()) {
return;
}
MOZ_ASSERT(XRE_IsParentProcess());
nsAutoString resolvedDosPath;
if (!NtPathToDosPath(aResolvedNtPath, resolvedDosPath)) {
#if defined(DEBUG)
nsAutoCString msg;
msg.AppendLiteral("NtPathToDosPath failed for path \"");
msg.Append(NS_ConvertUTF16toUTF8(aResolvedNtPath));
msg.AppendLiteral("\"");
NS_WARNING(msg.get());
#endif // defined(DEBUG)
return;
}
nsresult rv =
NS_NewLocalFile(aResolvedPath, false, getter_AddRefs(mResolvedDllName));
if (NS_FAILED(rv) || !mResolvedDllName) {
NS_NewLocalFile(resolvedDosPath, false, getter_AddRefs(mResolvedDosName));
if (NS_FAILED(rv) || !mResolvedDosName) {
return;
}
GetVersionAndVendorInfo(aResolvedPath);
GetVersionAndVendorInfo(resolvedDosPath);
// Now sanitize the resolved DLL name. If we cannot sanitize this then this
// record must not be considered valid.
nsAutoString strSanitizedPath(aResolvedPath);
nsAutoString strSanitizedPath(resolvedDosPath);
if (!widget::WinUtils::PreparePathForTelemetry(strSanitizedPath)) {
return;
}
@ -124,12 +141,12 @@ void ModuleRecord::GetVersionAndVendorInfo(const nsAString& aPath) {
}
bool ModuleRecord::IsXUL() const {
if (!mResolvedDllName) {
if (!mResolvedDosName) {
return false;
}
nsAutoString leafName;
nsresult rv = mResolvedDllName->GetLeafName(leafName);
nsresult rv = mResolvedDosName->GetLeafName(leafName);
if (NS_FAILED(rv)) {
return false;
}
@ -142,7 +159,7 @@ int32_t ModuleRecord::GetScoreThreshold() const {
// Check whether we are running as an xpcshell test.
if (MOZ_UNLIKELY(mozilla::EnvHasValue("XPCSHELL_TEST_PROFILE_DIR"))) {
nsAutoString dllLeaf;
if (NS_SUCCEEDED(mResolvedDllName->GetLeafName(dllLeaf))) {
if (NS_SUCCEEDED(mResolvedDosName->GetLeafName(dllLeaf))) {
// During xpcshell tests, this DLL is hard-coded to pass through all
// criteria checks and still result in "untrusted" status, so it shows up
// in the untrusted modules ping for the test to examine.
@ -178,6 +195,9 @@ bool ModuleRecord::IsTrusted() const {
return score >= GetScoreThreshold();
}
ProcessedModuleLoadEvent::ProcessedModuleLoadEvent()
: mProcessUptimeMS(0ULL), mThreadId(0UL), mBaseAddress(0U) {}
ProcessedModuleLoadEvent::ProcessedModuleLoadEvent(
glue::EnhancedModuleLoadInfo&& aModLoadInfo,
RefPtr<ModuleRecord>&& aModuleRecord)
@ -262,7 +282,7 @@ uint64_t ProcessedModuleLoadEvent::QPCTimeStampToProcessUptimeMilliseconds(
}
bool ProcessedModuleLoadEvent::IsXULLoad() const {
if (!mModule || !mLoadDurationMS || !IsTrusted()) {
if (!mModule || !mLoadDurationMS) {
return false;
}

View File

@ -7,18 +7,25 @@
#ifndef mozilla_UntrustedModulesData_h
#define mozilla_UntrustedModulesData_h
#include "mozilla/CombinedStacks.h"
#include "mozilla/Maybe.h"
#include "mozilla/RefPtr.h"
#include "mozilla/TypedEnumBits.h"
#include "mozilla/Vector.h"
#include "mozilla/WinHeaderOnlyUtils.h"
#include "nsCOMPtr.h"
#include "nsIFile.h"
#include "nsISupportsImpl.h"
#include "nsRefPtrHashtable.h"
#include "nsString.h"
#include "nsXULAppAPI.h"
#if defined(XP_WIN)
# include "ipc/IPCMessageUtils.h"
# include "mozilla/CombinedStacks.h"
# include "mozilla/DebugOnly.h"
# include "mozilla/Maybe.h"
# include "mozilla/RefPtr.h"
# include "mozilla/TypedEnumBits.h"
# include "mozilla/Unused.h"
# include "mozilla/Variant.h"
# include "mozilla/Vector.h"
# include "mozilla/WinHeaderOnlyUtils.h"
# include "nsCOMPtr.h"
# include "nsHashKeys.h"
# include "nsIFile.h"
# include "nsISupportsImpl.h"
# include "nsRefPtrHashtable.h"
# include "nsString.h"
# include "nsXULAppAPI.h"
namespace mozilla {
namespace glue {
@ -43,26 +50,31 @@ MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ModuleTrustFlags);
class VendorInfo final {
public:
enum class Source : uint32_t {
None,
Signature,
VersionInfo,
};
VendorInfo() : mSource(Source::None) {}
VendorInfo(const Source aSource, const nsAString& aVendor)
: mSource(aSource), mVendor(aVendor) {
MOZ_ASSERT(!aVendor.IsEmpty());
MOZ_ASSERT(aSource != Source::None && !aVendor.IsEmpty());
}
Source mSource;
nsString mVendor;
};
class ModulesMap;
class ModuleRecord final {
public:
explicit ModuleRecord(const nsAString& aResolvedPath);
explicit ModuleRecord(const nsAString& aResolvedNtPath);
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ModuleRecord)
nsCOMPtr<nsIFile> mResolvedDllName;
nsString mResolvedNtName;
nsCOMPtr<nsIFile> mResolvedDosName;
nsString mSanitizedDllName;
Maybe<ModuleVersion> mVersion;
Maybe<VendorInfo> mVendorInfo;
@ -79,13 +91,41 @@ class ModuleRecord final {
ModuleRecord& operator=(ModuleRecord&&) = delete;
private:
ModuleRecord();
~ModuleRecord() = default;
void GetVersionAndVendorInfo(const nsAString& aPath);
int32_t GetScoreThreshold() const;
friend struct ::IPC::ParamTraits<ModulesMap>;
};
/**
* This type holds module path data using one of two internal representations.
* It may be created from either a nsTHashtable or a Vector, and may be
* serialized from either representation into a common format over the wire.
* Deserialization always uses the Vector representation.
*/
struct ModulePaths final {
using SetType = nsTHashtable<nsStringCaseInsensitiveHashKey>;
using VecType = Vector<nsString>;
Variant<SetType, VecType> mModuleNtPaths;
template <typename T>
explicit ModulePaths(T&& aPaths)
: mModuleNtPaths(AsVariant(std::forward<T>(aPaths))) {}
ModulePaths() : mModuleNtPaths(VecType()) {}
ModulePaths(const ModulePaths& aOther) = delete;
ModulePaths(ModulePaths&& aOther) = default;
ModulePaths& operator=(const ModulePaths&) = delete;
ModulePaths& operator=(ModulePaths&&) = default;
};
class ProcessedModuleLoadEvent final {
public:
ProcessedModuleLoadEvent();
ProcessedModuleLoadEvent(glue::EnhancedModuleLoadInfo&& aModLoadInfo,
RefPtr<ModuleRecord>&& aModuleRecord);
@ -116,10 +156,17 @@ class ProcessedModuleLoadEvent final {
const LARGE_INTEGER& aTimeStamp);
};
// Declaring ModulesMap this way makes it much easier to forward declare than
// if we had used |using| or |typedef|.
class ModulesMap final
: public nsRefPtrHashtable<nsStringCaseInsensitiveHashKey, ModuleRecord> {
public:
ModulesMap()
: nsRefPtrHashtable<nsStringCaseInsensitiveHashKey, ModuleRecord>() {}
};
class UntrustedModulesData final {
public:
using ModulesMap = nsRefPtrHashtable<nsStringHashKey, ModuleRecord>;
UntrustedModulesData()
: mProcessType(XRE_GetProcessType()),
mPid(::GetCurrentProcessId()),
@ -154,6 +201,403 @@ class UntrustedModulesData final {
uint32_t mTrustTestFailures;
};
class ModulesMapResult final {
public:
ModulesMapResult() : mTrustTestFailures(0) {}
ModulesMapResult(const ModulesMapResult& aOther) = delete;
ModulesMapResult(ModulesMapResult&& aOther) = default;
ModulesMapResult& operator=(const ModulesMapResult& aOther) = delete;
ModulesMapResult& operator=(ModulesMapResult&& aOther) = default;
ModulesMap mModules;
uint32_t mTrustTestFailures;
};
} // namespace mozilla
namespace IPC {
template <>
struct ParamTraits<mozilla::ModuleVersion> {
typedef mozilla::ModuleVersion paramType;
static void Write(Message* aMsg, const paramType& aParam) {
aMsg->WriteUInt64(aParam.AsInteger());
}
static bool Read(const Message* aMsg, PickleIterator* aIter,
paramType* aResult) {
uint64_t ver;
if (!aMsg->ReadUInt64(aIter, &ver)) {
return false;
}
*aResult = ver;
return true;
}
};
template <>
struct ParamTraits<mozilla::VendorInfo> {
typedef mozilla::VendorInfo paramType;
static void Write(Message* aMsg, const paramType& aParam) {
aMsg->WriteUInt32(static_cast<uint32_t>(aParam.mSource));
WriteParam(aMsg, aParam.mVendor);
}
static bool Read(const Message* aMsg, PickleIterator* aIter,
paramType* aResult) {
uint32_t source;
if (!aMsg->ReadUInt32(aIter, &source)) {
return false;
}
aResult->mSource = static_cast<mozilla::VendorInfo::Source>(source);
if (!ReadParam(aMsg, aIter, &aResult->mVendor)) {
return false;
}
return true;
}
};
template <>
struct ParamTraits<mozilla::ModuleRecord> {
typedef mozilla::ModuleRecord paramType;
static void Write(Message* aMsg, const paramType& aParam) {
WriteParam(aMsg, aParam.mResolvedNtName);
nsAutoString resolvedDosName;
if (aParam.mResolvedDosName) {
mozilla::DebugOnly<nsresult> rv =
aParam.mResolvedDosName->GetPath(resolvedDosName);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
WriteParam(aMsg, resolvedDosName);
WriteParam(aMsg, aParam.mSanitizedDllName);
WriteParam(aMsg, aParam.mVersion);
WriteParam(aMsg, aParam.mVendorInfo);
aMsg->WriteUInt32(static_cast<uint32_t>(aParam.mTrustFlags));
}
static bool Read(const Message* aMsg, PickleIterator* aIter,
paramType* aResult) {
if (!ReadParam(aMsg, aIter, &aResult->mResolvedNtName)) {
return false;
}
nsAutoString resolvedDosName;
if (!ReadParam(aMsg, aIter, &resolvedDosName)) {
return false;
}
if (resolvedDosName.IsEmpty()) {
aResult->mResolvedDosName = nullptr;
} else if (NS_FAILED(NS_NewLocalFile(
resolvedDosName, false,
getter_AddRefs(aResult->mResolvedDosName)))) {
return false;
}
if (!ReadParam(aMsg, aIter, &aResult->mSanitizedDllName)) {
return false;
}
if (!ReadParam(aMsg, aIter, &aResult->mVersion)) {
return false;
}
if (!ReadParam(aMsg, aIter, &aResult->mVendorInfo)) {
return false;
}
uint32_t trustFlags;
if (!aMsg->ReadUInt32(aIter, &trustFlags)) {
return false;
}
aResult->mTrustFlags = static_cast<mozilla::ModuleTrustFlags>(trustFlags);
return true;
}
};
template <>
struct ParamTraits<mozilla::ModulesMap> {
typedef mozilla::ModulesMap paramType;
static void Write(Message* aMsg, const paramType& aParam) {
aMsg->WriteUInt32(aParam.Count());
for (auto iter = aParam.ConstIter(); !iter.Done(); iter.Next()) {
MOZ_RELEASE_ASSERT(iter.Data());
WriteParam(aMsg, iter.Key());
WriteParam(aMsg, *(iter.Data()));
}
}
static bool Read(const Message* aMsg, PickleIterator* aIter,
paramType* aResult) {
uint32_t count;
if (!ReadParam(aMsg, aIter, &count)) {
return false;
}
for (uint32_t current = 0; current < count; ++current) {
nsAutoString key;
if (!ReadParam(aMsg, aIter, &key) || key.IsEmpty()) {
return false;
}
RefPtr<mozilla::ModuleRecord> rec(new mozilla::ModuleRecord());
if (!ReadParam(aMsg, aIter, rec.get())) {
return false;
}
aResult->Put(key, rec.forget());
}
return true;
}
};
template <>
struct ParamTraits<mozilla::ModulePaths> {
typedef mozilla::ModulePaths paramType;
static void Write(Message* aMsg, const paramType& aParam) {
aParam.mModuleNtPaths.match(
[aMsg](const paramType::SetType& aSet) { WriteSet(aMsg, aSet); },
[aMsg](const paramType::VecType& aVec) { WriteVector(aMsg, aVec); });
}
static bool Read(const Message* aMsg, PickleIterator* aIter,
paramType* aResult) {
uint32_t len;
if (!aMsg->ReadUInt32(aIter, &len)) {
return false;
}
// As noted in the comments for ModulePaths, we only deserialize using the
// Vector representation.
auto& vec = aResult->mModuleNtPaths.as<paramType::VecType>();
if (!vec.reserve(len)) {
return false;
}
for (uint32_t idx = 0; idx < len; ++idx) {
nsString str;
if (!ReadParam(aMsg, aIter, &str)) {
return false;
}
if (!vec.emplaceBack(std::move(str))) {
return false;
}
}
return true;
}
private:
// NB: This function must write out the set in the same format as WriteVector
static void WriteSet(Message* aMsg, const paramType::SetType& aSet) {
aMsg->WriteUInt32(aSet.Count());
for (auto iter = aSet.ConstIter(); !iter.Done(); iter.Next()) {
WriteParam(aMsg, iter.Get()->GetKey());
}
}
// NB: This function must write out the vector in the same format as WriteSet
static void WriteVector(Message* aMsg, const paramType::VecType& aVec) {
aMsg->WriteUInt32(aVec.length());
for (auto const& item : aVec) {
WriteParam(aMsg, item);
}
}
};
template <>
struct ParamTraits<mozilla::UntrustedModulesData> {
typedef mozilla::UntrustedModulesData paramType;
static void Write(Message* aMsg, const paramType& aParam) {
aMsg->WriteUInt32(aParam.mProcessType);
aMsg->WriteULong(aParam.mPid);
WriteParam(aMsg, aParam.mElapsed);
WriteParam(aMsg, aParam.mModules);
aMsg->WriteUInt32(aParam.mEvents.length());
for (auto& evt : aParam.mEvents) {
WriteEvent(aMsg, evt);
}
WriteParam(aMsg, aParam.mStacks);
WriteParam(aMsg, aParam.mXULLoadDurationMS);
aMsg->WriteUInt32(aParam.mSanitizationFailures);
aMsg->WriteUInt32(aParam.mTrustTestFailures);
}
static bool Read(const Message* aMsg, PickleIterator* aIter,
paramType* aResult) {
uint32_t processType;
if (!aMsg->ReadUInt32(aIter, &processType)) {
return false;
}
aResult->mProcessType = static_cast<GeckoProcessType>(processType);
if (!aMsg->ReadULong(aIter, &aResult->mPid)) {
return false;
}
if (!ReadParam(aMsg, aIter, &aResult->mElapsed)) {
return false;
}
if (!ReadParam(aMsg, aIter, &aResult->mModules)) {
return false;
}
// We read mEvents manually so that we can use ReadEvent defined below.
uint32_t eventsLen;
if (!ReadParam(aMsg, aIter, &eventsLen)) {
return false;
}
if (!aResult->mEvents.resize(eventsLen)) {
return false;
}
for (uint32_t curEventIdx = 0; curEventIdx < eventsLen; ++curEventIdx) {
if (!ReadEvent(aMsg, aIter, &(aResult->mEvents[curEventIdx]),
aResult->mModules)) {
return false;
}
}
if (!ReadParam(aMsg, aIter, &aResult->mStacks)) {
return false;
}
if (!ReadParam(aMsg, aIter, &aResult->mXULLoadDurationMS)) {
return false;
}
if (!aMsg->ReadUInt32(aIter, &aResult->mSanitizationFailures)) {
return false;
}
if (!aMsg->ReadUInt32(aIter, &aResult->mTrustTestFailures)) {
return false;
}
return true;
}
private:
// Because ProcessedModuleLoadEvent depends on a hash table from
// UntrustedModulesData, we do its serialization as part of this
// specialization.
static void WriteEvent(Message* aMsg,
const mozilla::ProcessedModuleLoadEvent& aParam) {
aMsg->WriteUInt64(aParam.mProcessUptimeMS);
WriteParam(aMsg, aParam.mLoadDurationMS);
aMsg->WriteULong(aParam.mThreadId);
WriteParam(aMsg, aParam.mThreadName);
WriteParam(aMsg, aParam.mRequestedDllName);
WriteParam(aMsg, aParam.mBaseAddress);
// We don't write the ModuleRecord directly; we write its key into the
// UntrustedModulesData::mModules hash table.
MOZ_ASSERT(aParam.mModule && !aParam.mModule->mResolvedNtName.IsEmpty());
WriteParam(aMsg, aParam.mModule->mResolvedNtName);
}
// Because ProcessedModuleLoadEvent depends on a hash table from
// UntrustedModulesData, we do its deserialization as part of this
// specialization.
static bool ReadEvent(const Message* aMsg, PickleIterator* aIter,
mozilla::ProcessedModuleLoadEvent* aResult,
const mozilla::ModulesMap& aModulesMap) {
if (!aMsg->ReadUInt64(aIter, &aResult->mProcessUptimeMS)) {
return false;
}
if (!ReadParam(aMsg, aIter, &aResult->mLoadDurationMS)) {
return false;
}
if (!aMsg->ReadULong(aIter, &aResult->mThreadId)) {
return false;
}
if (!ReadParam(aMsg, aIter, &aResult->mThreadName)) {
return false;
}
if (!ReadParam(aMsg, aIter, &aResult->mRequestedDllName)) {
return false;
}
if (!ReadParam(aMsg, aIter, &aResult->mBaseAddress)) {
return false;
}
nsAutoString resolvedNtName;
if (!ReadParam(aMsg, aIter, &resolvedNtName)) {
return false;
}
aResult->mModule = aModulesMap.Get(resolvedNtName);
if (!aResult->mModule) {
return false;
}
return true;
}
};
template <>
struct ParamTraits<mozilla::ModulesMapResult> {
typedef mozilla::ModulesMapResult paramType;
static void Write(Message* aMsg, const paramType& aParam) {
WriteParam(aMsg, aParam.mModules);
aMsg->WriteUInt32(aParam.mTrustTestFailures);
}
static bool Read(const Message* aMsg, PickleIterator* aIter,
paramType* aResult) {
if (!ReadParam(aMsg, aIter, &aResult->mModules)) {
return false;
}
if (!aMsg->ReadUInt32(aIter, &aResult->mTrustTestFailures)) {
return false;
}
return true;
}
};
} // namespace IPC
#else // defined(XP_WIN)
namespace mozilla {
// For compiling IPDL on non-Windows platforms
using UntrustedModulesData = uint32_t;
using ModulePaths = uint32_t;
using ModulesMapResult = uint32_t;
} // namespace mozilla
#endif // defined(XP_WIN)
#endif // mozilla_UntrustedModulesData_h

View File

@ -9,18 +9,23 @@
#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) {
MOZ_ASSERT(aThread);
if (!aThread) {
return 0UL;
}
@ -32,12 +37,7 @@ static DWORD ToWin32ThreadId(nsIThread* aThread) {
return 0UL;
}
PRUint32 tid = ::PR_GetThreadID(prThread);
if (!tid) {
return 0UL;
}
return DWORD(tid);
return DWORD(::PR_GetThreadID(prThread));
}
namespace mozilla {
@ -86,15 +86,22 @@ class MOZ_RAII BackgroundPriorityRegion final {
const BOOL mIsBackground;
};
/* static */
bool UntrustedModulesProcessor::IsSupportedProcessType() {
switch (XRE_GetProcessType()) {
case GeckoProcessType_Default:
case GeckoProcessType_Content:
case GeckoProcessType_RDD:
return true;
default:
return false;
}
}
/* static */
RefPtr<UntrustedModulesProcessor> UntrustedModulesProcessor::Create() {
#if defined(EARLY_BETA_OR_EARLIER)
if (!XRE_IsParentProcess()) {
// Not currently supported outside the parent process
return nullptr;
}
if (!Telemetry::CanRecordReleaseData()) {
if (!IsSupportedProcessType() || !Telemetry::CanRecordReleaseData()) {
return nullptr;
}
@ -123,23 +130,35 @@ 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)) {
// No more background processing allowed beyond this point
mAllowProcessing = false;
// Ensure that mThread cannot run at low priority anymore
BackgroundPriorityRegion::Clear(mThread);
MutexAutoLock lock(mUnprocessedMutex);
CancelScheduledProcessing(lock);
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();
@ -157,10 +176,13 @@ 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 char* aSource, const MutexAutoLock& aProofOfLock) {
const MutexAutoLock& aProofOfLock) {
// In case something tried to load a DLL during shutdown
if (!mThread) {
return;
@ -185,9 +207,9 @@ void UntrustedModulesProcessor::ScheduleNonEmptyQueueProcessing(
// 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<const char*>(
nsCOMPtr<nsIRunnable> idleRunnable(NewCancelableRunnableMethod(
"UntrustedModulesProcessor::DispatchBackgroundProcessing", this,
&UntrustedModulesProcessor::DispatchBackgroundProcessing, aSource));
&UntrustedModulesProcessor::DispatchBackgroundProcessing));
if (NS_FAILED(NS_DispatchToMainThreadQueue(do_AddRef(idleRunnable),
EventQueuePriority::Idle))) {
@ -212,17 +234,16 @@ void UntrustedModulesProcessor::CancelScheduledProcessing(
mIdleRunnable = nullptr;
}
void UntrustedModulesProcessor::DispatchBackgroundProcessing(
const char* aSource) {
void UntrustedModulesProcessor::DispatchBackgroundProcessing() {
MOZ_ASSERT(NS_IsMainThread());
if (!mAllowProcessing) {
return;
}
nsCOMPtr<nsIRunnable> runnable(NewRunnableMethod<const char*>(
nsCOMPtr<nsIRunnable> runnable(NewRunnableMethod(
"UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue", this,
&UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue, aSource));
&UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue));
mThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
}
@ -243,7 +264,7 @@ void UntrustedModulesProcessor::Enqueue(
Unused << mUnprocessedModuleLoads.emplaceBack(std::move(aModLoadInfo));
ScheduleNonEmptyQueueProcessing(__func__, lock);
ScheduleNonEmptyQueueProcessing(lock);
}
void UntrustedModulesProcessor::Enqueue(ModuleLoadInfoVec&& aEvents) {
@ -260,7 +281,7 @@ void UntrustedModulesProcessor::Enqueue(ModuleLoadInfoVec&& aEvents) {
Unused << mUnprocessedModuleLoads.emplaceBack(std::move(event));
}
ScheduleNonEmptyQueueProcessing(__func__, lock);
ScheduleNonEmptyQueueProcessing(lock);
}
void UntrustedModulesProcessor::AssertRunningOnLazyIdleThread() {
@ -277,22 +298,79 @@ void UntrustedModulesProcessor::AssertRunningOnLazyIdleThread() {
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(__func__);
ProcessModuleLoadQueue();
return GetAllProcessedData(__func__);
}
RefPtr<UntrustedModulesPromise> UntrustedModulesProcessor::GetAllProcessedData(
const char* aSource) {
AssertRunningOnLazyIdleThread();
UntrustedModulesData result;
if (!mProcessedModuleLoads) {
return UntrustedModulesPromise::CreateAndResolve(Nothing(), __func__);
return UntrustedModulesPromise::CreateAndResolve(Nothing(), aSource);
}
result.Swap(mProcessedModuleLoads);
@ -300,11 +378,66 @@ UntrustedModulesProcessor::GetProcessedDataInternal() {
result.mElapsed = TimeStamp::Now() - TimeStamp::ProcessCreation();
return UntrustedModulesPromise::CreateAndResolve(
Some(UntrustedModulesData(std::move(result))), __func__);
Some(UntrustedModulesData(std::move(result))), aSource);
}
void UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue(
const char* 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;
}
@ -315,26 +448,35 @@ void UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue(
return;
}
ProcessModuleLoadQueue(aSource);
ProcessModuleLoadQueue();
}
RefPtr<ModuleRecord> UntrustedModulesProcessor::GetModuleRecord(
UntrustedModulesData::ModulesMap& aModules, const ModuleEvaluator& aModEval,
const nsAString& aResolvedNtPath) {
nsAutoString resolvedDosPath;
if (!NtPathToDosPath(aResolvedNtPath, resolvedDosPath)) {
return nullptr;
}
RefPtr<ModuleRecord> UntrustedModulesProcessor::GetOrAddModuleRecord(
ModulesMap& aModules, const ModuleEvaluator& aModEval,
const glue::EnhancedModuleLoadInfo& aModLoadInfo) {
return GetOrAddModuleRecord(aModules, aModEval,
aModLoadInfo.mNtLoadInfo.mSectionName.AsString());
}
auto addPtr = aModules.LookupForAdd(resolvedDosPath);
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(resolvedDosPath));
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;
}
@ -345,10 +487,69 @@ RefPtr<ModuleRecord> UntrustedModulesProcessor::GetModuleRecord(
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(const char* aSource) {
void UntrustedModulesProcessor::ProcessModuleLoadQueue() {
AssertRunningOnLazyIdleThread();
if (!XRE_IsParentProcess()) {
BackgroundProcessModuleLoadQueueChildProcess();
return;
}
Vector<glue::EnhancedModuleLoadInfo> loadsToProcess;
{ // Scope for lock
@ -357,7 +558,7 @@ void UntrustedModulesProcessor::ProcessModuleLoadQueue(const char* aSource) {
loadsToProcess.swap(mUnprocessedModuleLoads);
}
if (!mAllowProcessing) {
if (!mAllowProcessing || loadsToProcess.empty()) {
return;
}
@ -368,7 +569,7 @@ void UntrustedModulesProcessor::ProcessModuleLoadQueue(const char* aSource) {
}
Telemetry::BatchProcessedStackGenerator stackProcessor;
nsRefPtrHashtable<nsStringHashKey, ModuleRecord> modules;
ModulesMap modules;
Maybe<double> maybeXulLoadDuration;
Vector<Telemetry::ProcessedStack> processedStacks;
@ -381,8 +582,7 @@ void UntrustedModulesProcessor::ProcessModuleLoadQueue(const char* aSource) {
return;
}
RefPtr<ModuleRecord> module(GetModuleRecord(
modules, modEval, entry.mNtLoadInfo.mSectionName.AsString()));
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.
@ -454,4 +654,335 @@ void UntrustedModulesProcessor::ProcessModuleLoadQueue(const char* aSource) {
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) {
return GetModulesTrustPromise::CreateAndReject(
NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
}
// 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;
}
Telemetry::BatchProcessedStackGenerator stackProcessor;
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;
}
glue::EnhancedModuleLoadInfo::BacktraceType backtrace =
std::move(item.mNtLoadInfo.mBacktrace);
ProcessedModuleLoadEvent event(std::move(item), std::move(module));
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, module.forget());
}
return ModulesTrustPromise::CreateAndResolve(std::move(result), __func__);
}
} // namespace mozilla

View File

@ -8,6 +8,7 @@
#define mozilla_UntrustedModulesProcessor_h
#include "mozilla/Atomics.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/glue/WindowsDllServices.h"
#include "mozilla/LazyIdleThread.h"
#include "mozilla/Maybe.h"
@ -29,6 +30,11 @@ class ModuleEvaluator;
using UntrustedModulesPromise =
MozPromise<Maybe<UntrustedModulesData>, nsresult, true>;
using ModulesTrustPromise = MozPromise<ModulesMapResult, nsresult, true>;
using GetModulesTrustIpcPromise =
MozPromise<Maybe<ModulesMapResult>, ipc::ResponseRejectReason, true>;
class UntrustedModulesProcessor final : public nsIObserver {
public:
static RefPtr<UntrustedModulesProcessor> Create();
@ -36,11 +42,21 @@ class UntrustedModulesProcessor final : public nsIObserver {
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIOBSERVER
// Called by DLL Services to explicitly begin shutting down
void Disable();
// Called by DLL Services to submit module load data to the processor
void Enqueue(glue::EnhancedModuleLoadInfo&& aModLoadInfo);
void Enqueue(ModuleLoadInfoVec&& aEvents);
// Called by telemetry to retrieve the processed data
RefPtr<UntrustedModulesPromise> GetProcessedData();
// Called by IPC actors in the parent process to evaluate module trust
// on behalf of child processes
RefPtr<ModulesTrustPromise> GetModulesTrust(ModulePaths&& aModPaths,
bool aRunAtNormalPriority);
UntrustedModulesProcessor(const UntrustedModulesProcessor&) = delete;
UntrustedModulesProcessor(UntrustedModulesProcessor&&) = delete;
UntrustedModulesProcessor& operator=(const UntrustedModulesProcessor&) =
@ -50,20 +66,68 @@ class UntrustedModulesProcessor final : public nsIObserver {
private:
~UntrustedModulesProcessor() = default;
UntrustedModulesProcessor();
static bool IsSupportedProcessType();
void AddObservers();
void RemoveObservers();
void ScheduleNonEmptyQueueProcessing(const char* aSource,
const MutexAutoLock& aProofOfLock);
void CancelScheduledProcessing(const MutexAutoLock& aProofOfLock);
void DispatchBackgroundProcessing(const char* aSource);
void BackgroundProcessModuleLoadQueue(const char* aSource);
void ProcessModuleLoadQueue(const char* aSource);
void AssertRunningOnLazyIdleThread();
RefPtr<UntrustedModulesPromise> GetProcessedDataInternal();
RefPtr<ModuleRecord> GetModuleRecord(
UntrustedModulesData::ModulesMap& aModules,
const ModuleEvaluator& aModEval, const nsAString& aResolvedNtPath);
void ScheduleNonEmptyQueueProcessing(const MutexAutoLock& aProofOfLock);
void CancelScheduledProcessing(const MutexAutoLock& aProofOfLock);
void DispatchBackgroundProcessing();
void BackgroundProcessModuleLoadQueue();
void ProcessModuleLoadQueue();
using LoadsVec = Vector<glue::EnhancedModuleLoadInfo>;
class ModulesMapResultWithLoads final {
public:
ModulesMapResultWithLoads(Maybe<ModulesMapResult>&& aModMapResult,
LoadsVec&& aLoads)
: mModMapResult(std::move(aModMapResult)), mLoads(std::move(aLoads)) {}
Maybe<ModulesMapResult> mModMapResult;
LoadsVec mLoads;
};
using GetModulesTrustPromise =
MozPromise<Maybe<ModulesMapResultWithLoads>, nsresult, true>;
enum class Priority { Default, Background };
RefPtr<GetModulesTrustPromise> ProcessModuleLoadQueueChildProcess(
Priority aPriority);
void BackgroundProcessModuleLoadQueueChildProcess();
void AssertRunningOnLazyIdleThread();
RefPtr<UntrustedModulesPromise> GetProcessedDataInternal();
RefPtr<UntrustedModulesPromise> GetProcessedDataInternalChildProcess();
RefPtr<ModulesTrustPromise> GetModulesTrustInternal(
ModulePaths&& aModPaths, bool aRunAtNormalPriority);
RefPtr<ModulesTrustPromise> GetModulesTrustInternal(ModulePaths&& aModPaths);
// These two functions are only called by the parent process
RefPtr<ModuleRecord> GetOrAddModuleRecord(
ModulesMap& aModules, const ModuleEvaluator& aModEval,
const glue::EnhancedModuleLoadInfo& aModLoadInfo);
RefPtr<ModuleRecord> GetOrAddModuleRecord(ModulesMap& aModules,
const ModuleEvaluator& aModEval,
const nsAString& aResolvedNtPath);
// Only called by child processes
RefPtr<ModuleRecord> GetModuleRecord(
const ModulesMap& aModules,
const glue::EnhancedModuleLoadInfo& aModuleLoadInfo);
RefPtr<GetModulesTrustIpcPromise> SendGetModulesTrust(ModulePaths&& aModules,
Priority aPriority);
void CompleteProcessing(ModulesMapResultWithLoads&& aModulesAndLoads);
RefPtr<UntrustedModulesPromise> GetAllProcessedData(const char* aSource);
private:
RefPtr<LazyIdleThread> mThread;
Mutex mUnprocessedMutex;

View File

@ -17,6 +17,7 @@
#include "nsCOMPtr.h"
#include "nsIObserverService.h"
#include "nsString.h"
#include "nsXULAppAPI.h"
namespace mozilla {
@ -24,11 +25,17 @@ const char* DllServices::kTopicDllLoadedMainThread = "dll-loaded-main-thread";
const char* DllServices::kTopicDllLoadedNonMainThread =
"dll-loaded-non-main-thread";
/* static */
DllServices* DllServices::Get() {
static StaticLocalRefPtr<DllServices> sInstance(
[]() -> already_AddRefed<DllServices> {
RefPtr<DllServices> dllSvc(new DllServices());
dllSvc->EnableFull();
// Full DLL services require XPCOM, which GMP doesn't have
if (XRE_IsGMPluginProcess()) {
dllSvc->EnableBasic();
} else {
dllSvc->EnableFull();
}
auto setClearOnShutdown = [ptr = &sInstance]() -> void {
ClearOnShutdown(ptr);
@ -50,8 +57,11 @@ DllServices* DllServices::Get() {
return sInstance;
}
DllServices::DllServices()
: mUntrustedModulesProcessor(UntrustedModulesProcessor::Create()) {}
void DllServices::StartUntrustedModulesProcessor() {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mUntrustedModulesProcessor);
mUntrustedModulesProcessor = UntrustedModulesProcessor::Create();
}
RefPtr<UntrustedModulesPromise> DllServices::GetUntrustedModulesData() {
if (!mUntrustedModulesProcessor) {
@ -62,6 +72,29 @@ RefPtr<UntrustedModulesPromise> DllServices::GetUntrustedModulesData() {
return mUntrustedModulesProcessor->GetProcessedData();
}
void DllServices::DisableFull() {
if (XRE_IsGMPluginProcess()) {
return;
}
if (mUntrustedModulesProcessor) {
mUntrustedModulesProcessor->Disable();
}
glue::DllServices::DisableFull();
}
RefPtr<ModulesTrustPromise> DllServices::GetModulesTrust(
ModulePaths&& aModPaths, bool aRunAtNormalPriority) {
if (!mUntrustedModulesProcessor) {
return ModulesTrustPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED,
__func__);
}
return mUntrustedModulesProcessor->GetModulesTrust(std::move(aModPaths),
aRunAtNormalPriority);
}
void DllServices::NotifyDllLoad(glue::EnhancedModuleLoadInfo&& aModLoadInfo) {
MOZ_ASSERT(NS_IsMainThread());
@ -83,6 +116,10 @@ void DllServices::NotifyDllLoad(glue::EnhancedModuleLoadInfo&& aModLoadInfo) {
}
nsCOMPtr<nsIObserverService> obsServ(mozilla::services::GetObserverService());
if (!obsServ) {
return;
}
obsServ->NotifyObservers(nullptr, topic, dllFilePath.get());
}

View File

@ -8,22 +8,41 @@
#define mozilla_WinDllServices_h
#include "mozilla/glue/WindowsDllServices.h"
#include "mozilla/Maybe.h"
#include "mozilla/MozPromise.h"
#include "mozilla/RefPtr.h"
#include "mozilla/UntrustedModulesProcessor.h"
namespace mozilla {
class UntrustedModulesData;
class UntrustedModulesProcessor;
using UntrustedModulesPromise =
MozPromise<Maybe<UntrustedModulesData>, nsresult, true>;
struct ModulePaths;
class ModulesMapResult;
using ModulesTrustPromise = MozPromise<ModulesMapResult, nsresult, true>;
class DllServices : public glue::DllServices {
public:
static DllServices* Get();
virtual void DisableFull() override;
static const char* kTopicDllLoadedMainThread;
static const char* kTopicDllLoadedNonMainThread;
void StartUntrustedModulesProcessor();
RefPtr<UntrustedModulesPromise> GetUntrustedModulesData();
RefPtr<ModulesTrustPromise> GetModulesTrust(ModulePaths&& aModPaths,
bool aRunAtNormalPriority);
private:
DllServices();
DllServices() = default;
~DllServices() = default;
void NotifyDllLoad(glue::EnhancedModuleLoadInfo&& aModLoadInfo) override;

View File

@ -38,6 +38,7 @@ EXPORTS.mozilla += [
'Bootstrap.h',
'CmdLineAndEnvUtils.h',
'SafeMode.h',
'UntrustedModulesData.h',
]
if CONFIG['MOZ_INSTRUMENT_EVENT_LOOP']:
@ -47,7 +48,6 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
EXPORTS.mozilla += [
'ModuleVersionInfo.h',
'PolicyChecks.h',
'UntrustedModulesData.h',
'UntrustedModulesProcessor.h',
'WinDllServices.h',
'WinTokenUtils.h',

View File

@ -4228,6 +4228,7 @@ nsresult XREMain::XRE_mainRun() {
#if defined(XP_WIN)
RefPtr<mozilla::DllServices> dllServices(mozilla::DllServices::Get());
dllServices->StartUntrustedModulesProcessor();
auto dllServicesDisable =
MakeScopeExit([&dllServices]() { dllServices->DisableFull(); });
#endif // defined(XP_WIN)

View File

@ -24,6 +24,7 @@
# include <process.h>
# include <shobjidl.h>
# include "mozilla/ipc/WindowsMessageLoop.h"
# include "mozilla/WinDllServices.h"
#endif
#include "nsAppDirectoryServiceDefs.h"
@ -752,6 +753,10 @@ nsresult XRE_InitChildProcess(int aArgc, char* aArgv[],
// chrome process is killed in cases where the user shuts the system
// down or logs off.
::SetProcessShutdownParameters(0x280 - 1, SHUTDOWN_NORETRY);
RefPtr<DllServices> dllSvc(DllServices::Get());
auto dllSvcDisable =
MakeScopeExit([&dllSvc]() { dllSvc->DisableFull(); });
#endif
#if defined(MOZ_SANDBOX) && defined(XP_WIN)