diff --git a/browser/app/winlauncher/freestanding/DllBlocklist.cpp b/browser/app/winlauncher/freestanding/DllBlocklist.cpp index 3ef076d867d0..782fd41668c1 100644 --- a/browser/app/winlauncher/freestanding/DllBlocklist.cpp +++ b/browser/app/winlauncher/freestanding/DllBlocklist.cpp @@ -403,8 +403,9 @@ CrossProcessDllInterceptor::FuncHookType // 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(); } diff --git a/browser/app/winlauncher/freestanding/ModuleLoadFrame.cpp b/browser/app/winlauncher/freestanding/ModuleLoadFrame.cpp index 5fa2967a62b6..b84de14d7ce0 100644 --- a/browser/app/winlauncher/freestanding/ModuleLoadFrame.cpp +++ b/browser/app/winlauncher/freestanding/ModuleLoadFrame.cpp @@ -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, diff --git a/browser/app/winlauncher/freestanding/ModuleLoadFrame.h b/browser/app/winlauncher/freestanding/ModuleLoadFrame.h index 6e9eb4dd2ac5..661a7d11db80 100644 --- a/browser/app/winlauncher/freestanding/ModuleLoadFrame.h +++ b/browser/app/winlauncher/freestanding/ModuleLoadFrame.h @@ -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 diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index 72146a14dd82..3d28b898e304 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -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); diff --git a/dom/media/gmp/GMPPlatform.h b/dom/media/gmp/GMPPlatform.h index 54b51bdfb0d9..7f45236d0f32 100644 --- a/dom/media/gmp/GMPPlatform.h +++ b/dom/media/gmp/GMPPlatform.h @@ -10,9 +10,6 @@ #include "mozilla/gmp/PGMPChild.h" namespace mozilla { -#ifdef XP_WIN -struct ModuleIdentifiers; -#endif namespace ipc { class ByteBuf; diff --git a/mozglue/misc/NativeNt.h b/mozglue/misc/NativeNt.h index 7bd8de7c57eb..1725240772ec 100644 --- a/mozglue/misc/NativeNt.h +++ b/mozglue/misc/NativeNt.h @@ -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; diff --git a/toolkit/xre/dllservices/ModuleVersionInfo.cpp b/toolkit/xre/dllservices/ModuleVersionInfo.cpp index 8cc86af40971..71e696701bcd 100644 --- a/toolkit/xre/dllservices/ModuleVersionInfo.cpp +++ b/toolkit/xre/dllservices/ModuleVersionInfo.cpp @@ -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 = diff --git a/toolkit/xre/dllservices/UntrustedModulesData.h b/toolkit/xre/dllservices/UntrustedModulesData.h index 4964b2c9b1ea..6826402676a8 100644 --- a/toolkit/xre/dllservices/UntrustedModulesData.h +++ b/toolkit/xre/dllservices/UntrustedModulesData.h @@ -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; - using VecType = Vector; - - Variant mModuleNtPaths; - - template - explicit ModuleIdentifiers(T&& aPaths) - : mModuleNtPaths(AsVariant(std::forward(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; class ProcessedModuleLoadEvent final { public: @@ -391,64 +375,6 @@ struct ParamTraits { } }; -template <> -struct ParamTraits { - 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(); - 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 { typedef mozilla::UntrustedModulesData paramType; diff --git a/toolkit/xre/dllservices/UntrustedModulesProcessor.cpp b/toolkit/xre/dllservices/UntrustedModulesProcessor.cpp index 6238f06901ac..71fe96af7d26 100644 --- a/toolkit/xre/dllservices/UntrustedModulesProcessor.cpp +++ b/toolkit/xre/dllservices/UntrustedModulesProcessor.cpp @@ -5,6 +5,8 @@ #include "UntrustedModulesProcessor.h" #include +#include +#include #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(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(path.BeginWriting()), + SE_FILE_OBJECT, LABEL_SECURITY_INFORMATION, + nullptr, nullptr, nullptr, &sacl, + &rawSd) != ERROR_SUCCESS) { + // Treat errors as untrustworthy. + return true; + } + + UniquePtr 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(rawAce); + if (header->AceType != SYSTEM_MANDATORY_LABEL_ACE_TYPE) { + continue; + } + + auto* labelAce = static_cast(rawAce); + auto* sid = reinterpret_cast(&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(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 moduleNtPathSet; + nsTHashtable 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 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 p( @@ -1009,7 +1183,7 @@ RefPtr 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 UntrustedModulesProcessor::GetModulesTrustInternal( @@ -1028,18 +1202,24 @@ RefPtr UntrustedModulesProcessor::GetModulesTrustInternal( return ModulesTrustPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); } - for (auto& resolvedNtPath : - aModIdents.mModuleNtPaths.as()) { + 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 module(GetOrAddModuleRecord(modEval, resolvedNtPath)); if (!module) { // We failed to obtain trust information. diff --git a/toolkit/xre/dllservices/UntrustedModulesProcessor.h b/toolkit/xre/dllservices/UntrustedModulesProcessor.h index 9fa456f663c2..04a9d7e73450 100644 --- a/toolkit/xre/dllservices/UntrustedModulesProcessor.h +++ b/toolkit/xre/dllservices/UntrustedModulesProcessor.h @@ -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, nsresult, true>; diff --git a/toolkit/xre/dllservices/WinDllServices.cpp b/toolkit/xre/dllservices/WinDllServices.cpp index 3178b28b29fb..4b886da04907 100644 --- a/toolkit/xre/dllservices/WinDllServices.cpp +++ b/toolkit/xre/dllservices/WinDllServices.cpp @@ -79,7 +79,7 @@ void DllServices::DisableFull() { } RefPtr DllServices::GetModulesTrust( - ModuleIdentifiers&& aModIdents, bool aRunAtNormalPriority) { + nsTArray&& aModIdents, bool aRunAtNormalPriority) { if (!mUntrustedModulesProcessor) { return ModulesTrustPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED, __func__); diff --git a/toolkit/xre/dllservices/WinDllServices.h b/toolkit/xre/dllservices/WinDllServices.h index cd0c7b096984..69acfad91b24 100644 --- a/toolkit/xre/dllservices/WinDllServices.h +++ b/toolkit/xre/dllservices/WinDllServices.h @@ -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, nsresult, true>; -struct ModuleIdentifiers; +namespace ipc { +class FileDescriptor; +} // namespace ipc + class ModulesMapResult; using ModulesTrustPromise = MozPromise; @@ -37,8 +41,8 @@ class DllServices final : public glue::DllServices { RefPtr GetUntrustedModulesData(); - RefPtr GetModulesTrust(ModuleIdentifiers&& aModIdents, - bool aRunAtNormalPriority); + RefPtr GetModulesTrust( + nsTArray&& aModIdents, bool aRunAtNormalPriority); private: DllServices() = default; diff --git a/toolkit/xre/dllservices/mozglue/ModuleLoadInfo.h b/toolkit/xre/dllservices/mozglue/ModuleLoadInfo.h index 5043e490b395..5a096b42fb08 100644 --- a/toolkit/xre/dllservices/mozglue/ModuleLoadInfo.h +++ b/toolkit/xre/dllservices/mozglue/ModuleLoadInfo.h @@ -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 mBacktrace; // The status of DLL load