Bug 2057112: Part 2 - Pass child process's module list by section handle r=win-reviewers,gstoll

Child processes now report loaded DLLs to the parent by section HANDLE instead
of by filename.  The DLL blocklist's NtMapViewOfSection hook already receives
the section a module is being mapped from, so it duplicates that section,
read-only, onto the ModuleLoadInfo for the load.  The parent identifies the
module from the HANDLE.

Because the section outlives the view, this also fixes reporting for two kinds
of load: a module the blocklist rejected, which is unmapped immediately, and a
module unloaded before the parent processed the batch.  Previously, either
could be deleted before the parent had a chance to consider them.

A handle cannot be forged into a reference to a different file and a child can
only send one it holds.  ValidateAndResolveModuleSection sanity checks:

1. The section must map, and the view must be MEM_IMAGE. A data section can
   be made over any openable file but an image section means the kernel
   accepted the file as a PE and the child's image-load policy applied to it.
2. The file must not be on a remote device.
3. The file must not carry a mandatory integrity label below medium.

These checks replicate the checks of the MITIGATION_IMAGE_LOAD_NO_REMOTE and
MITIGATION_IMAGE_LOAD_NO_LOW_LABEL child sandbox behaviors.  A child
can create a non-IMAGE section over any file it can open, so what it hands us
is not bounded by the image-load policy.  The check avoid considering such
files at all.  This currently applies to all child processes: content, gpu,
RDD, socket, utility (all kinds), and GMP.  All have these sandbox mitigations.

Also fixes a couple of ways that a version resource could misuse VerQueryValueW's
output in the parent: a zero length underflowing to SIZE_MAX in
QueryStringValue and a too-short buffer dereference in GetFromImage.

Differential Revision: https://phabricator.services.mozilla.com/D324703
This commit is contained in:
David Parks
2026-09-11 20:26:16 +00:00
committed by daparks@mozilla.com
parent 75b4e31ffc
commit 476da53d95
13 changed files with 370 additions and 120 deletions
@@ -403,8 +403,9 @@ CrossProcessDllInterceptor::FuncHookType<NtMapViewOfSectionPtr>
// All the code for patched_NtMapViewOfSection that relies on checked stack
// buffers (e.g. mbi, sectionFileName) should be put in this helper function
// (see bug 1733532).
MOZ_NEVER_INLINE NTSTATUS AfterMapViewOfExecutableSection(
HANDLE aProcess, PVOID* aBaseAddress, NTSTATUS aStubStatus) {
MOZ_NEVER_INLINE NTSTATUS
AfterMapViewOfExecutableSection(HANDLE aSection, HANDLE aProcess,
PVOID* aBaseAddress, NTSTATUS aStubStatus) {
// We don't care about mappings that aren't MEM_IMAGE.
MEMORY_BASIC_INFORMATION mbi;
NTSTATUS ntStatus =
@@ -525,9 +526,30 @@ MOZ_NEVER_INLINE NTSTATUS AfterMapViewOfExecutableSection(
}
if (nt::RtlGetProcessHeap()) {
// Make a read-only duplicate of the section for the parent process. Only
// child processes need this.
// DuplicateHandle is in kernel32 and this intercepted function can run
// before that is loaded, so use ntdll's equivalent.
nt::AutoHandle sectionForParent;
bool sectionForParentUnavailable = false;
if (gBlocklistInitFlags & eDllBlocklistInitFlagIsChildProcess) {
HANDLE duplicate = nullptr;
if (NT_SUCCESS(::NtDuplicateObject(
nt::kCurrentProcess, aSection, nt::kCurrentProcess, &duplicate,
SECTION_QUERY | SECTION_MAP_READ, 0, 0)) &&
duplicate) {
sectionForParent = nt::AutoHandle(duplicate);
} else {
// Not fatal: the load proceeds and the parent simply cannot evaluate
// this module.
sectionForParentUnavailable = true;
}
}
ModuleLoadFrame::NotifySectionMap(
nt::AllocatedUnicodeString(sectionFileName), *aBaseAddress, aStubStatus,
loadStatus, isInjectedDependent);
loadStatus, isInjectedDependent, std::move(sectionForParent),
sectionForParentUnavailable);
}
if (loadStatus == ModuleLoadInfo::Status::Loaded ||
@@ -604,8 +626,8 @@ NTSTATUS NTAPI patched_NtMapViewOfSection(
return stubStatus;
}
NTSTATUS rv =
AfterMapViewOfExecutableSection(aProcess, aBaseAddress, stubStatus);
NTSTATUS rv = AfterMapViewOfExecutableSection(aSection, aProcess,
aBaseAddress, stubStatus);
if (FAILED(rv)) {
rollback();
}
@@ -24,7 +24,9 @@ ModuleLoadFrame::ModuleLoadFrame(PCUNICODE_STRING aRequestedDllName)
ModuleLoadFrame::ModuleLoadFrame(nt::AllocatedUnicodeString&& aSectionName,
const void* aMapBaseAddr, NTSTATUS aNtStatus,
ModuleLoadInfo::Status aLoadStatus,
bool aIsDependent)
bool aIsDependent,
nt::AutoHandle&& aSectionHandle,
bool aSectionHandleUnavailable)
: mPrev(sTopFrame.get()),
mContext(nullptr),
mLSPSubstitutionRequired(false),
@@ -33,6 +35,11 @@ ModuleLoadFrame::ModuleLoadFrame(nt::AllocatedUnicodeString&& aSectionName,
aIsDependent) {
sTopFrame.set(this);
// This constructor serves a mapping that did not pass through LdrLoadDll, so
// OnSectionMap never runs for it and the section handle has to be taken here.
mLoadInfo.mSectionHandle = std::move(aSectionHandle);
mLoadInfo.mSectionHandleUnavailable = aSectionHandleUnavailable;
gLoaderPrivateAPI.NotifyBeginDllLoad(&mContext, mLoadInfo.mSectionName);
}
@@ -71,7 +78,8 @@ void ModuleLoadFrame::SetLSPSubstitutionRequired(PCUNICODE_STRING aLeafName) {
void ModuleLoadFrame::NotifySectionMap(
nt::AllocatedUnicodeString&& aSectionName, const void* aMapBaseAddr,
NTSTATUS aMapNtStatus, ModuleLoadInfo::Status aLoadStatus,
bool aIsDependent) {
bool aIsDependent, nt::AutoHandle&& aSectionHandle,
bool aSectionHandleUnavailable) {
ModuleLoadFrame* topFrame = sTopFrame.get();
if (!topFrame) {
// The only time that this data is useful is during initial mapping of
@@ -80,13 +88,15 @@ void ModuleLoadFrame::NotifySectionMap(
// initial process startup.
if (gLoaderPrivateAPI.IsDefaultObserver()) {
OnBareSectionMap(std::move(aSectionName), aMapBaseAddr, aMapNtStatus,
aLoadStatus, aIsDependent);
aLoadStatus, aIsDependent, std::move(aSectionHandle),
aSectionHandleUnavailable);
}
return;
}
topFrame->OnSectionMap(std::move(aSectionName), aMapBaseAddr, aMapNtStatus,
aLoadStatus, aIsDependent);
aLoadStatus, aIsDependent, std::move(aSectionHandle),
aSectionHandleUnavailable);
}
/* static */
@@ -96,28 +106,35 @@ void ModuleLoadFrame::OnSectionMap(nt::AllocatedUnicodeString&& aSectionName,
const void* aMapBaseAddr,
NTSTATUS aMapNtStatus,
ModuleLoadInfo::Status aLoadStatus,
bool aIsDependent) {
bool aIsDependent,
nt::AutoHandle&& aSectionHandle,
bool aSectionHandleUnavailable) {
if (mLoadInfo.mBaseAddr) {
// If mBaseAddr is not null then |this| has already seen a module load. This
// means that we are witnessing a bare section map.
OnBareSectionMap(std::move(aSectionName), aMapBaseAddr, aMapNtStatus,
aLoadStatus, aIsDependent);
aLoadStatus, aIsDependent, std::move(aSectionHandle),
aSectionHandleUnavailable);
return;
}
mLoadInfo.mSectionName = std::move(aSectionName);
mLoadInfo.mBaseAddr = aMapBaseAddr;
mLoadInfo.mStatus = aLoadStatus;
mLoadInfo.mSectionHandle = std::move(aSectionHandle);
mLoadInfo.mSectionHandleUnavailable = aSectionHandleUnavailable;
}
/* static */
void ModuleLoadFrame::OnBareSectionMap(
nt::AllocatedUnicodeString&& aSectionName, const void* aMapBaseAddr,
NTSTATUS aMapNtStatus, ModuleLoadInfo::Status aLoadStatus,
bool aIsDependent) {
bool aIsDependent, nt::AutoHandle&& aSectionHandle,
bool aSectionHandleUnavailable) {
// We call the special constructor variant that is used for bare mappings.
ModuleLoadFrame frame(std::move(aSectionName), aMapBaseAddr, aMapNtStatus,
aLoadStatus, aIsDependent);
aLoadStatus, aIsDependent, std::move(aSectionHandle),
aSectionHandleUnavailable);
}
NTSTATUS ModuleLoadFrame::SetLoadStatus(NTSTATUS aNtStatus,
@@ -30,12 +30,18 @@ class MOZ_RAII ModuleLoadFrame final {
static void NotifyLSPSubstitutionRequired(PCUNICODE_STRING aLeafName);
/**
* This static method is called by the NtMapViewOfSection hook.
* Helper for the NtMapViewOfSection hook.
*
* Ownership of aSectionHandle is transfered to this function.
* The section handle may be null, in which case the parent simply cannot
* evaluate this module.
*/
static void NotifySectionMap(nt::AllocatedUnicodeString&& aSectionName,
const void* aMapBaseAddr, NTSTATUS aMapNtStatus,
ModuleLoadInfo::Status aLoadStatus,
bool aIsDependent);
bool aIsDependent,
nt::AutoHandle&& aSectionHandle,
bool aSectionHandleUnavailable);
static bool ExistsTopFrame();
/**
@@ -55,12 +61,16 @@ class MOZ_RAII ModuleLoadFrame final {
*/
ModuleLoadFrame(nt::AllocatedUnicodeString&& aSectionName,
const void* aMapBaseAddr, NTSTATUS aNtStatus,
ModuleLoadInfo::Status aLoadStatus, bool aIsDependent);
ModuleLoadInfo::Status aLoadStatus, bool aIsDependent,
nt::AutoHandle&& aSectionHandle,
bool aSectionHandleUnavailable);
void SetLSPSubstitutionRequired(PCUNICODE_STRING aLeafName);
void OnSectionMap(nt::AllocatedUnicodeString&& aSectionName,
const void* aMapBaseAddr, NTSTATUS aMapNtStatus,
ModuleLoadInfo::Status aLoadStatus, bool aIsDependent);
ModuleLoadInfo::Status aLoadStatus, bool aIsDependent,
nt::AutoHandle&& aSectionHandle,
bool aSectionHandleUnavailable);
/**
* A "bare" section mapping is one that was mapped without the code passing
@@ -70,7 +80,9 @@ class MOZ_RAII ModuleLoadFrame final {
static void OnBareSectionMap(nt::AllocatedUnicodeString&& aSectionName,
const void* aMapBaseAddr, NTSTATUS aMapNtStatus,
ModuleLoadInfo::Status aLoadStatus,
bool aIsDependent);
bool aIsDependent,
nt::AutoHandle&& aSectionHandle,
bool aSectionHandleUnavailable);
private:
// Link to the previous frame
+4
View File
@@ -1789,6 +1789,10 @@ parent:
* obtain enough information about a DLL file to determine its
* trustworthiness. This API asks the chrome process to perform that
* evaluation.
*
* Each entry carries a handle to the file backing the module, captured by
* the DLL blocklist from the handle the loader opened. The parent derives
* the module's path from the handle and evaluates the module it targets.
*/
async GetModulesTrust(ModuleIdentifiers aModIdents, bool aRunAtNormalPriority)
returns (ModulesMapResult? modMapResult);
-3
View File
@@ -10,9 +10,6 @@
#include "mozilla/gmp/PGMPChild.h"
namespace mozilla {
#ifdef XP_WIN
struct ModuleIdentifiers;
#endif
namespace ipc {
class ByteBuf;
+52
View File
@@ -106,6 +106,13 @@ NTSTATUS NTAPI NtReadVirtualMemory(HANDLE aProcessHandle, PVOID aBaseAddress,
PVOID aBuffer, SIZE_T aNumBytesToRead,
PSIZE_T aNumBytesRead);
NTSTATUS NTAPI NtDuplicateObject(HANDLE aSourceProcessHandle,
HANDLE aSourceHandle,
HANDLE aTargetProcessHandle,
PHANDLE aTargetHandle,
ACCESS_MASK aDesiredAccess,
ULONG aHandleAttributes, ULONG aOptions);
NTSTATUS NTAPI LdrLoadDll(PWCHAR aDllPath, PULONG aFlags,
PUNICODE_STRING aDllName, PHANDLE aOutHandle);
@@ -1755,6 +1762,51 @@ class RtlAllocPolicy {
[[nodiscard]] bool checkSimulatedOOM() const { return true; }
};
/**
* A minimal owning wrapper for a handle, closed with NtClose.
*
* Code reachable from the DLL blocklist hooks cannot use nsAutoHandle (XPCOM)
* or UniqueFileHandle, whose deleters call kernel32's CloseHandle: those hooks
* can run before kernel32.dll is available, which is why
* Kernel32ExportsSolver exists. NtClose is in ntdll and is always callable,
* in the launcher process and inside XUL alike.
*/
class AutoHandle final {
public:
AutoHandle() : mHandle(nullptr) {}
explicit AutoHandle(HANDLE aHandle) : mHandle(aHandle) {}
~AutoHandle() { reset(); }
AutoHandle(AutoHandle&& aOther) : mHandle(aOther.mHandle) {
aOther.mHandle = nullptr;
}
AutoHandle& operator=(AutoHandle&& aOther) {
if (this != &aOther) {
reset();
mHandle = aOther.mHandle;
aOther.mHandle = nullptr;
}
return *this;
}
AutoHandle(const AutoHandle&) = delete;
AutoHandle& operator=(const AutoHandle&) = delete;
HANDLE get() const { return mHandle; }
explicit operator bool() const { return !!mHandle; }
void reset() {
if (mHandle) {
::NtClose(mHandle);
mHandle = nullptr;
}
}
private:
HANDLE mHandle;
};
class AutoMappedView final {
void* mView;
@@ -33,6 +33,12 @@ static bool QueryStringValue(const void* aBlock, DWORD aTranslation,
if (!::VerQueryValueW(aBlock, path.get(), (PVOID*)&lpBuffer, &len)) {
return false;
}
// len includes the terminating NUL, so must be greater than 0 when valid.
if (!lpBuffer || !len) {
return false;
}
aResult.Assign(lpBuffer, (size_t)len - 1);
return true;
}
@@ -79,7 +85,8 @@ bool ModuleVersionInfo::GetFromImage(const nsAString& aPath) {
VS_FIXEDFILEINFO* vInfo = nullptr;
UINT vInfoLen = 0;
if (::VerQueryValueW(verInfo.get(), L"\\", (LPVOID*)&vInfo, &vInfoLen)) {
if (::VerQueryValueW(verInfo.get(), L"\\", (LPVOID*)&vInfo, &vInfoLen) &&
vInfo && vInfoLen >= sizeof(VS_FIXEDFILEINFO)) {
mFileVersion =
VersionNumber(vInfo->dwFileVersionMS, vInfo->dwFileVersionLS);
mProductVersion =
+6 -80
View File
@@ -15,9 +15,9 @@
# include "mozilla/Maybe.h"
# include "mozilla/RefPtr.h"
# include "mozilla/TypedEnumBits.h"
# include "mozilla/Variant.h"
# include "mozilla/Vector.h"
# include "mozilla/WinHeaderOnlyUtils.h"
# include "mozilla/ipc/FileDescriptor.h"
# include "nsCOMPtr.h"
# include "nsHashKeys.h"
# include "nsIFile.h"
@@ -104,28 +104,12 @@ class ModuleRecord final {
};
/**
* 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.
* The set of modules a child process wants trust information for. The child
* asks about each distinct module once per batch, identifying it by a
* read-only duplicate of the section handle it was mapped from, taken by the
* DLL blocklist's NtMapViewOfSection hook.
*/
struct ModuleIdentifiers final {
using SetType = nsTHashtable<nsStringCaseInsensitiveHashKey>;
using VecType = Vector<nsString>;
Variant<SetType, VecType> mModuleNtPaths;
template <typename T>
explicit ModuleIdentifiers(T&& aPaths)
: mModuleNtPaths(AsVariant(std::forward<T>(aPaths))) {}
ModuleIdentifiers() : mModuleNtPaths(VecType()) {}
ModuleIdentifiers(const ModuleIdentifiers& aOther) = delete;
ModuleIdentifiers(ModuleIdentifiers&& aOther) = default;
ModuleIdentifiers& operator=(const ModuleIdentifiers&) = delete;
ModuleIdentifiers& operator=(ModuleIdentifiers&&) = default;
};
using ModuleIdentifiers = nsTArray<mozilla::ipc::FileDescriptor>;
class ProcessedModuleLoadEvent final {
public:
@@ -391,64 +375,6 @@ struct ParamTraits<mozilla::ModulesMap> {
}
};
template <>
struct ParamTraits<mozilla::ModuleIdentifiers> {
typedef mozilla::ModuleIdentifiers paramType;
static void Write(MessageWriter* aWriter, const paramType& aParam) {
aParam.mModuleNtPaths.match(
[aWriter](const paramType::SetType& aSet) { WriteSet(aWriter, aSet); },
[aWriter](const paramType::VecType& aVec) {
WriteVector(aWriter, aVec);
});
}
static bool Read(MessageReader* aReader, paramType* aResult) {
uint32_t len;
if (!aReader->ReadUInt32(&len)) {
return false;
}
// As noted in the comments for ModuleIdentifiers, 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(aReader, &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(MessageWriter* aWriter, const paramType::SetType& aSet) {
aWriter->WriteUInt32(aSet.Count());
for (const auto& key : aSet.Keys()) {
WriteParam(aWriter, key);
}
}
// NB: This function must write out the vector in the same format as WriteSet
static void WriteVector(MessageWriter* aWriter,
const paramType::VecType& aVec) {
aWriter->WriteUInt32(aVec.length());
for (auto const& item : aVec) {
WriteParam(aWriter, item);
}
}
};
template <>
struct ParamTraits<mozilla::UntrustedModulesData> {
typedef mozilla::UntrustedModulesData paramType;
@@ -5,6 +5,8 @@
#include "UntrustedModulesProcessor.h"
#include <windows.h>
#include <aclapi.h>
#include <psapi.h>
#include "GMPPlatform.h"
#include "GMPServiceParent.h"
@@ -12,6 +14,7 @@
#include "mozilla/DebugOnly.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/FileUtilsWin.h"
#include "mozilla/Likely.h"
#include "mozilla/net/SocketProcessChild.h"
#include "mozilla/net/SocketProcessParent.h"
@@ -22,17 +25,175 @@
#include "mozilla/RDDProcessManager.h"
#include "mozilla/Services.h"
#include "mozilla/Telemetry.h"
#include "mozilla/UniquePtrExtensions.h"
#include "ModuleEvaluator.h"
#include "nsCOMPtr.h"
#include "nsHashKeys.h"
#include "nsIObserverService.h"
#include "nsTHashtable.h"
#include "nsThreadUtils.h"
#include "nsWindowsHelpers.h"
#include "nsXULAppAPI.h"
#include "private/prpriv.h" // For PR_GetThreadID
namespace mozilla {
// NT paths are not bounded by MAX_PATH. This is the ceiling we are willing to
// grow a path buffer to while resolving one.
static const uint32_t kMaxNtPathLen = 0x8000;
/**
* Returns true if aDosPath lives on a remote device.
*/
static bool IsRemoteFile(const nsAString& aDosPath) {
// A UNC path is always remote.
if (StringBeginsWith(aDosPath, u"\\\\"_ns)) {
return true;
}
if (aDosPath.Length() < 3 || aDosPath[1] != u':') {
// Some shape we do not recognise; do not guess.
return true;
}
// GetDriveTypeW also catches a drive letter mapped to a network share.
const wchar_t root[] = {static_cast<wchar_t>(aDosPath[0]), L':', L'\\',
L'\0'};
UINT driveType = ::GetDriveTypeW(root);
return driveType == DRIVE_REMOTE || driveType == DRIVE_UNKNOWN ||
driveType == DRIVE_NO_ROOT_DIR;
}
/**
* Returns true if the file at aDosPath carries a mandatory integrity label
* below medium. A file with no label ACE is medium by default, which is the
* most common case, and is accepted.
*/
static bool IsBelowMediumIntegrityFile(const nsAString& aDosPath) {
PACL sacl = nullptr;
PSECURITY_DESCRIPTOR rawSd = nullptr;
nsAutoString path(aDosPath);
if (::GetNamedSecurityInfoW(reinterpret_cast<wchar_t*>(path.BeginWriting()),
SE_FILE_OBJECT, LABEL_SECURITY_INFORMATION,
nullptr, nullptr, nullptr, &sacl,
&rawSd) != ERROR_SUCCESS) {
// Treat errors as untrustworthy.
return true;
}
UniquePtr<void, LocalFreeDeleter> sd(rawSd);
if (!sacl) {
// No label: medium by default.
return false;
}
for (WORD i = 0; i < sacl->AceCount; ++i) {
VOID* rawAce = nullptr;
if (!::GetAce(sacl, i, &rawAce)) {
return true;
}
auto* header = static_cast<ACE_HEADER*>(rawAce);
if (header->AceType != SYSTEM_MANDATORY_LABEL_ACE_TYPE) {
continue;
}
auto* labelAce = static_cast<SYSTEM_MANDATORY_LABEL_ACE*>(rawAce);
auto* sid = reinterpret_cast<PSID>(&labelAce->SidStart);
PUCHAR subAuthorityCount = ::GetSidSubAuthorityCount(sid);
if (!subAuthorityCount || !*subAuthorityCount) {
return true;
}
DWORD* rid = ::GetSidSubAuthority(sid, *subAuthorityCount - 1);
if (!rid) {
return true;
}
return *rid < SECURITY_MANDATORY_MEDIUM_RID;
}
// A SACL with no label ACE is also medium by default.
return false;
}
bool ValidateAndResolveModuleSection(const ipc::FileDescriptor& aSection,
nsAString& aOutNtPath) {
aOutNtPath.Truncate();
if (!aSection.IsValid()) {
return false;
}
UniqueFileHandle section(aSection.ClonePlatformHandle());
if (!section) {
return false;
}
// Recover the backing file's path by mapping a view.
nsAutoString resolved;
{
PVOID view = ::MapViewOfFile(section.get(), FILE_MAP_READ, 0, 0, 0);
if (!view) {
return false;
}
auto unmapView = MakeScopeExit([&]() { ::UnmapViewOfFile(view); });
// A module is an IMAGE section.
MEMORY_BASIC_INFORMATION mbi{};
if (!::VirtualQuery(view, &mbi, sizeof(mbi)) || mbi.Type != MEM_IMAGE) {
return false;
}
// GetMappedFileNameW reports the NT device form
// (\Device\HarddiskVolumeN\...), which is what the child's loader observer
// records via NtQueryVirtualMemory(..., MemorySectionName) and what
// ModuleRecord expects, so the two are directly comparable.
for (uint32_t bufLen = MAX_PATH; bufLen <= kMaxNtPathLen; bufLen *= 2) {
nsAutoString buf;
if (!buf.SetLength(bufLen, fallible)) {
break;
}
DWORD charsWritten = ::GetMappedFileNameW(
::GetCurrentProcess(), view,
reinterpret_cast<wchar_t*>(buf.BeginWriting()), bufLen);
if (!charsWritten) {
break;
}
if (charsWritten >= bufLen - 1) {
// May have been truncated; retry with more room.
continue;
}
buf.SetLength(charsWritten);
resolved = buf;
break;
}
}
if (resolved.IsEmpty()) {
return false;
}
// The remaining checks are about the file, so they need its DOS path.
nsAutoString dosPath;
if (!NtPathToDosPath(resolved, dosPath)) {
return false;
}
// Reject a file on a remote device, or one carrying a mandatory integrity
// label below medium.
if (IsRemoteFile(dosPath) || IsBelowMediumIntegrityFile(dosPath)) {
return false;
}
aOutNtPath = resolved;
return true;
}
class MOZ_RAII BackgroundPriorityRegion final {
public:
BackgroundPriorityRegion()
@@ -808,9 +969,10 @@ UntrustedModulesProcessor::ProcessModuleLoadQueueChildProcess(
NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
}
nsTHashtable<nsStringCaseInsensitiveHashKey> moduleNtPathSet;
nsTHashtable<nsStringCaseInsensitiveHashKey> alreadyAdded;
ModuleIdentifiers moduleIdents;
// Build a set of modules to be processed by the parent
// Build the set of modules to be processed by the parent.
for (UnprocessedModuleLoadInfoContainer* container : loadsToProcess) {
glue::EnhancedModuleLoadInfo& entry = container->mInfo;
@@ -819,7 +981,22 @@ UntrustedModulesProcessor::ProcessModuleLoadQueueChildProcess(
NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
}
moduleNtPathSet.PutEntry(entry.mNtLoadInfo.mSectionName.AsString());
if (!entry.mNtLoadInfo.mSectionHandle) {
// No section handle, so nothing the parent can verify.
continue;
}
nsDependentString sectionName(entry.mNtLoadInfo.mSectionName.AsString());
if (!alreadyAdded.EnsureInserted(sectionName)) {
continue;
}
ipc::FileDescriptor section(entry.mNtLoadInfo.mSectionHandle.get());
if (!section.IsValid()) {
continue;
}
moduleIdents.AppendElement(std::move(section));
}
if (!IsReadyForBackgroundProcessing()) {
@@ -827,14 +1004,11 @@ UntrustedModulesProcessor::ProcessModuleLoadQueueChildProcess(
NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
}
MOZ_ASSERT(!moduleNtPathSet.IsEmpty());
if (moduleNtPathSet.IsEmpty()) {
if (moduleIdents.IsEmpty()) {
// Nothing to process
return GetModulesTrustPromise::CreateAndResolve(Nothing(), __func__);
}
ModuleIdentifiers moduleNtPaths(std::move(moduleNtPathSet));
if (!IsReadyForBackgroundProcessing()) {
return GetModulesTrustPromise::CreateAndReject(
NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
@@ -843,9 +1017,9 @@ UntrustedModulesProcessor::ProcessModuleLoadQueueChildProcess(
RefPtr<UntrustedModulesProcessor> self(this);
auto invoker = [self = std::move(self),
moduleNtPaths = std::move(moduleNtPaths),
moduleIdentifiers = std::move(moduleIdents),
priority = aPriority]() mutable {
return self->SendGetModulesTrust(std::move(moduleNtPaths), priority);
return self->SendGetModulesTrust(std::move(moduleIdentifiers), priority);
};
RefPtr<GetModulesTrustPromise::Private> p(
@@ -1009,7 +1183,7 @@ RefPtr<ModulesTrustPromise> UntrustedModulesProcessor::GetModulesTrustInternal(
return GetModulesTrustInternal(std::move(aModIdents));
}
// For each module in |aModIdents|, evaluate its trustworthiness and only send
// For each module in aModIdents, 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(
@@ -1028,18 +1202,24 @@ RefPtr<ModulesTrustPromise> UntrustedModulesProcessor::GetModulesTrustInternal(
return ModulesTrustPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
}
for (auto& resolvedNtPath :
aModIdents.mModuleNtPaths.as<ModuleIdentifiers::VecType>()) {
for (auto& section : aModIdents) {
if (!IsReadyForBackgroundProcessing()) {
return ModulesTrustPromise::CreateAndReject(
NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
}
MOZ_ASSERT(!resolvedNtPath.IsEmpty());
if (resolvedNtPath.IsEmpty()) {
// The authoritative path, derived from the handle.
nsAutoString resolvedNtPath;
if (!ValidateAndResolveModuleSection(section, resolvedNtPath) ||
resolvedNtPath.IsEmpty()) {
continue;
}
if (!IsReadyForBackgroundProcessing()) {
return ModulesTrustPromise::CreateAndReject(
NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
}
RefPtr<ModuleRecord> module(GetOrAddModuleRecord(modEval, resolvedNtPath));
if (!module) {
// We failed to obtain trust information.
@@ -23,6 +23,21 @@ namespace mozilla {
class ModuleEvaluator;
/**
* Decides whether a section handle a child process sent us is one we should
* evaluate and, if so, derives the module's path from it. We require the
* handle to refer to a module file, on a local disk, that is not
* low-integrity. The requirements reconstruct, on the parent side, the limits
* the sandbox mitigations MITIGATION_IMAGE_LOAD_NO_REMOTE and
* MITIGATION_IMAGE_LOAD_NO_LOW_LABEL enforce, since the section handle can be
* obtained even if the child process isn't permitted to load it as a module.
*
* Declared here so that gtest can exercise it directly. Nothing outside of the
* UntrustedModules implementation and tests should call it.
*/
bool ValidateAndResolveModuleSection(const ipc::FileDescriptor& aSection,
nsAString& aOutNtPath);
using UntrustedModulesPromise =
MozPromise<Maybe<UntrustedModulesData>, nsresult, true>;
+1 -1
View File
@@ -79,7 +79,7 @@ void DllServices::DisableFull() {
}
RefPtr<ModulesTrustPromise> DllServices::GetModulesTrust(
ModuleIdentifiers&& aModIdents, bool aRunAtNormalPriority) {
nsTArray<ipc::FileDescriptor>&& aModIdents, bool aRunAtNormalPriority) {
if (!mUntrustedModulesProcessor) {
return ModulesTrustPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED,
__func__);
+7 -3
View File
@@ -9,6 +9,7 @@
#include "mozilla/Maybe.h"
#include "mozilla/MozPromise.h"
#include "mozilla/RefPtr.h"
#include "nsTArray.h"
namespace mozilla {
@@ -18,7 +19,10 @@ class UntrustedModulesProcessor;
using UntrustedModulesPromise =
MozPromise<Maybe<UntrustedModulesData>, nsresult, true>;
struct ModuleIdentifiers;
namespace ipc {
class FileDescriptor;
} // namespace ipc
class ModulesMapResult;
using ModulesTrustPromise = MozPromise<ModulesMapResult, nsresult, true>;
@@ -37,8 +41,8 @@ class DllServices final : public glue::DllServices {
RefPtr<UntrustedModulesPromise> GetUntrustedModulesData();
RefPtr<ModulesTrustPromise> GetModulesTrust(ModuleIdentifiers&& aModIdents,
bool aRunAtNormalPriority);
RefPtr<ModulesTrustPromise> GetModulesTrust(
nsTArray<ipc::FileDescriptor>&& aModIdents, bool aRunAtNormalPriority);
private:
DllServices() = default;
@@ -28,6 +28,7 @@ struct ModuleLoadInfo final {
mThreadId(nt::RtlGetCurrentThreadId()),
mRequestedDllName(aRequestedDllName),
mBaseAddr(nullptr),
mSectionHandleUnavailable(false),
mStatus(Status::Loaded),
mIsDependent(false) {
# if defined(IMPL_MFBT)
@@ -49,6 +50,7 @@ struct ModuleLoadInfo final {
mThreadId(nt::RtlGetCurrentThreadId()),
mSectionName(std::move(aSectionName)),
mBaseAddr(aBaseAddr),
mSectionHandleUnavailable(false),
mStatus(aLoadStatus),
mIsDependent(aIsDependent) {
# if defined(IMPL_MFBT)
@@ -161,6 +163,18 @@ struct ModuleLoadInfo final {
nt::AllocatedUnicodeString mSectionName;
// The base address of the module's mapped section
const void* mBaseAddr;
// A read-only duplicate of the section this module was mapped from, taken by
// the NtMapViewOfSection hook.
//
// Null for any load the NtMapViewOfSection hook did not take a handle for.
// See mSectionHandleUnavailable for how to tell the two reasons apart.
nt::AutoHandle mSectionHandle;
// Set when the hook did reach this load and tried to duplicate the section,
// but the duplication failed. This is used to distinguish that case from
// the case where mSectionHandle is null for the loads the hook deliberately
// skips (e.g. non-IMAGEs, low-integrity modules, etc). The distinction is
// recorded in telemetry.
bool mSectionHandleUnavailable;
// If the module was successfully loaded, stack trace of the DLL load request
Vector<PVOID, 0, nt::RtlAllocPolicy> mBacktrace;
// The status of DLL load