mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-03-03 23:30:46 +00:00
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:
parent
61206c170d
commit
ae13a56ab0
@ -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",
|
||||
]
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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(
|
||||
|
@ -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",
|
||||
]
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
281
toolkit/xre/dllservices/DynamicBlocklist.h
Normal file
281
toolkit/xre/dllservices/DynamicBlocklist.h
Normal 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(¤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 <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
|
138
toolkit/xre/dllservices/DynamicBlocklistWriter.cpp
Normal file
138
toolkit/xre/dllservices/DynamicBlocklistWriter.cpp
Normal 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
|
@ -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",
|
||||
]
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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__},
|
||||
|
@ -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; }
|
@ -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"]
|
@ -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);
|
||||
|
@ -24,6 +24,7 @@ TEST_DIRS += [
|
||||
"TestDllBlocklist_MatchByVersion",
|
||||
"TestDllBlocklist_NoOpEntryPoint",
|
||||
"TestDllBlocklist_SocketProcessOnly",
|
||||
"TestDllBlocklist_UserBlocked",
|
||||
"TestDllBlocklist_UtilityProcessOnly",
|
||||
"TestUntrustedModules_Dll1",
|
||||
"TestUntrustedModules_Dll2",
|
||||
|
@ -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).
|
||||
|
@ -33,6 +33,7 @@ USE_STATIC_LIBS = True
|
||||
OS_LIBS += [
|
||||
"advapi32",
|
||||
"comctl32",
|
||||
"ole32",
|
||||
"shell32",
|
||||
"userenv",
|
||||
"ws2_32",
|
||||
|
Loading…
x
Reference in New Issue
Block a user