388 lines
13 KiB
C++
388 lines
13 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "SharedSection.h"
|
|
|
|
#include <algorithm>
|
|
#include "CheckForCaller.h"
|
|
#include "mozilla/BinarySearch.h"
|
|
|
|
namespace {
|
|
|
|
bool AddString(mozilla::Span<wchar_t> aBuffer, const UNICODE_STRING& aStr) {
|
|
size_t offsetElements = 0;
|
|
while (offsetElements < aBuffer.Length()) {
|
|
UNICODE_STRING uniStr;
|
|
::RtlInitUnicodeString(&uniStr, aBuffer.data() + offsetElements);
|
|
|
|
if (uniStr.Length == 0) {
|
|
// Reached to the array's last item.
|
|
break;
|
|
}
|
|
|
|
if (::RtlCompareUnicodeString(&uniStr, &aStr, TRUE) == 0) {
|
|
// Already included in the array.
|
|
return true;
|
|
}
|
|
|
|
// Go to the next string.
|
|
offsetElements += uniStr.MaximumLength / sizeof(wchar_t);
|
|
}
|
|
|
|
// Ensure enough space including the last empty string at the end.
|
|
if (offsetElements * sizeof(wchar_t) + aStr.Length + sizeof(wchar_t) +
|
|
sizeof(wchar_t) >
|
|
aBuffer.LengthBytes()) {
|
|
return false;
|
|
}
|
|
|
|
auto newStr = aBuffer.Subspan(offsetElements);
|
|
memcpy(newStr.data(), aStr.Buffer, aStr.Length);
|
|
memset(newStr.data() + aStr.Length / sizeof(wchar_t), 0, sizeof(wchar_t));
|
|
return true;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
namespace mozilla {
|
|
namespace freestanding {
|
|
|
|
SharedSection gSharedSection;
|
|
|
|
// Why don't we use ::GetProcAddress?
|
|
// If the export table of kernel32.dll is tampered in the current process,
|
|
// we cannot transfer an RVA because the function pointed by the RVA may not
|
|
// exist in a target process.
|
|
// We can use ::GetProcAddress with additional check to detect tampering, but
|
|
// FindExportAddressTableEntry fits perfectly here because it returns nullptr
|
|
// if the target entry is outside the image, which means it's tampered or
|
|
// forwarded to another DLL.
|
|
#define INIT_FUNCTION(exports, name) \
|
|
do { \
|
|
auto rvaToFunction = exports.FindExportAddressTableEntry(#name); \
|
|
if (!rvaToFunction) { \
|
|
return; \
|
|
} \
|
|
m##name = reinterpret_cast<decltype(m##name)>(*rvaToFunction); \
|
|
} while (0)
|
|
|
|
#define RESOLVE_FUNCTION(base, name) \
|
|
m##name = reinterpret_cast<decltype(m##name)>( \
|
|
base + reinterpret_cast<uintptr_t>(m##name))
|
|
|
|
void Kernel32ExportsSolver::Init() {
|
|
interceptor::MMPolicyInProcess policy;
|
|
auto k32Exports = nt::PEExportSection<interceptor::MMPolicyInProcess>::Get(
|
|
::GetModuleHandleW(L"kernel32.dll"), policy);
|
|
if (!k32Exports) {
|
|
return;
|
|
}
|
|
|
|
// Please make sure these functions are not forwarded to another DLL.
|
|
INIT_FUNCTION(k32Exports, FlushInstructionCache);
|
|
INIT_FUNCTION(k32Exports, GetModuleHandleW);
|
|
INIT_FUNCTION(k32Exports, GetSystemInfo);
|
|
INIT_FUNCTION(k32Exports, VirtualProtect);
|
|
}
|
|
|
|
bool Kernel32ExportsSolver::Resolve() {
|
|
const UNICODE_STRING k32Name = MOZ_LITERAL_UNICODE_STRING(L"kernel32.dll");
|
|
|
|
// We cannot use GetModuleHandleW because this code can be called
|
|
// before IAT is resolved.
|
|
auto k32Module = nt::GetModuleHandleFromLeafName(k32Name);
|
|
if (k32Module.isErr()) {
|
|
// Probably this is called before kernel32.dll is loaded.
|
|
return false;
|
|
}
|
|
|
|
uintptr_t k32Base =
|
|
nt::PEHeaders::HModuleToBaseAddr<uintptr_t>(k32Module.unwrap());
|
|
|
|
RESOLVE_FUNCTION(k32Base, FlushInstructionCache);
|
|
RESOLVE_FUNCTION(k32Base, GetModuleHandleW);
|
|
RESOLVE_FUNCTION(k32Base, GetSystemInfo);
|
|
RESOLVE_FUNCTION(k32Base, VirtualProtect);
|
|
|
|
return true;
|
|
}
|
|
|
|
HANDLE SharedSection::sSectionHandle = nullptr;
|
|
SharedSection::Layout* SharedSection::sWriteCopyView = nullptr;
|
|
RTL_RUN_ONCE SharedSection::sEnsureOnce = RTL_RUN_ONCE_INIT;
|
|
nt::SRWLock SharedSection::sLock;
|
|
|
|
void SharedSection::Reset(HANDLE aNewSectionObject) {
|
|
nt::AutoExclusiveLock{sLock};
|
|
if (sWriteCopyView) {
|
|
nt::AutoMappedView view(sWriteCopyView);
|
|
sWriteCopyView = nullptr;
|
|
::RtlRunOnceInitialize(&sEnsureOnce);
|
|
}
|
|
|
|
if (sSectionHandle != aNewSectionObject) {
|
|
if (sSectionHandle) {
|
|
::CloseHandle(sSectionHandle);
|
|
}
|
|
sSectionHandle = aNewSectionObject;
|
|
}
|
|
}
|
|
|
|
void SharedSection::ConvertToReadOnly() {
|
|
if (!sSectionHandle) {
|
|
return;
|
|
}
|
|
|
|
HANDLE readonlyHandle;
|
|
if (!::DuplicateHandle(nt::kCurrentProcess, sSectionHandle,
|
|
nt::kCurrentProcess, &readonlyHandle, GENERIC_READ,
|
|
FALSE, 0)) {
|
|
return;
|
|
}
|
|
|
|
Reset(readonlyHandle);
|
|
}
|
|
|
|
LauncherVoidResult SharedSection::Init() {
|
|
static_assert(
|
|
kSharedViewSize >= sizeof(Layout),
|
|
"kSharedViewSize is too small to represent SharedSection::Layout.");
|
|
|
|
HANDLE section =
|
|
::CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0,
|
|
kSharedViewSize, nullptr);
|
|
if (!section) {
|
|
return LAUNCHER_ERROR_FROM_LAST();
|
|
}
|
|
Reset(section);
|
|
|
|
// The initial contents of the pages in a file mapping object backed by
|
|
// the operating system paging file are 0 (zero). No need to zero it out
|
|
// ourselves.
|
|
// https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-createfilemappingw
|
|
nt::AutoMappedView writableView(sSectionHandle, PAGE_READWRITE);
|
|
if (!writableView) {
|
|
return LAUNCHER_ERROR_FROM_LAST();
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
LauncherVoidResult SharedSection::AddDependentModule(PCUNICODE_STRING aNtPath) {
|
|
nt::AutoMappedView writableView(sSectionHandle, PAGE_READWRITE);
|
|
if (!writableView) {
|
|
return LAUNCHER_ERROR_FROM_WIN32(::RtlGetLastWin32Error());
|
|
}
|
|
|
|
Layout* view = writableView.as<Layout>();
|
|
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*) {
|
|
if (!sWriteCopyView) {
|
|
nt::AutoMappedView view(sSectionHandle, PAGE_WRITECOPY);
|
|
if (!view) {
|
|
return TRUE;
|
|
}
|
|
sWriteCopyView = view.as<Layout>();
|
|
view.release();
|
|
}
|
|
return sWriteCopyView->Resolve() ? TRUE : FALSE;
|
|
}
|
|
|
|
SharedSection::Layout* SharedSection::EnsureWriteCopyView(
|
|
bool requireKernel32Exports /*= false */) {
|
|
::RtlRunOnceExecuteOnce(&sEnsureOnce, &EnsureWriteCopyViewOnce, nullptr,
|
|
nullptr);
|
|
if (!sWriteCopyView) {
|
|
return nullptr;
|
|
}
|
|
auto requiredState = requireKernel32Exports
|
|
? Layout::State::kResolved
|
|
: Layout::State::kLoadedDynamicBlocklistEntries;
|
|
return sWriteCopyView->mState >= requiredState ? sWriteCopyView : nullptr;
|
|
}
|
|
|
|
bool SharedSection::Layout::Resolve() {
|
|
if (mState == State::kResolved) {
|
|
return true;
|
|
}
|
|
if (mState == State::kUninitialized) {
|
|
return false;
|
|
}
|
|
if (mState == State::kInitialized) {
|
|
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::kLoadedDynamicBlocklistEntries;
|
|
}
|
|
|
|
if (!mK32Exports.Resolve()) {
|
|
return false;
|
|
}
|
|
|
|
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::kLoadedDynamicBlocklistEntries);
|
|
DllBlockInfoComparator comp(aLeafName);
|
|
size_t match;
|
|
if (!BinarySearchIf(mFirstBlockEntry, 0, mNumBlockEntries, comp, &match)) {
|
|
return nullptr;
|
|
}
|
|
return &mFirstBlockEntry[match];
|
|
}
|
|
|
|
Kernel32ExportsSolver* SharedSection::GetKernel32Exports() {
|
|
Layout* writeCopyView = EnsureWriteCopyView(true);
|
|
return writeCopyView ? &writeCopyView->mK32Exports : nullptr;
|
|
}
|
|
|
|
Maybe<Vector<const wchar_t*>> SharedSection::GetDependentModules() {
|
|
Layout* writeCopyView = EnsureWriteCopyView();
|
|
if (!writeCopyView) {
|
|
return Nothing();
|
|
}
|
|
|
|
mozilla::Span<wchar_t> dependentModules =
|
|
writeCopyView->GetDependentModules();
|
|
// Convert a null-delimited string set to a string vector.
|
|
Vector<const wchar_t*> paths;
|
|
for (const wchar_t* p = dependentModules.data();
|
|
(p - dependentModules.data() <
|
|
static_cast<long long>(dependentModules.size()) &&
|
|
*p);) {
|
|
if (MOZ_UNLIKELY(!paths.append(p))) {
|
|
return Nothing();
|
|
}
|
|
while (*p) {
|
|
++p;
|
|
}
|
|
++p;
|
|
}
|
|
return Some(std::move(paths));
|
|
}
|
|
|
|
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) {
|
|
HANDLE remoteHandle;
|
|
if (!::DuplicateHandle(nt::kCurrentProcess, sSectionHandle,
|
|
aTransferMgr.RemoteProcess(), &remoteHandle,
|
|
aDesiredAccess, FALSE, 0)) {
|
|
return LAUNCHER_ERROR_FROM_LAST();
|
|
}
|
|
|
|
return aTransferMgr.Transfer(aDestinationAddress, &remoteHandle,
|
|
sizeof(remoteHandle));
|
|
}
|
|
|
|
} // namespace freestanding
|
|
} // namespace mozilla
|