зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
8303fe23c5
Коммит
1a74deabad
|
@ -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() \
|
||||
|
|
Загрузка…
Ссылка в новой задаче