gecko-dev/browser/app/winlauncher/DllBlocklistWin.cpp

436 строки
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 "NativeNt.h"
#include "nsWindowsDllInterceptor.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/Types.h"
#include "mozilla/WindowsDllBlocklist.h"
#define MOZ_LITERAL_UNICODE_STRING(s) \
{ \
/* Length of the string in bytes, less the null terminator */ \
sizeof(s) - sizeof(wchar_t), \
/* Length of the string in bytes, including the null terminator */ \
sizeof(s), \
/* Pointer to the buffer */ \
const_cast<wchar_t*>(s) \
}
#define DLL_BLOCKLIST_ENTRY(name, ...) \
{ MOZ_LITERAL_UNICODE_STRING(L##name), __VA_ARGS__ },
#define DLL_BLOCKLIST_STRING_TYPE UNICODE_STRING
#if defined(MOZ_LAUNCHER_PROCESS) || defined(NIGHTLY_BUILD)
#include "mozilla/WindowsDllBlocklistDefs.h"
#else
#include "mozilla/WindowsDllBlocklistCommon.h"
DLL_BLOCKLIST_DEFINITIONS_BEGIN
DLL_BLOCKLIST_DEFINITIONS_END
#endif
extern uint32_t gBlocklistInitFlags;
static const HANDLE kCurrentProcess = reinterpret_cast<HANDLE>(-1);
class MOZ_STATIC_CLASS MOZ_TRIVIAL_CTOR_DTOR NativeNtBlockSet final
{
struct NativeNtBlockSetEntry
{
NativeNtBlockSetEntry() = default;
~NativeNtBlockSetEntry() = default;
NativeNtBlockSetEntry(const UNICODE_STRING& aName, uint64_t aVersion,
NativeNtBlockSetEntry* aNext)
: mName(aName)
, mVersion(aVersion)
, mNext(aNext)
{}
UNICODE_STRING mName;
uint64_t mVersion;
NativeNtBlockSetEntry* mNext;
};
public:
// Constructor and destructor MUST be trivial
NativeNtBlockSet() = default;
~NativeNtBlockSet() = default;
void Add(const UNICODE_STRING& aName, uint64_t aVersion);
void Write(HANDLE aFile);
private:
static NativeNtBlockSetEntry* NewEntry(const UNICODE_STRING& aName,
uint64_t aVersion,
NativeNtBlockSetEntry* aNextEntry);
private:
NativeNtBlockSetEntry* mFirstEntry;
// SRWLOCK_INIT == 0, so this is okay to use without any additional work as
// long as NativeNtBlockSet is instantiated statically
SRWLOCK mLock;
};
NativeNtBlockSet::NativeNtBlockSetEntry*
NativeNtBlockSet::NewEntry(const UNICODE_STRING& aName, uint64_t aVersion,
NativeNtBlockSet::NativeNtBlockSetEntry* aNextEntry)
{
HANDLE processHeap = mozilla::nt::RtlGetProcessHeap();
if (!processHeap) {
return nullptr;
}
PVOID memory = ::RtlAllocateHeap(processHeap, 0, sizeof(NativeNtBlockSetEntry));
if (!memory) {
return nullptr;
}
return new (memory) NativeNtBlockSetEntry(aName, aVersion, aNextEntry);
}
void
NativeNtBlockSet::Add(const UNICODE_STRING& aName, uint64_t aVersion)
{
::RtlAcquireSRWLockExclusive(&mLock);
for (NativeNtBlockSetEntry* entry = mFirstEntry; entry; entry = entry->mNext) {
if (::RtlEqualUnicodeString(&entry->mName, &aName, TRUE) &&
aVersion == entry->mVersion) {
::RtlReleaseSRWLockExclusive(&mLock);
return;
}
}
// Not present, add it
NativeNtBlockSetEntry* newEntry = NewEntry(aName, aVersion, mFirstEntry);
mFirstEntry = newEntry;
::RtlReleaseSRWLockExclusive(&mLock);
}
void
NativeNtBlockSet::Write(HANDLE aFile)
{
// NB: If this function is called, it is long after kernel32 is initialized,
// so it is safe to use Win32 calls here.
DWORD nBytes;
char buf[MAX_PATH];
// It would be nicer to use RAII here. However, its destructor
// might not run if an exception occurs, in which case we would never release
// the lock (MSVC warns about this possibility). So we acquire and release
// manually.
::AcquireSRWLockExclusive(&mLock);
MOZ_SEH_TRY {
for (auto entry = mFirstEntry; entry; entry = entry->mNext) {
int convOk = ::WideCharToMultiByte(CP_UTF8, 0, entry->mName.Buffer,
entry->mName.Length / sizeof(wchar_t),
buf, sizeof(buf), nullptr, nullptr);
if (!convOk) {
continue;
}
// write name[,v.v.v.v];
if (!WriteFile(aFile, buf, convOk, &nBytes, nullptr)) {
continue;
}
if (entry->mVersion != ALL_VERSIONS) {
WriteFile(aFile, ",", 1, &nBytes, nullptr);
uint16_t parts[4];
parts[0] = entry->mVersion >> 48;
parts[1] = (entry->mVersion >> 32) & 0xFFFF;
parts[2] = (entry->mVersion >> 16) & 0xFFFF;
parts[3] = entry->mVersion & 0xFFFF;
for (size_t p = 0; p < mozilla::ArrayLength(parts); ++p) {
ltoa(parts[p], buf, 10);
WriteFile(aFile, buf, strlen(buf), &nBytes, nullptr);
if (p != mozilla::ArrayLength(parts) - 1) {
WriteFile(aFile, ".", 1, &nBytes, nullptr);
}
}
}
WriteFile(aFile, ";", 1, &nBytes, nullptr);
}
}
MOZ_SEH_EXCEPT (EXCEPTION_EXECUTE_HANDLER) {
}
::ReleaseSRWLockExclusive(&mLock);
}
static NativeNtBlockSet gBlockSet;
extern "C" void MOZ_EXPORT
NativeNtBlockSet_Write(HANDLE aHandle)
{
gBlockSet.Write(aHandle);
}
static bool
CheckBlockInfo(const DllBlockInfo* aInfo, void* aBaseAddress, uint64_t& aVersion)
{
aVersion = ALL_VERSIONS;
if (aInfo->flags & (DllBlockInfo::BLOCK_WIN8PLUS_ONLY | DllBlockInfo::BLOCK_WIN8_ONLY)) {
RTL_OSVERSIONINFOW osv;
NTSTATUS ntStatus = ::RtlGetVersion(&osv);
if (!NT_SUCCESS(ntStatus)) {
// huh?
return false;
}
if (osv.dwMajorVersion < 8) {
return true;
}
if ((aInfo->flags & DllBlockInfo::BLOCK_WIN8_ONLY) && (osv.dwMajorVersion > 8 ||
(osv.dwMajorVersion == 8 && osv.dwMinorVersion > 0))) {
return true;
}
}
// We're not bootstrapping child processes at this time, so this case is
// always true.
if (aInfo->flags & DllBlockInfo::CHILD_PROCESSES_ONLY) {
return true;
}
if (aInfo->maxVersion == ALL_VERSIONS) {
return false;
}
mozilla::nt::PEHeaders headers(aBaseAddress);
if (!headers) {
return false;
}
if (aInfo->flags & DllBlockInfo::USE_TIMESTAMP) {
DWORD timestamp;
if (!headers.GetTimeStamp(timestamp)) {
return false;
}
return timestamp > aInfo->maxVersion;
}
// Else we try to get the file version information. Note that we don't have
// access to GetFileVersionInfo* APIs.
if (!headers.GetVersionInfo(aVersion)) {
return false;
}
return aVersion > aInfo->maxVersion;
}
static bool
IsDllAllowed(const UNICODE_STRING& aLeafName, void* aBaseAddress)
{
if (mozilla::nt::Contains12DigitHexString(aLeafName) ||
mozilla::nt::IsFileNameAtLeast16HexDigits(aLeafName)) {
return false;
}
DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY(info);
DECLARE_POINTER_TO_LAST_DLL_BLOCKLIST_ENTRY(end);
while (info < end) {
if (::RtlEqualUnicodeString(&aLeafName, &info->name, TRUE)) {
break;
}
++info;
}
uint64_t version;
if (info->name.Length && !CheckBlockInfo(info, aBaseAddress, version)) {
gBlockSet.Add(info->name, version);
return false;
}
return true;
}
typedef decltype(&NtMapViewOfSection) NtMapViewOfSection_func;
static mozilla::CrossProcessDllInterceptor::FuncHookType<NtMapViewOfSection_func>
stub_NtMapViewOfSection;
static NTSTATUS NTAPI
patched_NtMapViewOfSection(HANDLE aSection, HANDLE aProcess, PVOID* aBaseAddress,
ULONG_PTR aZeroBits, SIZE_T aCommitSize,
PLARGE_INTEGER aSectionOffset, PSIZE_T aViewSize,
SECTION_INHERIT aInheritDisposition,
ULONG aAllocationType, ULONG aProtectionFlags)
{
// We always map first, then we check for additional info after.
NTSTATUS stubStatus = stub_NtMapViewOfSection(aSection, aProcess, aBaseAddress,
aZeroBits, aCommitSize,
aSectionOffset, aViewSize,
aInheritDisposition,
aAllocationType, aProtectionFlags);
if (!NT_SUCCESS(stubStatus)) {
return stubStatus;
}
if (aProcess != kCurrentProcess) {
// We're only interested in mapping for the current process.
return stubStatus;
}
// Do a query to see if the memory is MEM_IMAGE. If not, continue
MEMORY_BASIC_INFORMATION mbi;
NTSTATUS ntStatus = ::NtQueryVirtualMemory(aProcess, *aBaseAddress,
MemoryBasicInformation, &mbi,
sizeof(mbi), nullptr);
if (!NT_SUCCESS(ntStatus)) {
::NtUnmapViewOfSection(aProcess, *aBaseAddress);
return STATUS_ACCESS_DENIED;
}
// We don't care about mappings that aren't MEM_IMAGE
if (!(mbi.Type & MEM_IMAGE)) {
return stubStatus;
}
// Get the section name
mozilla::nt::MemorySectionNameBuf buf;
ntStatus = ::NtQueryVirtualMemory(aProcess, *aBaseAddress, MemorySectionName,
&buf, sizeof(buf), nullptr);
if (!NT_SUCCESS(ntStatus)) {
::NtUnmapViewOfSection(aProcess, *aBaseAddress);
return STATUS_ACCESS_DENIED;
}
// Find the leaf name
UNICODE_STRING leaf;
mozilla::nt::GetLeafName(&leaf, &buf.mSectionFileName);
// Check blocklist
if (IsDllAllowed(leaf, *aBaseAddress)) {
return stubStatus;
}
::NtUnmapViewOfSection(aProcess, *aBaseAddress);
return STATUS_ACCESS_DENIED;
}
namespace mozilla {
class MOZ_RAII AutoVirtualProtect final
{
public:
AutoVirtualProtect(void* aAddress, size_t aLength, DWORD aProtFlags,
HANDLE aTargetProcess = nullptr)
: mAddress(aAddress)
, mLength(aLength)
, mTargetProcess(aTargetProcess)
, mPrevProt(0)
{
::VirtualProtectEx(aTargetProcess, aAddress, aLength, aProtFlags,
&mPrevProt);
}
~AutoVirtualProtect()
{
if (!mPrevProt) {
return;
}
::VirtualProtectEx(mTargetProcess, mAddress, mLength, mPrevProt,
&mPrevProt);
}
explicit operator bool() const
{
return !!mPrevProt;
}
AutoVirtualProtect(const AutoVirtualProtect&) = delete;
AutoVirtualProtect(AutoVirtualProtect&&) = delete;
AutoVirtualProtect& operator=(const AutoVirtualProtect&) = delete;
AutoVirtualProtect& operator=(AutoVirtualProtect&&) = delete;
private:
void* mAddress;
size_t mLength;
HANDLE mTargetProcess;
DWORD mPrevProt;
};
bool
InitializeDllBlocklistOOP(HANDLE aChildProcess)
{
mozilla::CrossProcessDllInterceptor intcpt(aChildProcess);
intcpt.Init(L"ntdll.dll");
bool ok = stub_NtMapViewOfSection.SetDetour(aChildProcess, intcpt,
"NtMapViewOfSection",
&patched_NtMapViewOfSection);
if (!ok) {
return false;
}
// Because aChildProcess has just been created in a suspended state, its
// dynamic linker has not yet been initialized, thus its executable has
// not yet been linked with ntdll.dll. If the blocklist hook intercepts a
// library load prior to the link, the hook will be unable to invoke any
// ntdll.dll functions.
//
// We know that the executable for our *current* process's binary is already
// linked into ntdll, so we obtain the IAT from our own executable and graft
// it onto the child process's IAT, thus enabling the child process's hook to
// safely make its ntdll calls.
mozilla::nt::PEHeaders ourExeImage(::GetModuleHandleW(nullptr));
if (!ourExeImage) {
return false;
}
PIMAGE_IMPORT_DESCRIPTOR impDesc = ourExeImage.GetIATForModule("ntdll.dll");
if (!impDesc) {
return false;
}
// This is the pointer we need to write
auto firstIatThunk = ourExeImage.template
RVAToPtr<PIMAGE_THUNK_DATA>(impDesc->FirstThunk);
if (!firstIatThunk) {
return false;
}
// Find the length by iterating through the table until we find a null entry
PIMAGE_THUNK_DATA curIatThunk = firstIatThunk;
while (mozilla::nt::PEHeaders::IsValid(curIatThunk)) {
++curIatThunk;
}
ptrdiff_t iatLength = (curIatThunk - firstIatThunk) * sizeof(IMAGE_THUNK_DATA);
SIZE_T bytesWritten;
{ // Scope for prot
AutoVirtualProtect prot(firstIatThunk, iatLength, PAGE_READWRITE,
aChildProcess);
if (!prot) {
return false;
}
ok = !!::WriteProcessMemory(aChildProcess, firstIatThunk, firstIatThunk,
iatLength, &bytesWritten);
if (!ok) {
return false;
}
}
// Tell the mozglue blocklist that we have bootstrapped
uint32_t newFlags = eDllBlocklistInitFlagWasBootstrapped;
ok = !!::WriteProcessMemory(aChildProcess, &gBlocklistInitFlags, &newFlags,
sizeof(newFlags), &bytesWritten);
return ok;
}
} // namespace mozilla