/* -*- 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) # include "mozilla/CmdLineAndEnvUtils.h" # 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 = DllBlockInfoFlags::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) { #ifdef BLOCKLIST_INSERT_TEST_ENTRY const bool shouldInsertBlocklistTestEntry = MOZ_UNLIKELY(mozilla::EnvHasValue("MOZ_DISABLE_NONLOCAL_CONNECTIONS") || mozilla::EnvHasValue("MOZ_RUN_GTEST")); #endif 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 (shouldInsertBlocklistTestEntry) { // If the blocklist file doesn't exist, we still want to include a test // entry for testing purposes. CreateListWithTestEntry(); return true; } #endif return false; } DWORD bytesRead = 0; FileHeader header; BOOL ok = ::ReadFile(file.get(), &header, sizeof(header), &bytesRead, nullptr); if (!ok) { #ifdef BLOCKLIST_INSERT_TEST_ENTRY if (shouldInsertBlocklistTestEntry) { // If we have a problem reading the blocklist file, we still want to // include a test entry for testing purposes. CreateListWithTestEntry(); return true; } #endif return false; } if (bytesRead != sizeof(header)) { return false; } if (header.mSignature != kSignature || header.mFileVersion != kCurrentVersion) { return false; } uint32_t destinationPayloadSize = header.mPayloadSize; #ifdef BLOCKLIST_INSERT_TEST_ENTRY if (shouldInsertBlocklistTestEntry) { // Write an extra entry for testing purposes destinationPayloadSize += sizeof(DllBlockInfo) + kTestDllBytes; } #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 (shouldInsertBlocklistTestEntry && !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 (shouldInsertBlocklistTestEntry && !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 if (shouldInsertBlocklistTestEntry) { 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