diff --git a/browser/app/moz.build b/browser/app/moz.build index 9356538bc080..fcf98d6d48bf 100644 --- a/browser/app/moz.build +++ b/browser/app/moz.build @@ -81,6 +81,7 @@ if CONFIG["OS_ARCH"] == "WINNT": ] OS_LIBS += [ "advapi32", + "shell32", "uuid", ] DELAYLOAD_DLLS += [ @@ -88,6 +89,7 @@ if CONFIG["OS_ARCH"] == "WINNT": "oleaut32.dll", "ole32.dll", "rpcrt4.dll", + "shell32.dll", "version.dll", ] diff --git a/browser/app/winlauncher/DllBlocklistInit.cpp b/browser/app/winlauncher/DllBlocklistInit.cpp index 65b04ad28679..813189a49504 100644 --- a/browser/app/winlauncher/DllBlocklistInit.cpp +++ b/browser/app/winlauncher/DllBlocklistInit.cpp @@ -12,6 +12,7 @@ #include "mozilla/BinarySearch.h" #include "mozilla/ImportDir.h" #include "mozilla/NativeNt.h" +#include "mozilla/PolicyChecks.h" #include "mozilla/ScopeExit.h" #include "mozilla/Types.h" #include "mozilla/WindowsDllBlocklist.h" @@ -36,7 +37,9 @@ LauncherVoidResultWithLineInfo InitializeDllBlocklistOOP( } LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPFromLauncher( - const wchar_t* aFullImagePath, HANDLE aChildProcess) { + const wchar_t* aFullImagePath, HANDLE aChildProcess, + const bool aDisableDynamicBlocklist, + Maybe aBlocklistFileName) { return mozilla::Ok(); } @@ -187,7 +190,9 @@ LauncherVoidResultWithLineInfo InitializeDllBlocklistOOP( } LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPFromLauncher( - const wchar_t* aFullImagePath, HANDLE aChildProcess) { + const wchar_t* aFullImagePath, HANDLE aChildProcess, + const bool aDisableDynamicBlocklist, + Maybe aBlocklistFileName) { nt::CrossExecTransferManager transferMgr(aChildProcess); if (!transferMgr) { return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT); @@ -198,7 +203,17 @@ LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPFromLauncher( // the browser process is transferred to the sandbox processes. LauncherVoidResultWithLineInfo result = freestanding::gSharedSection.Init(); if (result.isErr()) { - return result.propagateErr(); + return result; + } + + if (aBlocklistFileName.isSome() && + !PolicyCheckBoolean(L"DisableThirdPartyModuleBlocking")) { + DynamicBlockList blockList(aBlocklistFileName->c_str()); + result = freestanding::gSharedSection.SetBlocklist( + blockList, aDisableDynamicBlocklist); + if (result.isErr()) { + return result; + } } // Transfer a writable handle to the main process because it needs to append diff --git a/browser/app/winlauncher/DllBlocklistInit.h b/browser/app/winlauncher/DllBlocklistInit.h index 0567bad7fc30..291283a7f64c 100644 --- a/browser/app/winlauncher/DllBlocklistInit.h +++ b/browser/app/winlauncher/DllBlocklistInit.h @@ -9,6 +9,9 @@ #include +#if defined(MOZ_LAUNCHER_PROCESS) +# include "mozilla/LauncherRegistryInfo.h" +#endif #include "mozilla/WinHeaderOnlyUtils.h" namespace mozilla { @@ -19,7 +22,9 @@ LauncherVoidResultWithLineInfo InitializeDllBlocklistOOP( const bool aIsSocketProcess); LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPFromLauncher( - const wchar_t* aFullImagePath, HANDLE aChildProcess); + const wchar_t* aFullImagePath, HANDLE aChildProcess, + const bool aDisableDynamicBlocklist, + Maybe aBlocklistFileName); } // namespace mozilla diff --git a/browser/app/winlauncher/LauncherProcessWin.cpp b/browser/app/winlauncher/LauncherProcessWin.cpp index 16e8c466e00d..b8124ea25fe5 100644 --- a/browser/app/winlauncher/LauncherProcessWin.cpp +++ b/browser/app/winlauncher/LauncherProcessWin.cpp @@ -15,6 +15,7 @@ #include "mozilla/DebugOnly.h" #include "mozilla/DynamicallyLinkedFunctionPtr.h" #include "mozilla/glue/Debug.h" +#include "mozilla/GeckoArgs.h" #include "mozilla/Maybe.h" #include "mozilla/SafeMode.h" #include "mozilla/UniquePtr.h" @@ -57,7 +58,8 @@ const volatile DeelevationStatus gDeelevationStatus = static mozilla::LauncherVoidResult PostCreationSetup( const wchar_t* aFullImagePath, HANDLE aChildProcess, HANDLE aChildMainThread, mozilla::DeelevationStatus aDStatus, - const bool aIsSafeMode) { + const bool aIsSafeMode, const bool aDisableDynamicBlocklist, + mozilla::Maybe aBlocklistFileName) { /* scope for txManager */ { mozilla::nt::CrossExecTransferManager txManager(aChildProcess); if (!txManager) { @@ -78,8 +80,9 @@ static mozilla::LauncherVoidResult PostCreationSetup( } } - return mozilla::InitializeDllBlocklistOOPFromLauncher(aFullImagePath, - aChildProcess); + return mozilla::InitializeDllBlocklistOOPFromLauncher( + aFullImagePath, aChildProcess, aDisableDynamicBlocklist, + aBlocklistFileName); } /** @@ -294,8 +297,14 @@ Maybe LauncherMain(int& argc, wchar_t* argv[], #if defined(MOZ_LAUNCHER_PROCESS) LauncherRegistryInfo regInfo; Maybe runAsLauncher = RunAsLauncherProcess(regInfo, argc, argv); + LauncherResult blocklistFileNameResult = + regInfo.GetBlocklistFileName(); + Maybe blocklistFileName = + blocklistFileNameResult.isOk() ? Some(blocklistFileNameResult.unwrap()) + : Nothing(); #else Maybe runAsLauncher = RunAsLauncherProcess(argc, argv); + Maybe blocklistFileName = Nothing(); #endif // defined(MOZ_LAUNCHER_PROCESS) if (!runAsLauncher || !runAsLauncher.value()) { #if defined(MOZ_LAUNCHER_PROCESS) @@ -486,9 +495,14 @@ Maybe LauncherMain(int& argc, wchar_t* argv[], job = CreateJobAndAssignProcess(process.get()); } - LauncherVoidResult setupResult = - PostCreationSetup(argv[0], process.get(), mainThread.get(), - deelevationStatus, isSafeMode.value()); + bool disableDynamicBlocklist = + isSafeMode.value() || + mozilla::CheckArg( + argc, argv, mozilla::geckoargs::sDisableDynamicDllBlocklist.sMatch, + nullptr, mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND; + LauncherVoidResult setupResult = PostCreationSetup( + argv[0], process.get(), mainThread.get(), deelevationStatus, + isSafeMode.value(), disableDynamicBlocklist, blocklistFileName); if (setupResult.isErr()) { HandleLauncherError(setupResult); ::TerminateProcess(process.get(), 1); diff --git a/browser/app/winlauncher/freestanding/DllBlocklist.cpp b/browser/app/winlauncher/freestanding/DllBlocklist.cpp index 19ee4d757ebb..69f795a2709b 100644 --- a/browser/app/winlauncher/freestanding/DllBlocklist.cpp +++ b/browser/app/winlauncher/freestanding/DllBlocklist.cpp @@ -230,18 +230,6 @@ static BlockAction CheckBlockInfo(const DllBlockInfo* aInfo, return BlockAction::Allow; } -struct DllBlockInfoComparator { - explicit DllBlockInfoComparator(const UNICODE_STRING& aTarget) - : mTarget(&aTarget) {} - - int operator()(const DllBlockInfo& aVal) const { - return static_cast( - ::RtlCompareUnicodeString(mTarget, &aVal.mName, TRUE)); - } - - PCUNICODE_STRING mTarget; -}; - static BOOL WINAPI NoOp_DllMain(HINSTANCE, DWORD, LPVOID) { return TRUE; } // This helper function checks whether a given module is included @@ -308,26 +296,39 @@ static BlockAction DetermineBlockAction( DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY(info); DECLARE_DLL_BLOCKLIST_NUM_ENTRIES(infoNumEntries); - DllBlockInfoComparator comp(aLeafName); + mozilla::freestanding::DllBlockInfoComparator comp(aLeafName); size_t match; - if (!BinarySearchIf(info, 0, infoNumEntries, comp, &match)) { + bool onBuiltinList = BinarySearchIf(info, 0, infoNumEntries, comp, &match); + const DllBlockInfo* entry = nullptr; + mozilla::nt::PEHeaders headers(aBaseAddress); + uint64_t version; + BlockAction checkResult = BlockAction::Allow; + if (onBuiltinList) { + entry = &info[match]; + checkResult = CheckBlockInfo(entry, headers, version); + } + mozilla::DebugOnly blockedByDynamicBlocklist = false; + // Make sure we handle a case that older versions are blocked by the static + // list, but the dynamic list blocks all versions. + if (checkResult == BlockAction::Allow) { + if (!mozilla::freestanding::gSharedSection.IsDisabled()) { + entry = mozilla::freestanding::gSharedSection.SearchBlocklist(aLeafName); + if (entry) { + checkResult = CheckBlockInfo(entry, headers, version); + blockedByDynamicBlocklist = checkResult != BlockAction::Allow; + } + } + } + if (checkResult == BlockAction::Allow) { return BlockAction::Allow; } - const DllBlockInfo& entry = info[match]; + gBlockSet.Add(entry->mName, version); - mozilla::nt::PEHeaders headers(aBaseAddress); - uint64_t version; - BlockAction checkResult = CheckBlockInfo(&entry, headers, version); - if (checkResult == BlockAction::Allow) { - return checkResult; - } - - gBlockSet.Add(entry.mName, version); - - if ((entry.mFlags & DllBlockInfo::REDIRECT_TO_NOOP_ENTRYPOINT) && + if ((entry->mFlags & DllBlockInfo::REDIRECT_TO_NOOP_ENTRYPOINT) && aK32Exports && RedirectToNoOpEntryPoint(headers, *aK32Exports)) { + MOZ_ASSERT(!blockedByDynamicBlocklist, "dynamic blocklist has redirect?"); return BlockAction::NoOpEntryPoint; } diff --git a/browser/app/winlauncher/freestanding/SharedSection.cpp b/browser/app/winlauncher/freestanding/SharedSection.cpp index cb326288650d..9349a9ae7c7d 100644 --- a/browser/app/winlauncher/freestanding/SharedSection.cpp +++ b/browser/app/winlauncher/freestanding/SharedSection.cpp @@ -6,7 +6,9 @@ #include "SharedSection.h" +#include #include "CheckForCaller.h" +#include "mozilla/BinarySearch.h" namespace { @@ -167,6 +169,8 @@ LauncherVoidResult SharedSection::Init() { Layout* view = writableView.as(); view->mK32Exports.Init(); view->mState = Layout::State::kInitialized; + // Leave view->mDependentModulePathArrayStart to be zero to indicate + // we can add blocklist entries return Ok(); } @@ -177,13 +181,58 @@ LauncherVoidResult SharedSection::AddDependentModule(PCUNICODE_STRING aNtPath) { } Layout* view = writableView.as(); - if (!AddString(view->GetModulePathArray(), *aNtPath)) { + if (!view->mDependentModulePathArrayStart) { + // This is the first time AddDependentModule is called. We set the initial + // value to mDependentModulePathArrayStart, which *closes* the blocklist. + // After this, AddBlocklist is no longer allowed. + view->mDependentModulePathArrayStart = + FIELD_OFFSET(Layout, mFirstBlockEntry) + sizeof(DllBlockInfo); + } + + if (!AddString(view->GetDependentModules(), *aNtPath)) { return LAUNCHER_ERROR_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); } return Ok(); } +LauncherVoidResult SharedSection::SetBlocklist( + const DynamicBlockList& aBlocklist, bool isDisabled) { + if (!aBlocklist.GetPayloadSize()) { + return Ok(); + } + + nt::AutoMappedView writableView(sSectionHandle, PAGE_READWRITE); + if (!writableView) { + return LAUNCHER_ERROR_FROM_WIN32(::RtlGetLastWin32Error()); + } + + Layout* view = writableView.as(); + if (view->mDependentModulePathArrayStart > 0) { + // If the dependent module array is already available, we must not update + // the blocklist. + return LAUNCHER_ERROR_FROM_WIN32(ERROR_INVALID_STATE); + } + + view->mBlocklistIsDisabled = isDisabled ? 1 : 0; + uintptr_t bufferEnd = reinterpret_cast(view) + kSharedViewSize; + size_t bytesCopied = aBlocklist.CopyTo( + view->mFirstBlockEntry, + bufferEnd - reinterpret_cast(view->mFirstBlockEntry)); + if (!bytesCopied) { + return LAUNCHER_ERROR_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + // Setting mDependentModulePathArrayStart to a non-zero value means + // we no longer accept blocklist entries + // Just to be safe, make sure we don't overwrite mFirstBlockEntry even + // if there are no entries. + view->mDependentModulePathArrayStart = + FIELD_OFFSET(Layout, mFirstBlockEntry) + + std::max(bytesCopied, sizeof(DllBlockInfo)); + return Ok(); +} + /* static */ ULONG NTAPI SharedSection::EnsureWriteCopyViewOnce(PRTL_RUN_ONCE, PVOID, PVOID*) { @@ -219,20 +268,78 @@ bool SharedSection::Layout::Resolve() { return false; } + if (!mNumBlockEntries) { + uintptr_t arrayBase = reinterpret_cast(mFirstBlockEntry); + uint32_t numEntries = 0; + for (DllBlockInfo* entry = mFirstBlockEntry; + entry->mName.Length && numEntries < GetMaxNumBlockEntries(); ++entry) { + entry->mName.Buffer = reinterpret_cast( + arrayBase + reinterpret_cast(entry->mName.Buffer)); + ++numEntries; + } + mNumBlockEntries = numEntries; + // Sort by name so that we can binary-search + std::sort(mFirstBlockEntry, mFirstBlockEntry + mNumBlockEntries, + [](const DllBlockInfo& a, const DllBlockInfo& b) { + return ::RtlCompareUnicodeString(&a.mName, &b.mName, TRUE) < 0; + }); + } + mState = State::kResolved; return true; } +Span SharedSection::Layout::GetDependentModules() { + if (!mDependentModulePathArrayStart) { + return nullptr; + } + return Span( + reinterpret_cast(reinterpret_cast(this) + + mDependentModulePathArrayStart), + (kSharedViewSize - mDependentModulePathArrayStart) / sizeof(wchar_t)); +} + +bool SharedSection::Layout::IsDisabled() const { + return !!mBlocklistIsDisabled; +} + +const DllBlockInfo* SharedSection::Layout::SearchBlocklist( + const UNICODE_STRING& aLeafName) const { + MOZ_ASSERT(mState == State::kResolved); + DllBlockInfoComparator comp(aLeafName); + size_t match; + if (!BinarySearchIf(mFirstBlockEntry, 0, mNumBlockEntries, comp, &match)) { + return nullptr; + } + return &mFirstBlockEntry[match]; +} + Kernel32ExportsSolver* SharedSection::GetKernel32Exports() { Layout* writeCopyView = EnsureWriteCopyView(); return writeCopyView ? &writeCopyView->mK32Exports : nullptr; } Span SharedSection::GetDependentModules() { + Layout* writeCopyView = EnsureWriteCopyView(); + return writeCopyView ? writeCopyView->GetDependentModules() : nullptr; +} + +Span SharedSection::GetDynamicBlocklist() { Layout* writeCopyView = EnsureWriteCopyView(); return writeCopyView ? writeCopyView->GetModulePathArray() : nullptr; } +const DllBlockInfo* SharedSection::SearchBlocklist( + const UNICODE_STRING& aLeafName) { + Layout* writeCopyView = EnsureWriteCopyView(); + return writeCopyView ? writeCopyView->SearchBlocklist(aLeafName) : nullptr; +} + +bool SharedSection::IsDisabled() { + Layout* writeCopyView = EnsureWriteCopyView(); + return writeCopyView ? writeCopyView->IsDisabled() : false; +} + LauncherVoidResult SharedSection::TransferHandle( nt::CrossExecTransferManager& aTransferMgr, DWORD aDesiredAccess, HANDLE* aDestinationAddress) { diff --git a/browser/app/winlauncher/freestanding/SharedSection.h b/browser/app/winlauncher/freestanding/SharedSection.h index b578ef914f72..9d07201827bf 100644 --- a/browser/app/winlauncher/freestanding/SharedSection.h +++ b/browser/app/winlauncher/freestanding/SharedSection.h @@ -7,6 +7,7 @@ #ifndef mozilla_freestanding_SharedSection_h #define mozilla_freestanding_SharedSection_h +#include "mozilla/DynamicBlocklist.h" #include "mozilla/glue/SharedSection.h" #include "mozilla/NativeNt.h" #include "mozilla/interceptor/MMPolicies.h" @@ -27,6 +28,18 @@ namespace mozilla { namespace freestanding { class SharedSectionTestHelper; +struct DllBlockInfoComparator { + explicit DllBlockInfoComparator(const UNICODE_STRING& aTarget) + : mTarget(&aTarget) {} + + int operator()(const DllBlockInfo& aVal) const { + return static_cast( + ::RtlCompareUnicodeString(mTarget, &aVal.mName, TRUE)); + } + + PCUNICODE_STRING mTarget; +}; + // This class calculates RVAs of kernel32's functions and transfers them // to a target process, where the transferred RVAs are resolved into // function addresses so that the target process can use them after @@ -44,9 +57,21 @@ struct MOZ_TRIVIAL_CTOR_DTOR Kernel32ExportsSolver final // (1) Kernel32's functions required for MMPolicyInProcessEarlyStage // Formatted as Kernel32ExportsSolver. // -// (2) Array of NT paths of the executable's dependent modules +// (2) Various flags and offsets +// +// (3) Entries in the dynamic blocklist, in DllBlockInfo format. There +// are mNumBlockEntries of these, followed by one that has mName.Length +// of 0. Note that the strings that contain +// the names of the entries in the blocklist are stored concatenated +// after the last entry. The mName pointers in each DllBlockInfo point +// to these strings correctly in Resolve(), so clients don't need +// to do anything special to read these strings. +// +// (4) Array of NT paths of the executable's dependent modules // Formatted as a null-delimited wide-character string set ending with -// an empty string. +// an empty string. These entries start at offset +// mDependentModulePathArrayStart (in bytes) from the beginning +// of the structure // // +--------------------------------------------------------------+ // | (1) | FlushInstructionCache | @@ -55,7 +80,16 @@ struct MOZ_TRIVIAL_CTOR_DTOR Kernel32ExportsSolver final // | | VirtualProtect | // | | State [kUninitialized|kInitialized|kResolved] | // +--------------------------------------------------------------+ -// | (2) | L"NT path 1" | +// | (2) | (flags and offsets) | +// +--------------------------------------------------------------+ +// | (3) | | +// | | | +// | | ... | +// | | | +// | | | +// | | L"string1.dllstring2.dll...stringlast.dll" | +// +--------------------------------------------------------------+ +// | (4) | L"NT path 1" | // | | L"NT path 2" | // | | ... | // | | L"" | @@ -69,17 +103,42 @@ class MOZ_TRIVIAL_CTOR_DTOR SharedSection final : public nt::SharedSection { } mState; Kernel32ExportsSolver mK32Exports; - wchar_t mModulePathArray[1]; + // 1 if the blocklist is disabled, 0 otherwise. + // If the blocklist is disabled, the entries are still loaded to make it + // easy for the user to remove any they don't want, but none of the DLLs + // here are actually blocked. + // Stored as a uint32_t for alignment reasons. + uint32_t mBlocklistIsDisabled; + // The offset, in bytes, from the beginning of the Layout structure to the + // first dependent module entry. + // When the Layout object is created, this value is 0, indicating that no + // dependent modules have been added and it is safe to add DllBlockInfo + // entries. + // After this value is set to something non-0, no more DllBlockInfo entries + // can be added. + uint32_t mDependentModulePathArrayStart; + // The number of blocklist entries. + uint32_t mNumBlockEntries; + DllBlockInfo mFirstBlockEntry[1]; - Span GetModulePathArray() { - return Span( - mModulePathArray, - (kSharedViewSize - (reinterpret_cast(mModulePathArray) - + Span GetModulePathArray() { + return Span( + mFirstBlockEntry, + (kSharedViewSize - (reinterpret_cast(mFirstBlockEntry) - reinterpret_cast(this))) / - sizeof(wchar_t)); + sizeof(DllBlockInfo)); + } + // Can be used to make sure we don't step past the end of the shared memory + // section. + static constexpr uint32_t GetMaxNumBlockEntries() { + return (kSharedViewSize - (offsetof(Layout, mFirstBlockEntry))) / + sizeof(DllBlockInfo); } Layout() = delete; // disallow instantiation bool Resolve(); + bool IsDisabled() const; + const DllBlockInfo* SearchBlocklist(const UNICODE_STRING& aLeafName) const; + Span GetDependentModules(); }; // As we define a global variable of this class and use it in our blocklist @@ -113,11 +172,17 @@ class MOZ_TRIVIAL_CTOR_DTOR SharedSection final : public nt::SharedSection { // Append a new string to the |sSectionHandle| static LauncherVoidResult AddDependentModule(PCUNICODE_STRING aNtPath); + static LauncherVoidResult SetBlocklist(const DynamicBlockList& aBlocklist, + bool isDisabled); // Map |sSectionHandle| to a copy-on-write page and return a writable pointer // to each structure, or null if Layout failed to resolve exports. Kernel32ExportsSolver* GetKernel32Exports(); Span GetDependentModules() final override; + Span GetDynamicBlocklist() final override; + + static bool IsDisabled(); + static const DllBlockInfo* SearchBlocklist(const UNICODE_STRING& aLeafName); // Transfer |sSectionHandle| to a process associated with |aTransferMgr|. static LauncherVoidResult TransferHandle( diff --git a/browser/app/winlauncher/moz.build b/browser/app/winlauncher/moz.build index a571fbd686e9..1b6fd0b9ab4f 100644 --- a/browser/app/winlauncher/moz.build +++ b/browser/app/winlauncher/moz.build @@ -40,7 +40,12 @@ TEST_DIRS += [ ] if CONFIG["MOZ_LAUNCHER_PROCESS"]: + LOCAL_INCLUDES += [ + "/other-licenses/nsis/Contrib/CityHash/cityhash", + ] UNIFIED_SOURCES += [ + "/other-licenses/nsis/Contrib/CityHash/cityhash/city.cpp", + "/toolkit/mozapps/update/common/commonupdatedir.cpp", "/toolkit/xre/LauncherRegistryInfo.cpp", ] diff --git a/browser/app/winlauncher/test/TestCrossProcessWin.cpp b/browser/app/winlauncher/test/TestCrossProcessWin.cpp index afe094edfb3c..c6f9b23ebd8a 100644 --- a/browser/app/winlauncher/test/TestCrossProcessWin.cpp +++ b/browser/app/winlauncher/test/TestCrossProcessWin.cpp @@ -4,15 +4,25 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#include +#include + #define MOZ_USE_LAUNCHER_ERROR #include #include #include "freestanding/SharedSection.cpp" #include "mozilla/CmdLineAndEnvUtils.h" +#include "mozilla/DynamicBlocklist.h" #include "mozilla/NativeNt.h" #include "mozilla/Vector.h" +#define DLL_BLOCKLIST_ENTRY(name, ...) \ + {MOZ_LITERAL_UNICODE_STRING(L##name), __VA_ARGS__}, +#define DLL_BLOCKLIST_STRING_TYPE UNICODE_STRING + +#include "mozilla/WindowsDllBlocklistLauncherDefs.h" + const wchar_t kChildArg[] = L"--child"; const char* kTestDependentModulePaths[] = { "\\Device\\HarddiskVolume4\\Windows\\system32\\A B C", @@ -30,6 +40,25 @@ const wchar_t kExpectedDependentModules[] = L"A B C.exe\0" L"X Y Z.dll\0"; +const UNICODE_STRING kStringNotInBlocklist = + MOZ_LITERAL_UNICODE_STRING(L"Test_NotInBlocklist.dll"); +const UNICODE_STRING kTestDependentModuleString = + MOZ_LITERAL_UNICODE_STRING(L"Test_DependentModule.dll"); + +// clang-format off +const DllBlockInfo kDllBlocklistShort[] = { + // The entries do not have to be sorted. + DLL_BLOCKLIST_ENTRY("X Y Z_Test", MAKE_VERSION(1, 2, 65535, 65535), + DllBlockInfo::BLOCK_WIN8_AND_OLDER) + DLL_BLOCKLIST_ENTRY("\u30E9\u30FC\u30E1\u30F3_Test") + DLL_BLOCKLIST_ENTRY("Avmvirtualsource_Test.ax", MAKE_VERSION(1, 0, 0, 3), + DllBlockInfo::BROWSER_PROCESS_ONLY) + DLL_BLOCKLIST_ENTRY("1ccelerator_Test.dll", MAKE_VERSION(3, 2, 1, 6)) + DLL_BLOCKLIST_ENTRY("atkdx11disp_Test.dll", DllBlockInfo::ALL_VERSIONS) + {}, +}; +// clang-format on + using namespace mozilla; using namespace mozilla::freestanding; @@ -38,11 +67,32 @@ class SharedSectionTestHelper { public: static constexpr size_t GetModulePathArraySize() { return SharedSection::kSharedViewSize - - offsetof(SharedSection::Layout, mModulePathArray); + (offsetof(SharedSection::Layout, mFirstBlockEntry) + + sizeof(DllBlockInfo)); } }; } // namespace mozilla::freestanding +class TempFile final { + wchar_t mFullPath[MAX_PATH + 1]; + + public: + TempFile() : mFullPath{0} { + wchar_t tempDir[MAX_PATH + 1]; + DWORD len = ::GetTempPathW(ArrayLength(tempDir), tempDir); + if (!len) { + return; + } + + len = ::GetTempFileNameW(tempDir, L"blocklist", 0, mFullPath); + if (!len) { + return; + } + } + + operator const wchar_t*() const { return mFullPath[0] ? mFullPath : nullptr; } +}; + template void PrintLauncherError(const LauncherResult& aResult, const char (&aMsg)[N]) { @@ -91,6 +141,27 @@ static bool VerifySharedSection(SharedSection& aSharedSection) { return false; } + for (const DllBlockInfo* info = kDllBlocklistShort; info->mName.Buffer; + ++info) { + const DllBlockInfo* matched = aSharedSection.SearchBlocklist(info->mName); + if (!matched) { + printf( + "TEST-FAILED | TestCrossProcessWin | No blocklist entry match for " + "entry in blocklist.\n"); + return false; + } + } + + if (aSharedSection.SearchBlocklist(kStringNotInBlocklist)) { + printf( + "TEST-FAILED | TestCrossProcessWin | Found blocklist entry match for " + "something not in the blocklist.\n"); + } + + if (aSharedSection.IsDisabled()) { + printf("TEST-FAILED | TestCrossProcessWin | Wrong disabled value.\n"); + } + return true; } @@ -127,7 +198,91 @@ static bool TestAddString() { return true; } -static bool TestSharedSectionInit() { +// Convert |aBlockEntries|, which is an array ending with an empty instance +// of DllBlockInfo, to DynamicBlockList by storing it to a temp file, loading +// as DynamicBlockList, and deleting the temp file. +static DynamicBlockList ConvertStaticBlocklistToDynamic( + const DllBlockInfo aBlockEntries[]) { + size_t originalLength = 0; + CheckedUint32 totalStringLen = 0; + for (const DllBlockInfo* entry = aBlockEntries; entry->mName.Length; + ++entry) { + totalStringLen += entry->mName.Length; + MOZ_RELEASE_ASSERT(totalStringLen.isValid()); + ++originalLength; + } + + // Pack all strings in this buffer without null characters + UniquePtr stringBuffer = + MakeUnique(totalStringLen.value()); + + // The string buffer is placed immediately after the array of DllBlockInfo + const size_t stringBufferOffset = (originalLength + 1) * sizeof(DllBlockInfo); + + // Entries in the dynamic blocklist do have to be sorted, + // unlike in the static blocklist. + UniquePtr sortedBlockEntries = + MakeUnique(originalLength); + memcpy(sortedBlockEntries.get(), aBlockEntries, + sizeof(DllBlockInfo) * originalLength); + std::sort(sortedBlockEntries.get(), sortedBlockEntries.get() + originalLength, + [](const DllBlockInfo& a, const DllBlockInfo& b) { + return ::RtlCompareUnicodeString(&a.mName, &b.mName, TRUE) < 0; + }); + + Vector copied; + Unused << copied.resize(originalLength + 1); // aBlockEntries + sentinel + + size_t currentStringOffset = 0; + for (size_t i = 0; i < originalLength; ++i) { + copied[i].mMaxVersion = sortedBlockEntries[i].mMaxVersion; + copied[i].mFlags = sortedBlockEntries[i].mFlags; + + // Copy the module's name to the string buffer and store its offset + // in mName.Buffer + memcpy(stringBuffer.get() + currentStringOffset, + sortedBlockEntries[i].mName.Buffer, + sortedBlockEntries[i].mName.Length); + copied[i].mName.Buffer = + reinterpret_cast(stringBufferOffset + currentStringOffset); + // Only keep mName.Length and leave mName.MaximumLength to be zero + copied[i].mName.Length = sortedBlockEntries[i].mName.Length; + + currentStringOffset += sortedBlockEntries[i].mName.Length; + } + + TempFile blocklistFile; + nsAutoHandle file(::CreateFileW(blocklistFile, GENERIC_WRITE, FILE_SHARE_READ, + nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, + nullptr)); + MOZ_RELEASE_ASSERT(file); + + DynamicBlockListBase::FileHeader header; + header.mSignature = DynamicBlockListBase::kSignature; + header.mFileVersion = DynamicBlockListBase::kCurrentVersion; + header.mPayloadSize = + sizeof(DllBlockInfo) * copied.length() + totalStringLen.value(); + + DWORD written = 0; + MOZ_RELEASE_ASSERT( + ::WriteFile(file.get(), &header, sizeof(header), &written, nullptr)); + MOZ_RELEASE_ASSERT(::WriteFile(file.get(), copied.begin(), + sizeof(DllBlockInfo) * copied.length(), + &written, nullptr)); + MOZ_RELEASE_ASSERT(::WriteFile(file.get(), stringBuffer.get(), + totalStringLen.value(), &written, nullptr)); + + DynamicBlockList blockList(blocklistFile); + ::DeleteFileW(blocklistFile); + return blockList; +} + +const DynamicBlockList gFullList = + ConvertStaticBlocklistToDynamic(gWindowsDllBlocklist); +const DynamicBlockList gShortList = + ConvertStaticBlocklistToDynamic(kDllBlocklistShort); + +static bool TestDependentModules() { LauncherVoidResult result = gSharedSection.Init(); if (result.isErr()) { PrintLauncherError(result, "SharedSection::Init failed"); @@ -146,15 +301,14 @@ static bool TestSharedSectionInit() { ustr.Length = ustr.MaximumLength = buffer.size(); result = gSharedSection.AddDependentModule(&ustr); - if (result.isOk()) { + if (result.isOk() || result.inspectErr() != WindowsError::FromWin32Error( + ERROR_INSUFFICIENT_BUFFER)) { printf( "TEST-FAILED | TestCrossProcessWin | " "Adding a too long string should fail.\n"); return false; } - gSharedSection.Reset(); - result = gSharedSection.Init(); if (result.isErr()) { PrintLauncherError(result, "SharedSection::Init failed"); @@ -191,6 +345,85 @@ static bool TestSharedSectionInit() { return false; } + // SetBlocklist is not allowed after AddDependentModule + result = gSharedSection.SetBlocklist(gShortList, false); + if (result.isOk() || result.inspectErr() != + WindowsError::FromWin32Error(ERROR_INVALID_STATE)) { + printf( + "TEST-FAILED | TestCrossProcessWin | " + "SetBlocklist is not allowed after AddDependentModule\n"); + return false; + } + + gSharedSection.Reset(); + return true; +} + +static bool TestDynamicBlocklist() { + if (!gFullList.GetPayloadSize() || !gShortList.GetPayloadSize()) { + printf( + "TEST-FAILED | TestCrossProcessWin | DynamicBlockList::LoadFile " + "failed\n"); + return false; + } + + LauncherVoidResult result = gSharedSection.Init(); + if (result.isErr()) { + PrintLauncherError(result, "SharedSection::Init failed"); + return false; + } + + // Set gShortList, and gShortList + // 1. Setting gShortList succeeds + // 2. Next try to set gShortList fails + result = gSharedSection.SetBlocklist(gShortList, false); + if (result.isErr()) { + PrintLauncherError(result, "SetBlocklist(gShortList) failed"); + return false; + } + result = gSharedSection.SetBlocklist(gShortList, false); + if (result.isOk() || result.inspectErr() != + WindowsError::FromWin32Error(ERROR_INVALID_STATE)) { + printf( + "TEST-FAILED | TestCrossProcessWin | " + "SetBlocklist is allowed only once\n"); + return false; + } + + result = gSharedSection.Init(); + if (result.isErr()) { + PrintLauncherError(result, "SharedSection::Init failed"); + return false; + } + + // Add gFullList and gShortList + // 1. Adding gFullList always fails because it doesn't fit the section + // 2. Adding gShortList succeeds because no entry is added yet + MOZ_RELEASE_ASSERT( + gFullList.GetPayloadSize() > + SharedSectionTestHelper::GetModulePathArraySize(), + "Test assumes gFullList is too big to fit in shared section"); + result = gSharedSection.SetBlocklist(gFullList, false); + if (result.isOk() || result.inspectErr() != WindowsError::FromWin32Error( + ERROR_INSUFFICIENT_BUFFER)) { + printf( + "TEST-FAILED | TestCrossProcessWin | " + "SetBlocklist(gFullList) should fail\n"); + return false; + } + result = gSharedSection.SetBlocklist(gShortList, false); + if (result.isErr()) { + PrintLauncherError(result, "SetBlocklist(gShortList) failed"); + return false; + } + + // AddDependentModule is allowed after SetBlocklist + result = gSharedSection.AddDependentModule(&kTestDependentModuleString); + if (result.isErr()) { + PrintLauncherError(result, "SharedSection::AddDependentModule failed"); + return false; + } + gSharedSection.Reset(); return true; } @@ -272,11 +505,9 @@ class ChildProcess final { // Test a scenario to transfer a transferred section as a readonly handle gSharedSection.ConvertToReadOnly(); - UNICODE_STRING ustr; - ::RtlInitUnicodeString(&ustr, L"test"); - LauncherVoidResult result = gSharedSection.AddDependentModule(&ustr); - // AddDependentModule fails as the handle is readonly. + LauncherVoidResult result = + gSharedSection.AddDependentModule(&kTestDependentModuleString); if (result.inspectErr() != WindowsError::FromWin32Error(ERROR_ACCESS_DENIED)) { PrintLauncherError(result, "The readonly section was writable"); @@ -369,7 +600,11 @@ int wmain(int argc, wchar_t* argv[]) { return 1; } - if (!TestSharedSectionInit()) { + if (!TestDependentModules()) { + return 1; + } + + if (!TestDynamicBlocklist()) { return 1; } @@ -429,6 +664,12 @@ int wmain(int argc, wchar_t* argv[]) { return 1; } + result = gSharedSection.SetBlocklist(gShortList, false); + if (result.isErr()) { + PrintLauncherError(result, "SetBlocklist(gShortList) failed"); + return false; + } + for (const char* testString : kTestDependentModulePaths) { // Test AllocatedUnicodeString(const char*) that is used // in IsDependentModule() diff --git a/ipc/glue/GeckoChildProcessHost.cpp b/ipc/glue/GeckoChildProcessHost.cpp index 206a148d967a..ed6b7f6b6eec 100644 --- a/ipc/glue/GeckoChildProcessHost.cpp +++ b/ipc/glue/GeckoChildProcessHost.cpp @@ -1372,6 +1372,14 @@ bool WindowsProcessLauncher::DoSetup() { mCmdLine->AppendLooseValue(UTF8ToWide("-contentproc")); } +# ifdef HAS_DLL_BLOCKLIST + if (CommandLine::ForCurrentProcess()->HasSwitch( + UTF8ToWide(mozilla::geckoargs::sDisableDynamicDllBlocklist.sMatch))) { + mCmdLine->AppendLooseValue( + UTF8ToWide(mozilla::geckoargs::sDisableDynamicDllBlocklist.sMatch)); + } +# endif // HAS_DLL_BLOCKLIST + mCmdLine->AppendSwitchWithValue(switches::kProcessChannelID, mChannelId); for (std::vector::iterator it = mExtraOpts.begin(); diff --git a/toolkit/xre/GeckoArgs.h b/toolkit/xre/GeckoArgs.h index 0d1bb0a5ccaf..7db63c430159 100644 --- a/toolkit/xre/GeckoArgs.h +++ b/toolkit/xre/GeckoArgs.h @@ -138,7 +138,9 @@ static CommandLineArg sWin32kLockedDown{"-win32kLockedDown", static CommandLineArg sA11yResourceId{"-a11yResourceId", "a11yresourceid"}; # endif // defined(ACCESSIBILITY) -#endif // defined(XP_WIN) && defined(ACCESSIBILITY) +static CommandLineArg sDisableDynamicDllBlocklist{ + "-disableDynamicBlocklist", "disabledynamicblocklist"}; +#endif // defined(XP_WIN) #if defined(__GNUC__) # pragma GCC diagnostic pop diff --git a/toolkit/xre/LauncherRegistryInfo.cpp b/toolkit/xre/LauncherRegistryInfo.cpp index 6778827fecb7..e85ae4852e5a 100644 --- a/toolkit/xre/LauncherRegistryInfo.cpp +++ b/toolkit/xre/LauncherRegistryInfo.cpp @@ -6,11 +6,14 @@ #include "LauncherRegistryInfo.h" -#include "mozilla/UniquePtr.h" +#include "commonupdatedir.h" #include "mozilla/ArrayUtils.h" #include "mozilla/Assertions.h" #include "mozilla/NativeNt.h" +#include "mozilla/UniquePtr.h" +#include +#include #include #define EXPAND_STRING_MACRO2(t) t @@ -66,6 +69,45 @@ static mozilla::LauncherResult> ReadRegistryValueData( return mozilla::Some(data); } +static mozilla::LauncherResult> +ReadRegistryValueString(const nsAutoRegKey& aKey, const std::wstring& aName) { + mozilla::UniquePtr buf; + DWORD dataLen; + LSTATUS status = ::RegGetValueW(aKey.get(), nullptr, aName.c_str(), + RRF_RT_REG_SZ, nullptr, nullptr, &dataLen); + if (status == ERROR_FILE_NOT_FOUND) { + return buf; + } + + if (status != ERROR_SUCCESS) { + return LAUNCHER_ERROR_FROM_WIN32(status); + } + + buf = mozilla::MakeUnique(dataLen / sizeof(wchar_t)); + + status = ::RegGetValueW(aKey.get(), nullptr, aName.c_str(), RRF_RT_REG_SZ, + nullptr, buf.get(), &dataLen); + if (status != ERROR_SUCCESS) { + return LAUNCHER_ERROR_FROM_WIN32(status); + } + + return buf; +} + +static mozilla::LauncherVoidResult WriteRegistryValueString( + const nsAutoRegKey& aKey, const std::wstring& aName, + const std::wstring& aValue) { + DWORD dataBytes = (aValue.size() + 1) * sizeof(wchar_t); + LSTATUS status = ::RegSetValueExW( + aKey.get(), aName.c_str(), /*Reserved*/ 0, REG_SZ, + reinterpret_cast(aValue.c_str()), dataBytes); + if (status != ERROR_SUCCESS) { + return LAUNCHER_ERROR_FROM_WIN32(status); + } + + return mozilla::Ok(); +} + template static mozilla::LauncherVoidResult WriteRegistryValueData( const nsAutoRegKey& key, const std::wstring& name, DWORD type, T data) { @@ -104,6 +146,7 @@ const wchar_t LauncherRegistryInfo::kLauncherSuffix[] = L"|Launcher"; const wchar_t LauncherRegistryInfo::kBrowserSuffix[] = L"|Browser"; const wchar_t LauncherRegistryInfo::kImageTimestampSuffix[] = L"|Image"; const wchar_t LauncherRegistryInfo::kTelemetrySuffix[] = L"|Telemetry"; +const wchar_t LauncherRegistryInfo::kBlocklistSuffix[] = L"|Blocklist"; bool LauncherRegistryInfo::sAllowCommit = true; @@ -461,6 +504,16 @@ const std::wstring& LauncherRegistryInfo::ResolveTelemetryValueName() { return mTelemetryValueName; } +const std::wstring& LauncherRegistryInfo::ResolveBlocklistValueName() { + if (mBlocklistValueName.empty()) { + mBlocklistValueName.assign(mBinPath); + mBlocklistValueName.append(kBlocklistSuffix, + ArrayLength(kBlocklistSuffix) - 1); + } + + return mBlocklistValueName; +} + LauncherVoidResult LauncherRegistryInfo::WriteLauncherStartTimestamp( uint64_t aValue) { return WriteRegistryValueData(mRegKey, ResolveLauncherValueName(), REG_QWORD, @@ -530,4 +583,69 @@ LauncherRegistryInfo::GetBrowserStartTimestamp() { REG_QWORD); } +LauncherResult +LauncherRegistryInfo::BuildDefaultBlocklistFilename() { + // These flags are chosen to avoid I/O, see bug 1363398. + constexpr DWORD flags = + KF_FLAG_SIMPLE_IDLIST | KF_FLAG_DONT_VERIFY | KF_FLAG_NO_ALIAS; + PWSTR rawPath = nullptr; + HRESULT hr = + ::SHGetKnownFolderPath(FOLDERID_RoamingAppData, flags, nullptr, &rawPath); + if (FAILED(hr)) { + ::CoTaskMemFree(rawPath); + return LAUNCHER_ERROR_FROM_HRESULT(hr); + } + + UniquePtr appDataPath(rawPath); + std::wstring defaultBlocklistPath(appDataPath.get()); + + UniquePtr hash; + std::wstring binPathLower; + binPathLower.reserve(mBinPath.size()); + std::transform(mBinPath.begin(), mBinPath.end(), + std::back_inserter(binPathLower), std::towlower); + if (!::GetInstallHash(reinterpret_cast(binPathLower.c_str()), + hash)) { + return LAUNCHER_ERROR_FROM_WIN32(ERROR_INVALID_DATA); + } + + defaultBlocklistPath.append( + L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\blocklist-"); + defaultBlocklistPath.append(hash.get()); + + return defaultBlocklistPath; +} + +LauncherResult LauncherRegistryInfo::GetBlocklistFileName() { + LauncherResult disposition = Open(); + if (disposition.isErr()) { + return disposition.propagateErr(); + } + + LauncherResult> readResult = + ReadRegistryValueString(mRegKey, ResolveBlocklistValueName()); + if (readResult.isErr()) { + return readResult.propagateErr(); + } + + if (readResult.inspect()) { + UniquePtr buf = readResult.unwrap(); + return std::wstring(buf.get()); + } + + LauncherResult defaultBlocklistPath = + BuildDefaultBlocklistFilename(); + if (defaultBlocklistPath.isErr()) { + return defaultBlocklistPath.propagateErr(); + } + + LauncherVoidResult writeResult = WriteRegistryValueString( + mRegKey, ResolveBlocklistValueName(), defaultBlocklistPath.inspect()); + if (writeResult.isErr()) { + return writeResult.propagateErr(); + } + + return defaultBlocklistPath; +} + } // namespace mozilla diff --git a/toolkit/xre/LauncherRegistryInfo.h b/toolkit/xre/LauncherRegistryInfo.h index c8d015a4ced2..c89ed66a695f 100644 --- a/toolkit/xre/LauncherRegistryInfo.h +++ b/toolkit/xre/LauncherRegistryInfo.h @@ -48,6 +48,7 @@ class LauncherRegistryInfo final { LauncherVoidResult DisableDueToFailure(); LauncherVoidResult Commit(); void Abort(); + LauncherResult GetBlocklistFileName(); private: enum class Disposition { CreatedNew, OpenedExisting }; @@ -70,11 +71,13 @@ class LauncherRegistryInfo final { LauncherResult> GetSavedImageTimestamp(); LauncherResult> GetLauncherStartTimestamp(); LauncherResult> GetBrowserStartTimestamp(); + LauncherResult BuildDefaultBlocklistFilename(); const std::wstring& ResolveLauncherValueName(); const std::wstring& ResolveBrowserValueName(); const std::wstring& ResolveImageTimestampValueName(); const std::wstring& ResolveTelemetryValueName(); + const std::wstring& ResolveBlocklistValueName(); private: Maybe mLauncherTimestampToWrite; @@ -86,12 +89,14 @@ class LauncherRegistryInfo final { std::wstring mBrowserValueName; std::wstring mLauncherValueName; std::wstring mTelemetryValueName; + std::wstring mBlocklistValueName; static const wchar_t kLauncherSubKeyPath[]; static const wchar_t kLauncherSuffix[]; static const wchar_t kBrowserSuffix[]; static const wchar_t kImageTimestampSuffix[]; static const wchar_t kTelemetrySuffix[]; + static const wchar_t kBlocklistSuffix[]; }; } // namespace mozilla diff --git a/toolkit/xre/dllservices/DynamicBlocklist.h b/toolkit/xre/dllservices/DynamicBlocklist.h new file mode 100644 index 000000000000..5de95baf24b4 --- /dev/null +++ b/toolkit/xre/dllservices/DynamicBlocklist.h @@ -0,0 +1,281 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_DynamicBlocklist_h +#define mozilla_DynamicBlocklist_h + +#include + +#include "nsWindowsHelpers.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/NativeNt.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" +#include "mozilla/Vector.h" + +#if defined(MOZILLA_INTERNAL_API) +# include "mozilla/dom/Promise.h" +# include "nsIOutputStream.h" +# include "nsTHashSet.h" +#endif // defined(MOZILLA_INTERNAL_API) + +#include "mozilla/WindowsDllBlocklistInfo.h" + +#if !defined(MOZILLA_INTERNAL_API) && defined(ENABLE_TESTS) +# define BLOCKLIST_INSERT_TEST_ENTRY +#endif // !defined(MOZILLA_INTERNAL_API) && defined(ENABLE_TESTS) + +namespace mozilla { +using DllBlockInfo = DllBlockInfoT; + +struct DynamicBlockListBase { + static constexpr uint32_t kSignature = 'FFBL'; // Firefox Blocklist + static constexpr uint32_t kCurrentVersion = 1; + + struct FileHeader { + uint32_t mSignature; + uint32_t mFileVersion; + uint32_t mPayloadSize; + }; +}; +// Define this class in a header so that TestCrossProcessWin +// can include and test it. +class DynamicBlockList final : public DynamicBlockListBase { + uint32_t mPayloadSize; + UniquePtr mPayload; + +#ifdef ENABLE_TESTS + // These two definitions are needed for the DynamicBlocklistWriter to avoid + // writing this test entry out to the blocklist file, so compile these in + // even if MOZILLA_INTERNAL_API is defined. + public: + static constexpr wchar_t kTestDll[] = L"TestDllBlocklist_UserBlocked.dll"; + // kTestDll is null-terminated, but we don't want that for the blocklist + // file + static constexpr size_t kTestDllBytes = sizeof(kTestDll) - sizeof(wchar_t); + + private: +# ifdef BLOCKLIST_INSERT_TEST_ENTRY + // Set up a test entry in the blocklist, used for testing purposes. + void AssignTestEntry(DllBlockInfo* testEntry, uint32_t nameOffset) { + testEntry->mName.Length = kTestDllBytes; + testEntry->mName.MaximumLength = kTestDllBytes; + testEntry->mName.Buffer = reinterpret_cast(nameOffset); + testEntry->mMaxVersion = DllBlockInfo::ALL_VERSIONS; + testEntry->mFlags = DllBlockInfo::FLAGS_DEFAULT; + } + + void CreateListWithTestEntry() { + mPayloadSize = sizeof(DllBlockInfo) * 2 + kTestDllBytes; + mPayload = MakeUnique(mPayloadSize); + DllBlockInfo* entry = reinterpret_cast(mPayload.get()); + AssignTestEntry(entry, sizeof(DllBlockInfo) * 2); + memcpy(mPayload.get() + sizeof(DllBlockInfo) * 2, kTestDll, kTestDllBytes); + ++entry; + entry->mName.Length = 0; + entry->mName.MaximumLength = 0; + } +# endif // BLOCKLIST_INSERT_TEST_ENTRY +#endif // ENABLE_TESTS + + bool LoadFile(const wchar_t* aPath) { + nsAutoHandle file( + ::CreateFileW(aPath, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); + if (!file) { +#ifdef BLOCKLIST_INSERT_TEST_ENTRY + // If the blocklist file doesn't exist, we still want to include a test + // entry for testing purposes. + CreateListWithTestEntry(); + return true; +#else + return false; +#endif + } + + DWORD bytesRead = 0; + FileHeader header; + BOOL ok = + ::ReadFile(file.get(), &header, sizeof(header), &bytesRead, nullptr); + if (!ok) { +#ifdef BLOCKLIST_INSERT_TEST_ENTRY + // If we have a problem reading the blocklist file, we still want to + // include a test entry for testing purposes. + CreateListWithTestEntry(); + return true; +#else + return false; +#endif + } + if (bytesRead != sizeof(header)) { + return false; + } + + if (header.mSignature != kSignature || + header.mFileVersion != kCurrentVersion) { + return false; + } + + // Write an extra entry for testing purposes if BLOCKLIST_INSERT_TEST_ENTRY + // is on +#ifdef BLOCKLIST_INSERT_TEST_ENTRY + uint32_t destinationPayloadSize = + header.mPayloadSize + sizeof(DllBlockInfo) + kTestDllBytes; +#else + uint32_t destinationPayloadSize = header.mPayloadSize; +#endif + UniquePtr payload = + MakeUnique(destinationPayloadSize); + ok = ::ReadFile(file.get(), payload.get(), header.mPayloadSize, &bytesRead, + nullptr); + if (!ok || bytesRead != header.mPayloadSize) { + return false; + } + + uint32_t sizeOfPayloadToIterateOver = header.mPayloadSize; +#ifdef BLOCKLIST_INSERT_TEST_ENTRY + bool haveWrittenTestEntry = false; + UNICODE_STRING testUnicodeString; + // Cast the const-ness away since we're only going to use + // this to compare against strings. + testUnicodeString.Buffer = const_cast(kTestDll); + testUnicodeString.Length = kTestDllBytes; + testUnicodeString.MaximumLength = kTestDllBytes; +#endif + for (uint32_t offset = 0; offset < sizeOfPayloadToIterateOver; + offset += sizeof(DllBlockInfo)) { + DllBlockInfo* entry = + reinterpret_cast(payload.get() + offset); + if (!entry->mName.Length) { +#ifdef BLOCKLIST_INSERT_TEST_ENTRY + if (!haveWrittenTestEntry) { + // Shift everything forward + memmove(payload.get() + offset + sizeof(DllBlockInfo), + payload.get() + offset, header.mPayloadSize - offset); + AssignTestEntry(entry, destinationPayloadSize - kTestDllBytes); + haveWrittenTestEntry = true; + } +#endif + break; + } + + size_t stringOffset = reinterpret_cast(entry->mName.Buffer); + if (stringOffset + entry->mName.Length > header.mPayloadSize) { + entry->mName.Length = 0; + break; + } + entry->mName.MaximumLength = entry->mName.Length; +#ifdef BLOCKLIST_INSERT_TEST_ENTRY + if (!haveWrittenTestEntry) { + UNICODE_STRING currentUnicodeString; + currentUnicodeString.Buffer = + reinterpret_cast(payload.get() + stringOffset); + currentUnicodeString.Length = entry->mName.Length; + currentUnicodeString.MaximumLength = entry->mName.Length; + if (RtlCompareUnicodeString(¤tUnicodeString, &testUnicodeString, + TRUE) > 0) { + // Shift everything forward + memmove(payload.get() + offset + sizeof(DllBlockInfo), + payload.get() + offset, header.mPayloadSize - offset); + AssignTestEntry(entry, destinationPayloadSize - kTestDllBytes); + haveWrittenTestEntry = true; + // Now we have expanded the area of valid memory, so there is more + // allowable space to iterate over. + sizeOfPayloadToIterateOver = destinationPayloadSize; + offset += sizeof(DllBlockInfo); + ++entry; + } + } + // The start of the string section will be moving ahead (or has already + // moved ahead) to make room for the test entry + entry->mName.Buffer += + sizeof(DllBlockInfo) / sizeof(entry->mName.Buffer[0]); +#endif + } +#ifdef BLOCKLIST_INSERT_TEST_ENTRY + memcpy(payload.get() + destinationPayloadSize - kTestDllBytes, kTestDll, + kTestDllBytes); +#endif + + mPayloadSize = destinationPayloadSize; + mPayload = std::move(payload); + return true; + } + + public: + DynamicBlockList() : mPayloadSize(0) {} + explicit DynamicBlockList(const wchar_t* aPath) : mPayloadSize(0) { + LoadFile(aPath); + } + + DynamicBlockList(DynamicBlockList&&) = default; + DynamicBlockList& operator=(DynamicBlockList&&) = default; + DynamicBlockList(const DynamicBlockList&) = delete; + DynamicBlockList& operator=(const DynamicBlockList&) = delete; + + constexpr uint32_t GetPayloadSize() const { return mPayloadSize; } + + // Return the number of bytes copied + size_t CopyTo(void* aBuffer, size_t aBufferLength) const { + if (mPayloadSize > aBufferLength) { + return 0; + } + memcpy(aBuffer, mPayload.get(), mPayloadSize); + return mPayloadSize; + } +}; + +#if defined(MOZILLA_INTERNAL_API) && defined(MOZ_LAUNCHER_PROCESS) + +class DynamicBlocklistWriter final : public DynamicBlockListBase { + template + static nsresult WriteValue(nsIOutputStream* aOutputStream, const T& aValue) { + uint32_t written; + return aOutputStream->Write(reinterpret_cast(&aValue), + sizeof(T), &written); + } + + template + static nsresult WriteBuffer(nsIOutputStream* aOutputStream, const T* aBuffer, + uint32_t aBufferSize) { + uint32_t written; + return aOutputStream->Write(reinterpret_cast(aBuffer), + aBufferSize, &written); + } + + RefPtr mPromise; + Vector mArray; + // All strings are packed in this buffer without null characters + UniquePtr mStringBuffer; + + size_t mArraySize; + size_t mStringBufferSize; + + nsresult WriteToFile(const nsAString& aName) const; + + public: + DynamicBlocklistWriter( + RefPtr aPromise, + const nsTHashSet& aBlocklist); + ~DynamicBlocklistWriter() = default; + + DynamicBlocklistWriter(DynamicBlocklistWriter&&) = default; + DynamicBlocklistWriter& operator=(DynamicBlocklistWriter&&) = default; + DynamicBlocklistWriter(const DynamicBlocklistWriter&) = delete; + DynamicBlocklistWriter& operator=(const DynamicBlocklistWriter&) = delete; + + bool IsReady() const { return mStringBuffer.get(); } + + void Run(); + void Cancel(); +}; + +#endif // defined(MOZILLA_INTERNAL_API) && defined(MOZ_LAUNCHER_PROCESS) + +} // namespace mozilla + +#endif // mozilla_DynamicBlocklist_h diff --git a/toolkit/xre/dllservices/DynamicBlocklistWriter.cpp b/toolkit/xre/dllservices/DynamicBlocklistWriter.cpp new file mode 100644 index 000000000000..c9774590f5a3 --- /dev/null +++ b/toolkit/xre/dllservices/DynamicBlocklistWriter.cpp @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "mozilla/DynamicBlocklist.h" +#include "mozilla/LauncherRegistryInfo.h" + +#include "nsISafeOutputStream.h" +#include "nsNetUtil.h" + +namespace mozilla { +#if ENABLE_TESTS +nsDependentString testEntryString(DynamicBlockList::kTestDll, + DynamicBlockList::kTestDllBytes / + sizeof(DynamicBlockList::kTestDll[0])); +#endif + +bool ShouldWriteEntry(const nsAString& name) { +#if ENABLE_TESTS + return name != testEntryString; +#else + return true; +#endif +} + +DynamicBlocklistWriter::DynamicBlocklistWriter( + RefPtr aPromise, + const nsTHashSet& aBlocklist) + : mPromise(aPromise), mArraySize(0), mStringBufferSize(0) { + CheckedUint32 payloadSize; + bool hasTestEntry = false; + for (const nsAString& name : aBlocklist) { + if (ShouldWriteEntry(name)) { + hasTestEntry = true; + payloadSize += name.Length() * sizeof(char16_t); + } + if (!payloadSize.isValid()) { + return; + } + } + + uint32_t entriesToWrite = aBlocklist.Count(); + if (hasTestEntry) { + entriesToWrite -= 1; + } + + mStringBufferSize = payloadSize.value(); + mArraySize = (entriesToWrite + 1) * sizeof(DllBlockInfo); + + payloadSize += mArraySize; + if (!payloadSize.isValid()) { + return; + } + + mStringBuffer = MakeUnique(mStringBufferSize); + Unused << mArray.resize(entriesToWrite + 1); // aBlockEntries + sentinel + + size_t currentStringOffset = 0; + size_t i = 0; + for (const nsAString& name : aBlocklist) { + if (!ShouldWriteEntry(name)) { + continue; + } + const uint32_t nameSize = name.Length() * sizeof(char16_t); + + mArray[i].mMaxVersion = DllBlockInfo::ALL_VERSIONS; + mArray[i].mFlags = DllBlockInfo::Flags::FLAGS_DEFAULT; + + // Copy the module's name to the string buffer and store its offset + // in mName.Buffer + memcpy(mStringBuffer.get() + currentStringOffset, name.BeginReading(), + nameSize); + mArray[i].mName.Buffer = + reinterpret_cast(mArraySize + currentStringOffset); + // Only keep mName.Length and leave mName.MaximumLength to be zero + mArray[i].mName.Length = nameSize; + + currentStringOffset += nameSize; + ++i; + } +} + +nsresult DynamicBlocklistWriter::WriteToFile(const nsAString& aName) const { + nsCOMPtr file; + MOZ_TRY(NS_NewLocalFile(aName, true, getter_AddRefs(file))); + + nsCOMPtr stream; + MOZ_TRY(NS_NewSafeLocalFileOutputStream(getter_AddRefs(stream), file)); + + MOZ_TRY(WriteValue(stream, kSignature)); + MOZ_TRY(WriteValue(stream, kCurrentVersion)); + MOZ_TRY(WriteValue(stream, + static_cast(mArraySize + mStringBufferSize))); + MOZ_TRY(WriteBuffer(stream, mArray.begin(), mArraySize)); + MOZ_TRY(WriteBuffer(stream, mStringBuffer.get(), mStringBufferSize)); + + nsresult rv; + nsCOMPtr safeStream = do_QueryInterface(stream, &rv); + if (NS_FAILED(rv)) { + return rv; + } + + return safeStream->Finish(); +} + +void DynamicBlocklistWriter::Run() { + MOZ_ASSERT(!NS_IsMainThread()); + + nsresult rv = NS_ERROR_ABORT; + + LauncherRegistryInfo regInfo; + LauncherResult blocklistFile = regInfo.GetBlocklistFileName(); + if (blocklistFile.isOk()) { + const wchar_t* rawBuffer = blocklistFile.inspect().c_str(); + rv = WriteToFile(nsDependentString(rawBuffer)); + } + + NS_DispatchToMainThread( + // Don't capture mPromise by copy because we're not in the main thread + NS_NewRunnableFunction(__func__, [promise = std::move(mPromise), rv]() { + if (NS_SUCCEEDED(rv)) { + promise->MaybeResolve(JS::NullHandleValue); + } else { + promise->MaybeReject(rv); + } + })); +} + +void DynamicBlocklistWriter::Cancel() { + MOZ_ASSERT(NS_IsMainThread()); + if (mPromise) { + mPromise->MaybeReject(NS_ERROR_ABORT); + } +} + +} // namespace mozilla diff --git a/toolkit/xre/dllservices/moz.build b/toolkit/xre/dllservices/moz.build index f9c362a01847..bb23f5fb5352 100644 --- a/toolkit/xre/dllservices/moz.build +++ b/toolkit/xre/dllservices/moz.build @@ -12,6 +12,7 @@ Library("dllservices") FINAL_LIBRARY = "xul" EXPORTS.mozilla += [ + "DynamicBlocklist.h", "ModuleVersionInfo.h", "UntrustedModulesData.h", "UntrustedModulesProcessor.h", @@ -30,6 +31,11 @@ UNIFIED_SOURCES += [ "WinDllServices.cpp", ] +if CONFIG["MOZ_LAUNCHER_PROCESS"]: + UNIFIED_SOURCES += [ + "DynamicBlocklistWriter.cpp", + ] + TEST_DIRS += [ "tests", ] diff --git a/toolkit/xre/dllservices/mozglue/ModuleLoadInfo.h b/toolkit/xre/dllservices/mozglue/ModuleLoadInfo.h index 15710430610c..807adaae0ad3 100644 --- a/toolkit/xre/dllservices/mozglue/ModuleLoadInfo.h +++ b/toolkit/xre/dllservices/mozglue/ModuleLoadInfo.h @@ -14,9 +14,6 @@ namespace mozilla { struct ModuleLoadInfo final { - // If you add a new value or change the meaning of the values, please - // update createLoadStatusElement in aboutSupport.js accordingly, which - // defines text labels of these enum values displayed on about:support. enum class Status : uint32_t { Loaded = 0, Blocked, diff --git a/toolkit/xre/dllservices/mozglue/SharedSection.h b/toolkit/xre/dllservices/mozglue/SharedSection.h index 3075898d4744..75fc61967908 100644 --- a/toolkit/xre/dllservices/mozglue/SharedSection.h +++ b/toolkit/xre/dllservices/mozglue/SharedSection.h @@ -7,8 +7,10 @@ #ifndef mozilla_glue_SharedSection_h #define mozilla_glue_SharedSection_h +#include #include "nscore.h" #include "mozilla/Span.h" +#include "mozilla/WindowsDllBlocklistInfo.h" namespace mozilla::nt { @@ -16,6 +18,7 @@ namespace mozilla::nt { // through DllServices. struct NS_NO_VTABLE SharedSection { virtual Span GetDependentModules() = 0; + virtual Span> GetDynamicBlocklist() = 0; }; } // namespace mozilla::nt diff --git a/toolkit/xre/dllservices/tests/gtest/TestDLLBlocklist.cpp b/toolkit/xre/dllservices/tests/gtest/TestDLLBlocklist.cpp index 696e32573bf2..5f141b22ae5d 100644 --- a/toolkit/xre/dllservices/tests/gtest/TestDLLBlocklist.cpp +++ b/toolkit/xre/dllservices/tests/gtest/TestDLLBlocklist.cpp @@ -124,6 +124,25 @@ TEST(TestDllBlocklist, NoOpEntryPoint) EXPECT_TRUE(!!::GetModuleHandleW(kLeafName.get())); # endif } + +// User blocklist needs the launcher process +TEST(TestDllBlocklist, UserBlocked) +{ + constexpr auto kLeafName = u"TestDllBlocklist_UserBlocked.dll"_ns; + nsString dllPath = GetFullPath(kLeafName); + + nsModuleHandle hDll(::LoadLibraryW(dllPath.get())); + +// With ASAN, the test uses mozglue's blocklist where +// the user blocklist is not used. +# if !defined(MOZ_ASAN) + EXPECT_TRUE(!hDll); + EXPECT_TRUE(!::GetModuleHandleW(kLeafName.get())); +# endif + hDll.own(::LoadLibraryExW(dllPath.get(), nullptr, LOAD_LIBRARY_AS_DATAFILE)); + // Mapped as MEM_MAPPED + PAGE_READONLY + EXPECT_TRUE(hDll); +} #endif // defined(MOZ_LAUNCHER_PROCESS) #define DLL_BLOCKLIST_ENTRY(name, ...) {name, __VA_ARGS__}, diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/TestDllBlocklist_UserBlocked.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/TestDllBlocklist_UserBlocked.cpp new file mode 100644 index 000000000000..7bd936296e4e --- /dev/null +++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/TestDllBlocklist_UserBlocked.cpp @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include + +BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID) { return TRUE; } diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/moz.build new file mode 100644 index 000000000000..31996c5cb2ae --- /dev/null +++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/moz.build @@ -0,0 +1,15 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIST_INSTALL = False + +SharedLibrary("TestDllBlocklist_UserBlocked") + +UNIFIED_SOURCES = [ + "TestDllBlocklist_UserBlocked.cpp", +] + +if CONFIG["COMPILE_ENVIRONMENT"]: + TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_UserBlocked.dll"] diff --git a/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules.cpp b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules.cpp index b0eec470da35..da0ab241b7c6 100644 --- a/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules.cpp +++ b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules.cpp @@ -274,11 +274,17 @@ void UntrustedModulesFixture::ValidateUntrustedModules( const wchar_t* mName; ModuleLoadInfo::Status mStatus; } kKnownModules[] = { - // Sorted by mName for binary-search - {L"TestDllBlocklist_MatchByName.dll", ModuleLoadInfo::Status::Blocked}, - {L"TestDllBlocklist_MatchByVersion.dll", ModuleLoadInfo::Status::Blocked}, - {L"TestDllBlocklist_NoOpEntryPoint.dll", - ModuleLoadInfo::Status::Redirected}, + // Sorted by mName for binary-search + {L"TestDllBlocklist_MatchByName.dll", ModuleLoadInfo::Status::Blocked}, + {L"TestDllBlocklist_MatchByVersion.dll", ModuleLoadInfo::Status::Blocked}, + {L"TestDllBlocklist_NoOpEntryPoint.dll", + ModuleLoadInfo::Status::Redirected}, +#if !defined(MOZ_ASAN) + // With ASAN, the test uses mozglue's blocklist where + // the user blocklist is not used. So only check for this + // DLL in the non-ASAN case. + {L"TestDllBlocklist_UserBlocked.dll", ModuleLoadInfo::Status::Blocked}, +#endif // !defined(MOZ_ASAN) }; EXPECT_EQ(aData.mProcessType, GeckoProcessType_Default); diff --git a/toolkit/xre/dllservices/tests/gtest/moz.build b/toolkit/xre/dllservices/tests/gtest/moz.build index 3c6f3d62e582..525eba0c3868 100644 --- a/toolkit/xre/dllservices/tests/gtest/moz.build +++ b/toolkit/xre/dllservices/tests/gtest/moz.build @@ -24,6 +24,7 @@ TEST_DIRS += [ "TestDllBlocklist_MatchByVersion", "TestDllBlocklist_NoOpEntryPoint", "TestDllBlocklist_SocketProcessOnly", + "TestDllBlocklist_UserBlocked", "TestDllBlocklist_UtilityProcessOnly", "TestUntrustedModules_Dll1", "TestUntrustedModules_Dll2", diff --git a/toolkit/xre/test/win/TestLauncherRegistryInfo.cpp b/toolkit/xre/test/win/TestLauncherRegistryInfo.cpp index 273c1cb72a9d..ab5edcf1ca23 100644 --- a/toolkit/xre/test/win/TestLauncherRegistryInfo.cpp +++ b/toolkit/xre/test/win/TestLauncherRegistryInfo.cpp @@ -219,6 +219,10 @@ static mozilla::LauncherVoidResult DeleteAllRegstryValues() { return DeleteRegistryValueData(gTelemetryValue); } +bool GetInstallHash(const char16_t*, mozilla::UniquePtr& result) { + return true; +} + static mozilla::LauncherVoidResult SetupEnabledScenario() { // Reset the registry state to an enabled state. First, we delete all existing // registry values (if any). diff --git a/toolkit/xre/test/win/moz.build b/toolkit/xre/test/win/moz.build index 2fd363b9f387..edf3815f53ac 100644 --- a/toolkit/xre/test/win/moz.build +++ b/toolkit/xre/test/win/moz.build @@ -33,6 +33,7 @@ USE_STATIC_LIBS = True OS_LIBS += [ "advapi32", "comctl32", + "ole32", "shell32", "userenv", "ws2_32",