Bug 1744362 - Part 6: use dynamic blocklist file to block third-party DLLs r=handyman

Differential Revision: https://phabricator.services.mozilla.com/D164488
This commit is contained in:
Greg Stoll 2022-12-30 20:10:04 +00:00
parent 61206c170d
commit ae13a56ab0
25 changed files with 1131 additions and 65 deletions

View File

@ -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",
]

View File

@ -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<std::wstring> 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<std::wstring> 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

View File

@ -9,6 +9,9 @@
#include <windows.h>
#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<std::wstring> aBlocklistFileName);
} // namespace mozilla

View File

@ -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<std::wstring> 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<int> LauncherMain(int& argc, wchar_t* argv[],
#if defined(MOZ_LAUNCHER_PROCESS)
LauncherRegistryInfo regInfo;
Maybe<bool> runAsLauncher = RunAsLauncherProcess(regInfo, argc, argv);
LauncherResult<std::wstring> blocklistFileNameResult =
regInfo.GetBlocklistFileName();
Maybe<std::wstring> blocklistFileName =
blocklistFileNameResult.isOk() ? Some(blocklistFileNameResult.unwrap())
: Nothing();
#else
Maybe<bool> runAsLauncher = RunAsLauncherProcess(argc, argv);
Maybe<std::wstring> blocklistFileName = Nothing();
#endif // defined(MOZ_LAUNCHER_PROCESS)
if (!runAsLauncher || !runAsLauncher.value()) {
#if defined(MOZ_LAUNCHER_PROCESS)
@ -486,9 +495,14 @@ Maybe<int> 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);

View File

@ -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<int>(
::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<bool> 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;
}

View File

@ -6,7 +6,9 @@
#include "SharedSection.h"
#include <algorithm>
#include "CheckForCaller.h"
#include "mozilla/BinarySearch.h"
namespace {
@ -167,6 +169,8 @@ LauncherVoidResult SharedSection::Init() {
Layout* view = writableView.as<Layout>();
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<Layout>();
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<Layout>();
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<uintptr_t>(view) + kSharedViewSize;
size_t bytesCopied = aBlocklist.CopyTo(
view->mFirstBlockEntry,
bufferEnd - reinterpret_cast<uintptr_t>(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<uintptr_t>(mFirstBlockEntry);
uint32_t numEntries = 0;
for (DllBlockInfo* entry = mFirstBlockEntry;
entry->mName.Length && numEntries < GetMaxNumBlockEntries(); ++entry) {
entry->mName.Buffer = reinterpret_cast<wchar_t*>(
arrayBase + reinterpret_cast<uintptr_t>(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<wchar_t> SharedSection::Layout::GetDependentModules() {
if (!mDependentModulePathArrayStart) {
return nullptr;
}
return Span<wchar_t>(
reinterpret_cast<wchar_t*>(reinterpret_cast<uintptr_t>(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<const wchar_t> SharedSection::GetDependentModules() {
Layout* writeCopyView = EnsureWriteCopyView();
return writeCopyView ? writeCopyView->GetDependentModules() : nullptr;
}
Span<const DllBlockInfo> 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) {

View File

@ -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<int>(
::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) | <DllBlockInfo for first entry in dynamic blocklist> |
// | | <DllBlockInfo for second entry in dynamic blocklist> |
// | | ... |
// | | <DllBlockInfo for last entry in dynamic blocklist> |
// | | <DllBlockInfo with mName.Length of 0> |
// | | 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<wchar_t> GetModulePathArray() {
return Span<wchar_t>(
mModulePathArray,
(kSharedViewSize - (reinterpret_cast<uintptr_t>(mModulePathArray) -
Span<DllBlockInfo> GetModulePathArray() {
return Span<DllBlockInfo>(
mFirstBlockEntry,
(kSharedViewSize - (reinterpret_cast<uintptr_t>(mFirstBlockEntry) -
reinterpret_cast<uintptr_t>(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<wchar_t> 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<const wchar_t> GetDependentModules() final override;
Span<const DllBlockInfo> 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(

View File

@ -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",
]

View File

@ -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 <thread>
#include <winternl.h>
#define MOZ_USE_LAUNCHER_ERROR
#include <atomic>
#include <thread>
#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 <typename T, int N>
void PrintLauncherError(const LauncherResult<T>& 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<uint8_t[]> stringBuffer =
MakeUnique<uint8_t[]>(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<DllBlockInfo[]> sortedBlockEntries =
MakeUnique<DllBlockInfo[]>(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<DllBlockInfo> 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<wchar_t*>(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()

View File

@ -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<std::string>::iterator it = mExtraOpts.begin();

View File

@ -138,7 +138,9 @@ static CommandLineArg<bool> sWin32kLockedDown{"-win32kLockedDown",
static CommandLineArg<uint64_t> sA11yResourceId{"-a11yResourceId",
"a11yresourceid"};
# endif // defined(ACCESSIBILITY)
#endif // defined(XP_WIN) && defined(ACCESSIBILITY)
static CommandLineArg<bool> sDisableDynamicDllBlocklist{
"-disableDynamicBlocklist", "disabledynamicblocklist"};
#endif // defined(XP_WIN)
#if defined(__GNUC__)
# pragma GCC diagnostic pop

View File

@ -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 <cwctype>
#include <shlobj.h>
#include <string>
#define EXPAND_STRING_MACRO2(t) t
@ -66,6 +69,45 @@ static mozilla::LauncherResult<mozilla::Maybe<T>> ReadRegistryValueData(
return mozilla::Some(data);
}
static mozilla::LauncherResult<mozilla::UniquePtr<wchar_t[]>>
ReadRegistryValueString(const nsAutoRegKey& aKey, const std::wstring& aName) {
mozilla::UniquePtr<wchar_t[]> 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<wchar_t[]>(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<const BYTE*>(aValue.c_str()), dataBytes);
if (status != ERROR_SUCCESS) {
return LAUNCHER_ERROR_FROM_WIN32(status);
}
return mozilla::Ok();
}
template <typename T>
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<std::wstring>
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<wchar_t, CoTaskMemFreeDeleter> appDataPath(rawPath);
std::wstring defaultBlocklistPath(appDataPath.get());
UniquePtr<NS_tchar[]> hash;
std::wstring binPathLower;
binPathLower.reserve(mBinPath.size());
std::transform(mBinPath.begin(), mBinPath.end(),
std::back_inserter(binPathLower), std::towlower);
if (!::GetInstallHash(reinterpret_cast<const char16_t*>(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<std::wstring> LauncherRegistryInfo::GetBlocklistFileName() {
LauncherResult<Disposition> disposition = Open();
if (disposition.isErr()) {
return disposition.propagateErr();
}
LauncherResult<UniquePtr<wchar_t[]>> readResult =
ReadRegistryValueString(mRegKey, ResolveBlocklistValueName());
if (readResult.isErr()) {
return readResult.propagateErr();
}
if (readResult.inspect()) {
UniquePtr<wchar_t[]> buf = readResult.unwrap();
return std::wstring(buf.get());
}
LauncherResult<std::wstring> 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

View File

@ -48,6 +48,7 @@ class LauncherRegistryInfo final {
LauncherVoidResult DisableDueToFailure();
LauncherVoidResult Commit();
void Abort();
LauncherResult<std::wstring> GetBlocklistFileName();
private:
enum class Disposition { CreatedNew, OpenedExisting };
@ -70,11 +71,13 @@ class LauncherRegistryInfo final {
LauncherResult<Maybe<DWORD>> GetSavedImageTimestamp();
LauncherResult<Maybe<uint64_t>> GetLauncherStartTimestamp();
LauncherResult<Maybe<uint64_t>> GetBrowserStartTimestamp();
LauncherResult<std::wstring> BuildDefaultBlocklistFilename();
const std::wstring& ResolveLauncherValueName();
const std::wstring& ResolveBrowserValueName();
const std::wstring& ResolveImageTimestampValueName();
const std::wstring& ResolveTelemetryValueName();
const std::wstring& ResolveBlocklistValueName();
private:
Maybe<uint64_t> 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

View File

@ -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 <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)
# 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 = DllBlockInfo::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) {
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<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 (!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 (!haveWrittenTestEntry) {
UNICODE_STRING currentUnicodeString;
currentUnicodeString.Buffer =
reinterpret_cast<PWSTR>(payload.get() + stringOffset);
currentUnicodeString.Length = entry->mName.Length;
currentUnicodeString.MaximumLength = entry->mName.Length;
if (RtlCompareUnicodeString(&currentUnicodeString, &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 <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

View File

@ -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<dom::Promise> aPromise,
const nsTHashSet<nsStringCaseInsensitiveHashKey>& 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<uint8_t[]>(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<wchar_t*>(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<nsIFile> file;
MOZ_TRY(NS_NewLocalFile(aName, true, getter_AddRefs(file)));
nsCOMPtr<nsIOutputStream> 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<uint32_t>(mArraySize + mStringBufferSize)));
MOZ_TRY(WriteBuffer(stream, mArray.begin(), mArraySize));
MOZ_TRY(WriteBuffer(stream, mStringBuffer.get(), mStringBufferSize));
nsresult rv;
nsCOMPtr<nsISafeOutputStream> 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<std::wstring> 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

View File

@ -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",
]

View File

@ -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,

View File

@ -7,8 +7,10 @@
#ifndef mozilla_glue_SharedSection_h
#define mozilla_glue_SharedSection_h
#include <winternl.h>
#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<const wchar_t> GetDependentModules() = 0;
virtual Span<const DllBlockInfoT<UNICODE_STRING>> GetDynamicBlocklist() = 0;
};
} // namespace mozilla::nt

View File

@ -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__},

View File

@ -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 <windows.h>
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID) { return TRUE; }

View File

@ -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"]

View File

@ -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);

View File

@ -24,6 +24,7 @@ TEST_DIRS += [
"TestDllBlocklist_MatchByVersion",
"TestDllBlocklist_NoOpEntryPoint",
"TestDllBlocklist_SocketProcessOnly",
"TestDllBlocklist_UserBlocked",
"TestDllBlocklist_UtilityProcessOnly",
"TestUntrustedModules_Dll1",
"TestUntrustedModules_Dll2",

View File

@ -219,6 +219,10 @@ static mozilla::LauncherVoidResult DeleteAllRegstryValues() {
return DeleteRegistryValueData(gTelemetryValue);
}
bool GetInstallHash(const char16_t*, mozilla::UniquePtr<NS_tchar[]>& result) {
return true;
}
static mozilla::LauncherVoidResult SetupEnabledScenario() {
// Reset the registry state to an enabled state. First, we delete all existing
// registry values (if any).

View File

@ -33,6 +33,7 @@ USE_STATIC_LIBS = True
OS_LIBS += [
"advapi32",
"comctl32",
"ole32",
"shell32",
"userenv",
"ws2_32",