зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1445025: Part 5 - Implement a Native NT version of the DLL blocklist; r=mhowell
This version of the blocklist should be functionally comparable to the mozglue based blocklist, except: * We hook NtMapViewOfSection instead of LdrLoadDll: The former allows us to easily obtain the module file name being used for the load. The latter requires us to essentially emulate the loader's path searching, which is a perf hit, potentially a correctness issue, and more work to do given the limited native NT API set. * Since the paths in native NT land are all unicode, and since this code is critical to startup performance, this version of the blocklist uses unicode strings instead of ASCII strings. My thoughts here are that we don't want to be wasting time on every DLL load doing ASCII-to-unicode conversion every time we want to do a blocklist string comparison. * I am completely aware that this leaves us in a bizarre situation where we have two copies of the blocklist in our binaries: one unicode version in firefox.exe, and one ASCII version in mozglue.dll. Once we (hopefully) move to using the launcher process by default, the ASCII copy can go away. In the meantime, we need to be able to use either one depending on how Firefox was started. I am happy to make the Native NT blocklist Nightly-only to assuage these concerns.
This commit is contained in:
Родитель
d1efe98e29
Коммит
8630a054fc
|
@ -0,0 +1,431 @@
|
|||
/* -*- 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 DLL_BLOCKLIST_ENTRY(name, ...) \
|
||||
{ L##name, __VA_ARGS__ },
|
||||
#define DLL_BLOCKLIST_CHAR_TYPE wchar_t
|
||||
|
||||
// Restrict the blocklist definitions to Nightly-only for now
|
||||
#if 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 wchar_t* aName, uint64_t aVersion,
|
||||
NativeNtBlockSetEntry* aNext)
|
||||
: mName(aName)
|
||||
, mVersion(aVersion)
|
||||
, mNext(aNext)
|
||||
{}
|
||||
const wchar_t* mName;
|
||||
uint64_t mVersion;
|
||||
NativeNtBlockSetEntry* mNext;
|
||||
};
|
||||
|
||||
public:
|
||||
// Constructor and destructor MUST be trivial
|
||||
NativeNtBlockSet() = default;
|
||||
~NativeNtBlockSet() = default;
|
||||
|
||||
void Add(const wchar_t* aName, uint64_t aVersion);
|
||||
void Write(HANDLE aFile);
|
||||
|
||||
private:
|
||||
static NativeNtBlockSetEntry* NewEntry(const wchar_t* 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 wchar_t* 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 wchar_t* aName, uint64_t aVersion)
|
||||
{
|
||||
::RtlAcquireSRWLockExclusive(&mLock);
|
||||
|
||||
for (NativeNtBlockSetEntry* entry = mFirstEntry; entry; entry = entry->mNext) {
|
||||
// We just need to compare the string pointers, not the strings themselves,
|
||||
// as we always pass in the strings statically defined in the blocklist.
|
||||
if (aName == entry->mName && 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, -1, 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;
|
||||
}
|
||||
|
||||
UNICODE_STRING testStr;
|
||||
DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY(info);
|
||||
while (info->name) {
|
||||
::RtlInitUnicodeString(&testStr, info->name);
|
||||
if (::RtlEqualUnicodeString(&aLeafName, &testStr, TRUE)) {
|
||||
break;
|
||||
}
|
||||
|
||||
++info;
|
||||
}
|
||||
|
||||
uint64_t version;
|
||||
if (info->name && !CheckBlockInfo(info, aBaseAddress, version)) {
|
||||
gBlockSet.Add(info->name, version);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef decltype(&NtMapViewOfSection) NtMapViewOfSection_func;
|
||||
static 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 = intcpt.AddDetour("NtMapViewOfSection",
|
||||
reinterpret_cast<intptr_t>(&patched_NtMapViewOfSection),
|
||||
(void**) &stub_NtMapViewOfSection);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the child process's copy of stub_NtMapViewOfSection
|
||||
SIZE_T bytesWritten;
|
||||
ok = !!::WriteProcessMemory(aChildProcess, &stub_NtMapViewOfSection,
|
||||
&stub_NtMapViewOfSection,
|
||||
sizeof(stub_NtMapViewOfSection), &bytesWritten);
|
||||
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);
|
||||
|
||||
{ // 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
|
|
@ -0,0 +1,18 @@
|
|||
/* -*- 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_DllBlocklistWin_h
|
||||
#define mozilla_DllBlocklistWin_h
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
bool InitializeDllBlocklistOOP(HANDLE aChildProcess);
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_DllBlocklistWin_h
|
|
@ -9,6 +9,7 @@ Library('winlauncher')
|
|||
FORCE_STATIC_LIB = True
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'DllBlocklistWin.cpp',
|
||||
'LauncherProcessWin.cpp',
|
||||
'LaunchUnelevated.cpp',
|
||||
]
|
||||
|
|
Загрузка…
Ссылка в новой задаче