291 lines
10 KiB
C++
291 lines
10 KiB
C++
/* -*- 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 <winternl.h>
|
|
|
|
#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<UNICODE_STRING>;
|
|
|
|
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<uint8_t[]> 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<PWSTR>(nameOffset);
|
|
testEntry->mMaxVersion = DllBlockInfo::ALL_VERSIONS;
|
|
testEntry->mFlags = DllBlockInfoFlags::FLAGS_DEFAULT;
|
|
}
|
|
|
|
void CreateListWithTestEntry() {
|
|
mPayloadSize = sizeof(DllBlockInfo) * 2 + kTestDllBytes;
|
|
mPayload = MakeUnique<uint8_t[]>(mPayloadSize);
|
|
DllBlockInfo* entry = reinterpret_cast<DllBlockInfo*>(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<uint8_t[]> payload =
|
|
MakeUnique<uint8_t[]>(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<PWSTR>(kTestDll);
|
|
testUnicodeString.Length = kTestDllBytes;
|
|
testUnicodeString.MaximumLength = kTestDllBytes;
|
|
#endif
|
|
for (uint32_t offset = 0; offset < sizeOfPayloadToIterateOver;
|
|
offset += sizeof(DllBlockInfo)) {
|
|
DllBlockInfo* entry =
|
|
reinterpret_cast<DllBlockInfo*>(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<size_t>(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<PWSTR>(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 <typename T>
|
|
static nsresult WriteValue(nsIOutputStream* aOutputStream, const T& aValue) {
|
|
uint32_t written;
|
|
return aOutputStream->Write(reinterpret_cast<const char*>(&aValue),
|
|
sizeof(T), &written);
|
|
}
|
|
|
|
template <typename T>
|
|
static nsresult WriteBuffer(nsIOutputStream* aOutputStream, const T* aBuffer,
|
|
uint32_t aBufferSize) {
|
|
uint32_t written;
|
|
return aOutputStream->Write(reinterpret_cast<const char*>(aBuffer),
|
|
aBufferSize, &written);
|
|
}
|
|
|
|
RefPtr<dom::Promise> mPromise;
|
|
Vector<DllBlockInfo> mArray;
|
|
// All strings are packed in this buffer without null characters
|
|
UniquePtr<uint8_t[]> mStringBuffer;
|
|
|
|
size_t mArraySize;
|
|
size_t mStringBufferSize;
|
|
|
|
nsresult WriteToFile(const nsAString& aName) const;
|
|
|
|
public:
|
|
DynamicBlocklistWriter(
|
|
RefPtr<dom::Promise> aPromise,
|
|
const nsTHashSet<nsStringCaseInsensitiveHashKey>& 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
|