Bug 1503538: Part 3 - Changes to NativeNt and ImportDir to allow for blocking injected static DLL dependencies; r=mhowell

Differential Revision: https://phabricator.services.mozilla.com/D27145

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Aaron Klotz 2019-04-12 19:58:01 +00:00
Родитель 8303fe23c5
Коммит 1a74deabad
4 изменённых файлов: 295 добавлений и 40 удалений

111
mozglue/misc/ImportDir.h Normal file
Просмотреть файл

@ -0,0 +1,111 @@
/* -*- 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/LauncherResult.h"
#include "mozilla/NativeNt.h"
#include "mozilla/WinHeaderOnlyUtils.h"
namespace mozilla {
namespace detail {
inline LauncherResult<nt::DataDirectoryEntry>
GetImageDirectoryViaFileIo(const nsAutoHandle& aImageFile,
const uint32_t aOurImportDirectoryRva) {
OVERLAPPED ov = {};
ov.Offset = aOurImportDirectoryRva;
DWORD bytesRead;
nt::DataDirectoryEntry result;
if (!::ReadFile(aImageFile, &result, sizeof(result), &bytesRead, &ov) ||
bytesRead != sizeof(result)) {
return LAUNCHER_ERROR_FROM_LAST();
}
return result;
}
} // namespace detail
/**
* This function ensures that the import directory of a loaded binary image
* matches the version that is found in the original file on disk. We do this
* to prevent tampering by third-party code.
*
* Yes, this function may perform file I/O on the critical path during
* startup. A mitigating factor here is that this function must be called
* immediately after creating a process using the image specified by
* |aFullImagePath|; by this point, the system has already paid the price of
* pulling the image file's contents into the page cache.
*
* @param aFullImagePath Wide-character string containing the absolute path
* to the binary whose import directory we are touching.
* @param aLocalExeImage The binary's PE headers that have been loaded into our
* process for examination.
* @param aTargetProcess Handle to the child process whose import table we are
* touching.
* @param aRemoteExeImage HMODULE referencing the child process's executable
* binary that we are touching. This value is used to
* determine the base address of the binary within the
* target process.
*/
inline LauncherVoidResult
RestoreImportDirectory(const wchar_t* aFullImagePath,
const nt::PEHeaders& aLocalExeImage,
HANDLE aTargetProcess, HMODULE aRemoteExeImage) {
uint32_t importDirEntryRva;
PIMAGE_DATA_DIRECTORY importDirEntry =
aLocalExeImage.GetImageDirectoryEntryPtr(IMAGE_DIRECTORY_ENTRY_IMPORT,
&importDirEntryRva);
if (!importDirEntry) {
return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
}
nsAutoHandle file(::CreateFileW(aFullImagePath, GENERIC_READ,
FILE_SHARE_READ, nullptr, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, nullptr));
if (file.get() == INVALID_HANDLE_VALUE) {
return LAUNCHER_ERROR_FROM_LAST();
}
// Why do we use file I/O here instead of a memory mapping? The simple reason
// is that we do not want any kernel-mode drivers to start tampering with file
// contents under the belief that the file is being mapped for execution.
// Windows 8 supports creation of file mappings using the SEC_IMAGE_NO_EXECUTE
// flag, which may help to mitigate this, but we might as well just support
// a single implementation that works everywhere.
LauncherResult<nt::DataDirectoryEntry> realImportDirectory =
detail::GetImageDirectoryViaFileIo(file, importDirEntryRva);
if (realImportDirectory.isErr()) {
return LAUNCHER_ERROR_FROM_RESULT(realImportDirectory);
}
nt::DataDirectoryEntry toWrite = realImportDirectory.unwrap();
void* remoteAddress =
reinterpret_cast<char*>(nt::PEHeaders::HModuleToBaseAddr(aRemoteExeImage)) +
importDirEntryRva;
{ // Scope for prot
AutoVirtualProtect prot(remoteAddress,
sizeof(IMAGE_DATA_DIRECTORY),
PAGE_READWRITE, aTargetProcess);
if (!prot) {
return LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(prot.GetError());
}
SIZE_T bytesWritten;
if (!::WriteProcessMemory(aTargetProcess, remoteAddress,
&toWrite, sizeof(IMAGE_DATA_DIRECTORY),
&bytesWritten) ||
bytesWritten != sizeof(IMAGE_DATA_DIRECTORY)) {
return LAUNCHER_ERROR_FROM_LAST();
}
}
return Ok();
}
} // namespace mozilla

Просмотреть файл

@ -79,6 +79,10 @@ VOID NTAPI RtlAcquireSRWLockExclusive(PSRWLOCK aLock);
VOID NTAPI RtlReleaseSRWLockExclusive(PSRWLOCK aLock);
NTSTATUS NTAPI NtReadVirtualMemory(HANDLE aProcessHandle, PVOID aBaseAddress,
PVOID aBuffer, SIZE_T aNumBytesToRead,
PSIZE_T aNumBytesRead);
} // extern "C"
#endif // !defined(MOZILLA_INTERNAL_API)
@ -244,15 +248,45 @@ class MOZ_RAII PEHeaders final {
};
public:
explicit PEHeaders(void* aBaseAddress)
: PEHeaders(reinterpret_cast<PIMAGE_DOS_HEADER>(aBaseAddress)) {}
// The lowest two bits of an HMODULE are used as flags. Stripping those bits
// from the HMODULE yields the base address of the binary's memory mapping.
// (See LoadLibraryEx docs on MSDN)
static PIMAGE_DOS_HEADER HModuleToBaseAddr(HMODULE aModule) {
return reinterpret_cast<PIMAGE_DOS_HEADER>(
reinterpret_cast<uintptr_t>(aModule) & ~uintptr_t(3));
}
explicit PEHeaders(void* aBaseAddress)
: PEHeaders(reinterpret_cast<PIMAGE_DOS_HEADER>(aBaseAddress)) {}
explicit PEHeaders(HMODULE aModule)
: PEHeaders(reinterpret_cast<PIMAGE_DOS_HEADER>(
reinterpret_cast<uintptr_t>(aModule) & ~uintptr_t(3))) {}
: PEHeaders(HModuleToBaseAddr(aModule)) {}
explicit PEHeaders(PIMAGE_DOS_HEADER aMzHeader)
: mMzHeader(aMzHeader), mPeHeader(nullptr), mImageLimit(nullptr) {
if (!mMzHeader || mMzHeader->e_magic != IMAGE_DOS_SIGNATURE) {
return;
}
mPeHeader = RVAToPtrUnchecked<PIMAGE_NT_HEADERS>(mMzHeader->e_lfanew);
if (!mPeHeader || mPeHeader->Signature != IMAGE_NT_SIGNATURE) {
return;
}
if (mPeHeader->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC) {
return;
}
DWORD imageSize = mPeHeader->OptionalHeader.SizeOfImage;
// This is a coarse-grained check to ensure that the image size is
// reasonable. It we aren't big enough to contain headers, we have a
// problem!
if (imageSize < sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS)) {
return;
}
mImageLimit = RVAToPtrUnchecked<void*>(imageSize - 1UL);
}
explicit operator bool() const { return !!mImageLimit; }
@ -295,6 +329,32 @@ class MOZ_RAII PEHeaders final {
IMAGE_DIRECTORY_ENTRY_RESOURCE);
}
PIMAGE_DATA_DIRECTORY GetImageDirectoryEntryPtr(
const uint32_t aDirectoryIndex,
uint32_t* aOutRva = nullptr) const {
if (aOutRva) {
*aOutRva = 0;
}
IMAGE_OPTIONAL_HEADER& optionalHeader = mPeHeader->OptionalHeader;
const uint32_t maxIndex = std::min(optionalHeader.NumberOfRvaAndSizes,
DWORD(IMAGE_NUMBEROF_DIRECTORY_ENTRIES));
if (aDirectoryIndex >= maxIndex) {
return nullptr;
}
PIMAGE_DATA_DIRECTORY dirEntry =
&optionalHeader.DataDirectory[aDirectoryIndex];
if (aOutRva) {
*aOutRva = reinterpret_cast<char*>(dirEntry) -
reinterpret_cast<char*>(mMzHeader);
MOZ_ASSERT(*aOutRva);
}
return dirEntry;
}
bool GetVersionInfo(uint64_t& aOutVersion) {
// RT_VERSION == 16
// Version resources require an id of 1
@ -342,6 +402,40 @@ class MOZ_RAII PEHeaders final {
return nullptr;
}
struct IATThunks {
IATThunks(PIMAGE_THUNK_DATA aFirstThunk, ptrdiff_t aNumThunks)
: mFirstThunk(aFirstThunk), mNumThunks(aNumThunks) {}
size_t Length() const {
return size_t(mNumThunks) * sizeof(IMAGE_THUNK_DATA);
}
PIMAGE_THUNK_DATA mFirstThunk;
ptrdiff_t mNumThunks;
};
Maybe<IATThunks> GetIATThunksForModule(const char* aModuleNameASCII) {
PIMAGE_IMPORT_DESCRIPTOR impDesc = GetIATForModule(aModuleNameASCII);
if (!impDesc) {
return Nothing();
}
auto firstIatThunk =
this->template RVAToPtr<PIMAGE_THUNK_DATA>(impDesc->FirstThunk);
if (!firstIatThunk) {
return Nothing();
}
// Find the length by iterating through the table until we find a null entry
PIMAGE_THUNK_DATA curIatThunk = firstIatThunk;
while (IsValid(curIatThunk)) {
++curIatThunk;
}
ptrdiff_t thunkCount = curIatThunk - firstIatThunk;
return Some(IATThunks(firstIatThunk, thunkCount));
}
/**
* Resources are stored in a three-level tree. To locate a particular entry,
* you must supply a resource type, the resource id, and then the language id.
@ -394,48 +488,17 @@ class MOZ_RAII PEHeaders final {
}
private:
explicit PEHeaders(PIMAGE_DOS_HEADER aMzHeader)
: mMzHeader(aMzHeader), mPeHeader(nullptr), mImageLimit(nullptr) {
if (!mMzHeader || mMzHeader->e_magic != IMAGE_DOS_SIGNATURE) {
return;
}
mPeHeader = RVAToPtrUnchecked<PIMAGE_NT_HEADERS>(mMzHeader->e_lfanew);
if (!mPeHeader || mPeHeader->Signature != IMAGE_NT_SIGNATURE) {
return;
}
if (mPeHeader->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC) {
return;
}
DWORD imageSize = mPeHeader->OptionalHeader.SizeOfImage;
// This is a coarse-grained check to ensure that the image size is
// reasonable. It we aren't big enough to contain headers, we have a
// problem!
if (imageSize < sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS)) {
return;
}
mImageLimit = RVAToPtrUnchecked<void*>(imageSize - 1UL);
}
template <typename T>
T GetImageDirectoryEntry(const uint32_t aDirectoryIndex) {
IMAGE_OPTIONAL_HEADER& optionalHeader = mPeHeader->OptionalHeader;
const uint32_t maxIndex = std::min(optionalHeader.NumberOfRvaAndSizes,
DWORD(IMAGE_NUMBEROF_DIRECTORY_ENTRIES));
if (aDirectoryIndex >= maxIndex) {
PIMAGE_DATA_DIRECTORY dirEntry = GetImageDirectoryEntryPtr(aDirectoryIndex);
if (!dirEntry) {
return nullptr;
}
IMAGE_DATA_DIRECTORY& dirEntry =
optionalHeader.DataDirectory[aDirectoryIndex];
return RVAToPtr<T>(dirEntry.VirtualAddress);
return RVAToPtr<T>(dirEntry->VirtualAddress);
}
// This private overload does not have bounds checks, because we need to be
// This private variant does not have bounds checks, because we need to be
// able to resolve the bounds themselves.
template <typename T, typename R>
T RVAToPtrUnchecked(R aRva) {
@ -547,6 +610,83 @@ inline LauncherResult<DWORD> GetParentProcessId() {
return static_cast<DWORD>(pbi.InheritedFromUniqueProcessId & 0xFFFFFFFF);
}
struct DataDirectoryEntry : public _IMAGE_DATA_DIRECTORY {
DataDirectoryEntry() : _IMAGE_DATA_DIRECTORY() {
}
MOZ_IMPLICIT DataDirectoryEntry(const _IMAGE_DATA_DIRECTORY& aOther)
: _IMAGE_DATA_DIRECTORY(aOther) {
}
DataDirectoryEntry(const DataDirectoryEntry& aOther) = default;
};
inline LauncherResult<void*> GetProcessPebPtr(HANDLE aProcess) {
ULONG returnLength;
PROCESS_BASIC_INFORMATION pbi;
NTSTATUS status = ::NtQueryInformationProcess(aProcess,
ProcessBasicInformation, &pbi, sizeof(pbi), &returnLength);
if (!NT_SUCCESS(status)) {
return LAUNCHER_ERROR_FROM_NTSTATUS(status);
}
return pbi.PebBaseAddress;
}
/**
* This function relies on a specific offset into the mostly-undocumented PEB
* structure. The risk is reduced thanks to the fact that the Chromium sandbox
* relies on the location of this field. It is unlikely to change at this point.
* To further reduce the risk, we also check for the magic 'MZ' signature that
* should indicate the beginning of a PE image.
*/
inline LauncherResult<HMODULE> GetProcessExeModule(HANDLE aProcess) {
LauncherResult<void*> ppeb = GetProcessPebPtr(aProcess);
if (ppeb.isErr()) {
return LAUNCHER_ERROR_FROM_RESULT(ppeb);
}
PEB peb;
SIZE_T bytesRead;
#if defined(MOZILLA_INTERNAL_API)
if (!::ReadProcessMemory(aProcess, ppeb.unwrap(), &peb, sizeof(peb),
&bytesRead) || bytesRead != sizeof(peb)) {
return LAUNCHER_ERROR_FROM_LAST();
}
#else
NTSTATUS ntStatus = ::NtReadVirtualMemory(aProcess, ppeb.unwrap(), &peb,
sizeof(peb), &bytesRead);
if (!NT_SUCCESS(ntStatus) || bytesRead != sizeof(peb)) {
return LAUNCHER_ERROR_FROM_NTSTATUS(ntStatus);
}
#endif
// peb.ImageBaseAddress
void* baseAddress = peb.Reserved3[1];
char mzMagic[2];
#if defined(MOZILLA_INTERNAL_API)
if (!::ReadProcessMemory(aProcess, baseAddress, mzMagic, sizeof(mzMagic),
&bytesRead) || bytesRead != sizeof(mzMagic)) {
return LAUNCHER_ERROR_FROM_LAST();
}
#else
ntStatus = ::NtReadVirtualMemory(aProcess, baseAddress, mzMagic,
sizeof(mzMagic), &bytesRead);
if (!NT_SUCCESS(ntStatus) || bytesRead != sizeof(mzMagic)) {
return LAUNCHER_ERROR_FROM_NTSTATUS(ntStatus);
}
#endif
MOZ_ASSERT(mzMagic[0] == 'M' && mzMagic[1] == 'Z');
if (mzMagic[0] != 'M' || mzMagic[1] != 'Z') {
return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
}
return static_cast<HMODULE>(baseAddress);
}
} // namespace nt
} // namespace mozilla

Просмотреть файл

@ -45,6 +45,7 @@ if CONFIG['OS_ARCH'] == 'WINNT':
]
EXPORTS.mozilla += [
'DynamicallyLinkedFunctionPtr.h',
'ImportDir.h',
'NativeNt.h',
'WindowsMapRemoteView.h',
]

Просмотреть файл

@ -66,6 +66,9 @@ using LauncherVoidResult = LauncherResult<Ok>;
# define LAUNCHER_ERROR_FROM_HRESULT(hresult) \
::mozilla::Err(::mozilla::WindowsError::FromHResult(hresult))
# define LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(err) \
::mozilla::Err(err)
#else
# define LAUNCHER_ERROR_GENERIC() \