Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#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.
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