mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-26 12:20:56 +00:00
Bug 1451511: Add cross-process function hooking to DLL interceptor; r=handyman
--HG-- rename : ipc/mscom/DynamicallyLinkedFunctionPtr.h => mozglue/misc/DynamicallyLinkedFunctionPtr.h extra : amend_source : 1eea43cda6e05f722f0b1373535d9ceabac18661
This commit is contained in:
parent
9e9c6ca596
commit
0850bc3ec5
@ -6,8 +6,8 @@
|
||||
|
||||
#include "mozilla/mscom/AgileReference.h"
|
||||
|
||||
#include "DynamicallyLinkedFunctionPtr.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/DynamicallyLinkedFunctionPtr.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Move.h"
|
||||
|
||||
|
@ -4,14 +4,13 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_mscom_DynamicallyLinkedFunctionPtr_h
|
||||
#define mozilla_mscom_DynamicallyLinkedFunctionPtr_h
|
||||
#ifndef mozilla_DynamicallyLinkedFunctionPtr_h
|
||||
#define mozilla_DynamicallyLinkedFunctionPtr_h
|
||||
|
||||
#include "mozilla/Move.h"
|
||||
#include <windows.h>
|
||||
|
||||
namespace mozilla {
|
||||
namespace mscom {
|
||||
|
||||
template <typename T>
|
||||
class DynamicallyLinkedFunctionPtr;
|
||||
@ -68,8 +67,7 @@ private:
|
||||
FunctionPtrT mFunction;
|
||||
};
|
||||
|
||||
} // namespace mscom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_mscom_DynamicallyLinkedFunctionPtr_h
|
||||
#endif // mozilla_DynamicallyLinkedFunctionPtr_h
|
||||
|
131
mozglue/misc/WindowsMapRemoteView.cpp
Normal file
131
mozglue/misc/WindowsMapRemoteView.cpp
Normal file
@ -0,0 +1,131 @@
|
||||
/* -*- 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/WindowsMapRemoteView.h"
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/DynamicallyLinkedFunctionPtr.h"
|
||||
|
||||
#include <winternl.h>
|
||||
|
||||
#if (NTDDI_VERSION < NTDDI_WIN10_RS2)
|
||||
|
||||
// MapViewOfFile2 is just an inline function that calls MapViewOfFileNuma2 with
|
||||
// its preferred node set to NUMA_NO_PREFERRED_NODE
|
||||
PVOID WINAPI
|
||||
MapViewOfFileNuma2(HANDLE aFileMapping, HANDLE aProcess, ULONG64 aOffset,
|
||||
PVOID aBaseAddress, SIZE_T aViewSize, ULONG aAllocationType,
|
||||
ULONG aPageProtection, ULONG aPreferredNode);
|
||||
|
||||
BOOL WINAPI
|
||||
UnmapViewOfFile2(HANDLE aProcess, PVOID aBaseAddress, ULONG aUnmapFlags);
|
||||
|
||||
#endif // (NTDDI_VERSION < NTDDI_WIN10_RS2)
|
||||
|
||||
enum SECTION_INHERIT
|
||||
{
|
||||
ViewShare = 1,
|
||||
ViewUnmap = 2
|
||||
};
|
||||
|
||||
NTSTATUS NTAPI
|
||||
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);
|
||||
|
||||
NTSTATUS NTAPI
|
||||
NtUnmapViewOfSection(HANDLE aProcess, PVOID aBaseAddress);
|
||||
|
||||
static DWORD
|
||||
GetWin32ErrorCode(NTSTATUS aNtStatus)
|
||||
{
|
||||
static const mozilla::DynamicallyLinkedFunctionPtr<decltype(&RtlNtStatusToDosError)>
|
||||
pRtlNtStatusToDosError(L"ntdll.dll", "RtlNtStatusToDosError");
|
||||
|
||||
MOZ_ASSERT(!!pRtlNtStatusToDosError);
|
||||
if (!pRtlNtStatusToDosError) {
|
||||
return ERROR_GEN_FAILURE;
|
||||
}
|
||||
|
||||
return pRtlNtStatusToDosError(aNtStatus);
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
MFBT_API void*
|
||||
MapRemoteViewOfFile(HANDLE aFileMapping, HANDLE aProcess, ULONG64 aOffset,
|
||||
PVOID aBaseAddress, SIZE_T aViewSize, ULONG aAllocationType,
|
||||
ULONG aProtectionFlags)
|
||||
{
|
||||
static const DynamicallyLinkedFunctionPtr<decltype(&MapViewOfFileNuma2)>
|
||||
pMapViewOfFileNuma2(L"Api-ms-win-core-memory-l1-1-5.dll", "MapViewOfFileNuma2");
|
||||
|
||||
if (!!pMapViewOfFileNuma2) {
|
||||
return pMapViewOfFileNuma2(aFileMapping, aProcess, aOffset, aBaseAddress,
|
||||
aViewSize, aAllocationType, aProtectionFlags,
|
||||
NUMA_NO_PREFERRED_NODE);
|
||||
}
|
||||
|
||||
static const DynamicallyLinkedFunctionPtr<decltype(&NtMapViewOfSection)>
|
||||
pNtMapViewOfSection(L"ntdll.dll", "NtMapViewOfSection");
|
||||
|
||||
MOZ_ASSERT(!!pNtMapViewOfSection);
|
||||
if (!pNtMapViewOfSection) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// For the sake of consistency, we only permit the same flags that
|
||||
// MapViewOfFileNuma2 allows
|
||||
if (aAllocationType != 0 && aAllocationType != MEM_RESERVE &&
|
||||
aAllocationType != MEM_LARGE_PAGES) {
|
||||
::SetLastError(ERROR_INVALID_PARAMETER);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NTSTATUS ntStatus;
|
||||
|
||||
LARGE_INTEGER offset;
|
||||
offset.QuadPart = aOffset;
|
||||
|
||||
ntStatus = pNtMapViewOfSection(aFileMapping, aProcess, &aBaseAddress, 0, 0,
|
||||
&offset, &aViewSize, ViewUnmap,
|
||||
aAllocationType, aProtectionFlags);
|
||||
if (NT_SUCCESS(ntStatus)) {
|
||||
::SetLastError(ERROR_SUCCESS);
|
||||
return aBaseAddress;
|
||||
}
|
||||
|
||||
::SetLastError(GetWin32ErrorCode(ntStatus));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MFBT_API bool
|
||||
UnmapRemoteViewOfFile(HANDLE aProcess, PVOID aBaseAddress)
|
||||
{
|
||||
static const DynamicallyLinkedFunctionPtr<decltype(&UnmapViewOfFile2)>
|
||||
pUnmapViewOfFile2(L"kernel32.dll", "UnmapViewOfFile2");
|
||||
|
||||
if (!!pUnmapViewOfFile2) {
|
||||
return !!pUnmapViewOfFile2(aProcess, aBaseAddress, 0);
|
||||
}
|
||||
|
||||
static const DynamicallyLinkedFunctionPtr<decltype(&NtUnmapViewOfSection)>
|
||||
pNtUnmapViewOfSection(L"ntdll.dll", "NtUnmapViewOfSection");
|
||||
|
||||
MOZ_ASSERT(!!pNtUnmapViewOfSection);
|
||||
if (!pNtUnmapViewOfSection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NTSTATUS ntStatus = pNtUnmapViewOfSection(aProcess, aBaseAddress);
|
||||
::SetLastError(GetWin32ErrorCode(ntStatus));
|
||||
return NT_SUCCESS(ntStatus);
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
26
mozglue/misc/WindowsMapRemoteView.h
Normal file
26
mozglue/misc/WindowsMapRemoteView.h
Normal file
@ -0,0 +1,26 @@
|
||||
/* -*- 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_WindowsMapRemoteView_h
|
||||
#define mozilla_WindowsMapRemoteView_h
|
||||
|
||||
#include "mozilla/Types.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
MFBT_API PVOID
|
||||
MapRemoteViewOfFile(HANDLE aFileMapping, HANDLE aProcess, ULONG64 aOffset,
|
||||
PVOID aBaseAddress, SIZE_T aViewSize, ULONG aAllocationType,
|
||||
ULONG aProtectionFlags);
|
||||
|
||||
MFBT_API bool
|
||||
UnmapRemoteViewOfFile(HANDLE aProcess, PVOID aBaseAddress);
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_WindowsMapRemoteView_h
|
@ -9,6 +9,7 @@
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Types.h"
|
||||
#include "mozilla/WindowsMapRemoteView.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
@ -127,7 +128,12 @@ public:
|
||||
MOZ_ASSERT(aPrevProtFlags);
|
||||
BOOL ok = ::VirtualProtect(aVAddress, aSize, aProtFlags,
|
||||
reinterpret_cast<PDWORD>(aPrevProtFlags));
|
||||
MOZ_ASSERT(ok);
|
||||
if (!ok && aPrevProtFlags) {
|
||||
// VirtualProtect can fail but still set valid protection flags.
|
||||
// Let's clear those upon failure.
|
||||
*aPrevProtFlags = 0;
|
||||
}
|
||||
|
||||
return !!ok;
|
||||
}
|
||||
|
||||
@ -217,6 +223,270 @@ private:
|
||||
uint32_t mCommitOffset;
|
||||
};
|
||||
|
||||
class MMPolicyOutOfProcess : public MMPolicyBase
|
||||
{
|
||||
public:
|
||||
typedef MMPolicyOutOfProcess MMPolicyT;
|
||||
|
||||
explicit MMPolicyOutOfProcess(HANDLE aProcess)
|
||||
: mProcess(nullptr)
|
||||
, mMapping(nullptr)
|
||||
, mLocalView(nullptr)
|
||||
, mRemoteView(nullptr)
|
||||
, mReservationSize(0)
|
||||
, mCommitOffset(0)
|
||||
{
|
||||
MOZ_ASSERT(aProcess);
|
||||
::DuplicateHandle(::GetCurrentProcess(), aProcess, ::GetCurrentProcess(),
|
||||
&mProcess, kAccessFlags, FALSE, 0);
|
||||
MOZ_ASSERT(mProcess);
|
||||
}
|
||||
|
||||
explicit MMPolicyOutOfProcess(DWORD aPid)
|
||||
: mProcess(::OpenProcess(kAccessFlags, FALSE, aPid))
|
||||
, mMapping(nullptr)
|
||||
, mLocalView(nullptr)
|
||||
, mRemoteView(nullptr)
|
||||
, mReservationSize(0)
|
||||
, mCommitOffset(0)
|
||||
{
|
||||
MOZ_ASSERT(mProcess);
|
||||
}
|
||||
|
||||
~MMPolicyOutOfProcess()
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
|
||||
MMPolicyOutOfProcess(MMPolicyOutOfProcess&& aOther)
|
||||
: mProcess(nullptr)
|
||||
, mMapping(nullptr)
|
||||
, mLocalView(nullptr)
|
||||
, mRemoteView(nullptr)
|
||||
, mReservationSize(0)
|
||||
, mCommitOffset(0)
|
||||
{
|
||||
*this = Move(aOther);
|
||||
}
|
||||
|
||||
MMPolicyOutOfProcess(const MMPolicyOutOfProcess& aOther) = delete;
|
||||
MMPolicyOutOfProcess& operator=(const MMPolicyOutOfProcess&) = delete;
|
||||
|
||||
MMPolicyOutOfProcess& operator=(MMPolicyOutOfProcess&& aOther)
|
||||
{
|
||||
Destroy();
|
||||
|
||||
mProcess = aOther.mProcess;
|
||||
aOther.mProcess = nullptr;
|
||||
|
||||
mMapping = aOther.mMapping;
|
||||
aOther.mMapping = nullptr;
|
||||
|
||||
mLocalView = aOther.mLocalView;
|
||||
aOther.mLocalView = nullptr;
|
||||
|
||||
mRemoteView = aOther.mRemoteView;
|
||||
aOther.mRemoteView = nullptr;
|
||||
|
||||
mReservationSize = aOther.mReservationSize;
|
||||
aOther.mReservationSize = 0;
|
||||
|
||||
mCommitOffset = aOther.mCommitOffset;
|
||||
aOther.mCommitOffset = 0;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit operator bool() const
|
||||
{
|
||||
return mProcess && mMapping && mLocalView && mRemoteView;
|
||||
}
|
||||
|
||||
bool ShouldUnhookUponDestruction() const
|
||||
{
|
||||
// We don't clean up hooks for remote processes; they are expected to
|
||||
// outlive our process.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Read(void* aToPtr, const void* aFromPtr, size_t aLen) const
|
||||
{
|
||||
MOZ_ASSERT(mProcess);
|
||||
if (!mProcess) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SIZE_T numBytes = 0;
|
||||
BOOL ok = ::ReadProcessMemory(mProcess, aFromPtr, aToPtr, aLen, &numBytes);
|
||||
return ok && numBytes == aLen;
|
||||
}
|
||||
|
||||
bool Write(void* aToPtr, const void* aFromPtr, size_t aLen) const
|
||||
{
|
||||
MOZ_ASSERT(mProcess);
|
||||
if (!mProcess) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SIZE_T numBytes = 0;
|
||||
BOOL ok = ::WriteProcessMemory(mProcess, aToPtr, aFromPtr, aLen, &numBytes);
|
||||
return ok && numBytes == aLen;
|
||||
}
|
||||
|
||||
bool Protect(void* aVAddress, size_t aSize, uint32_t aProtFlags,
|
||||
uint32_t* aPrevProtFlags) const
|
||||
{
|
||||
MOZ_ASSERT(mProcess);
|
||||
if (!mProcess) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(aPrevProtFlags);
|
||||
BOOL ok = ::VirtualProtectEx(mProcess, aVAddress, aSize, aProtFlags,
|
||||
reinterpret_cast<PDWORD>(aPrevProtFlags));
|
||||
if (!ok && aPrevProtFlags) {
|
||||
// VirtualProtectEx can fail but still set valid protection flags.
|
||||
// Let's clear those upon failure.
|
||||
*aPrevProtFlags = 0;
|
||||
}
|
||||
|
||||
return !!ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the page that hosts aVAddress is accessible.
|
||||
*/
|
||||
bool IsPageAccessible(void* aVAddress) const
|
||||
{
|
||||
MEMORY_BASIC_INFORMATION mbi;
|
||||
SIZE_T result = ::VirtualQueryEx(mProcess, aVAddress, &mbi, sizeof(mbi));
|
||||
|
||||
return result && mbi.AllocationProtect && (mbi.Type & MEM_IMAGE) &&
|
||||
mbi.State == MEM_COMMIT && mbi.Protect != PAGE_NOACCESS;
|
||||
}
|
||||
|
||||
bool FlushInstructionCache() const
|
||||
{
|
||||
return !!::FlushInstructionCache(mProcess, nullptr, 0);
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8_t* GetLocalView() const
|
||||
{
|
||||
return mLocalView;
|
||||
}
|
||||
|
||||
uintptr_t GetRemoteView() const
|
||||
{
|
||||
return reinterpret_cast<uintptr_t>(mRemoteView);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the effective number of bytes reserved, or 0 on failure
|
||||
*/
|
||||
uint32_t Reserve(const uint32_t aSize)
|
||||
{
|
||||
if (!aSize || !mProcess) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (mRemoteView) {
|
||||
MOZ_ASSERT(mReservationSize >= aSize);
|
||||
return mReservationSize;
|
||||
}
|
||||
|
||||
mReservationSize = ComputeAllocationSize(aSize);
|
||||
|
||||
mMapping = ::CreateFileMapping(INVALID_HANDLE_VALUE, nullptr,
|
||||
PAGE_EXECUTE_READWRITE | SEC_RESERVE,
|
||||
0, mReservationSize, nullptr);
|
||||
if (!mMapping) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
mLocalView = static_cast<uint8_t*>(
|
||||
::MapViewOfFile(mMapping, FILE_MAP_WRITE, 0, 0, 0));
|
||||
if (!mLocalView) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
mRemoteView = MapRemoteViewOfFile(mMapping, mProcess, 0ULL,
|
||||
nullptr, 0, 0, PAGE_EXECUTE_READ);
|
||||
if (!mRemoteView) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return mReservationSize;
|
||||
}
|
||||
|
||||
bool MaybeCommitNextPage(const uint32_t aRequestedOffset,
|
||||
const uint32_t aRequestedLength)
|
||||
{
|
||||
if (!(*this)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t limit = aRequestedOffset + aRequestedLength - 1;
|
||||
if (limit < mCommitOffset) {
|
||||
// No commit required
|
||||
return true;
|
||||
}
|
||||
|
||||
MOZ_DIAGNOSTIC_ASSERT(mCommitOffset < mReservationSize);
|
||||
if (mCommitOffset >= mReservationSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PVOID local = ::VirtualAlloc(mLocalView + mCommitOffset, GetPageSize(),
|
||||
MEM_COMMIT, PAGE_READWRITE);
|
||||
if (!local) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PVOID remote = ::VirtualAllocEx(mProcess,
|
||||
static_cast<uint8_t*>(mRemoteView) +
|
||||
mCommitOffset, GetPageSize(),
|
||||
MEM_COMMIT, PAGE_EXECUTE_READ);
|
||||
if (!remote) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mCommitOffset += GetPageSize();
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
void Destroy()
|
||||
{
|
||||
// We always leak the remote view
|
||||
if (mLocalView) {
|
||||
::UnmapViewOfFile(mLocalView);
|
||||
mLocalView = nullptr;
|
||||
}
|
||||
|
||||
if (mMapping) {
|
||||
::CloseHandle(mMapping);
|
||||
mMapping = nullptr;
|
||||
}
|
||||
|
||||
if (mProcess) {
|
||||
::CloseHandle(mProcess);
|
||||
mProcess = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
HANDLE mProcess;
|
||||
HANDLE mMapping;
|
||||
uint8_t* mLocalView;
|
||||
PVOID mRemoteView;
|
||||
uint32_t mReservationSize;
|
||||
uint32_t mCommitOffset;
|
||||
|
||||
static const DWORD kAccessFlags = PROCESS_VM_OPERATION | PROCESS_VM_READ |
|
||||
PROCESS_VM_WRITE;
|
||||
};
|
||||
|
||||
} // namespace interceptor
|
||||
} // namespace mozilla
|
||||
|
||||
|
@ -226,6 +226,13 @@ private:
|
||||
uint32_t mOffset;
|
||||
uint32_t mStartWriteOffset;
|
||||
uint32_t mPrevProt;
|
||||
|
||||
// In an ideal world, we'd only read 5 bytes on 32-bit and 13 bytes on 64-bit,
|
||||
// to match the minimum bytes that we need to write in in order to patch the
|
||||
// target function. Since the actual opcodes will often require us to pull in
|
||||
// extra bytes above that minimum, we set the inline storage to be larger than
|
||||
// those minima in an effort to give the Vector extra wiggle room before it
|
||||
// needs to touch the heap.
|
||||
#if defined(_M_IX86)
|
||||
static const size_t kInlineStorage = 16;
|
||||
#elif defined(_M_X64)
|
||||
@ -288,11 +295,26 @@ public:
|
||||
return mMMPolicy.IsPageAccessible(reinterpret_cast<void*>(adjusted));
|
||||
}
|
||||
|
||||
const uint8_t* Get() const
|
||||
/**
|
||||
* This returns a pointer to a *potentially local copy* of the target
|
||||
* function's bytes. The returned pointer should not be used for any
|
||||
* pointer arithmetic relating to the target function.
|
||||
*/
|
||||
const uint8_t* GetLocalBytes() const
|
||||
{
|
||||
return mBase;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns a pointer to the target function's bytes. The returned pointer
|
||||
* may possibly belong to another process, so while it should be used for
|
||||
* pointer arithmetic, it *must not* be dereferenced.
|
||||
*/
|
||||
uintptr_t GetBase() const
|
||||
{
|
||||
return reinterpret_cast<uintptr_t>(mBase);
|
||||
}
|
||||
|
||||
const MMPolicyInProcess& GetMMPolicy() const
|
||||
{
|
||||
return mMMPolicy;
|
||||
@ -306,16 +328,159 @@ private:
|
||||
uint8_t const * const mBase;
|
||||
};
|
||||
|
||||
template <>
|
||||
class ReadOnlyTargetBytes<MMPolicyOutOfProcess>
|
||||
{
|
||||
public:
|
||||
ReadOnlyTargetBytes(const MMPolicyOutOfProcess& aMMPolicy, const void* aBase)
|
||||
: mMMPolicy(aMMPolicy)
|
||||
, mBase(reinterpret_cast<const uint8_t*>(aBase))
|
||||
{
|
||||
}
|
||||
|
||||
ReadOnlyTargetBytes(ReadOnlyTargetBytes&& aOther)
|
||||
: mMMPolicy(aOther.mMMPolicy)
|
||||
, mLocalBytes(Move(aOther.mLocalBytes))
|
||||
, mBase(aOther.mBase)
|
||||
{
|
||||
}
|
||||
|
||||
ReadOnlyTargetBytes(const ReadOnlyTargetBytes& aOther)
|
||||
: mMMPolicy(aOther.mMMPolicy)
|
||||
, mBase(aOther.mBase)
|
||||
{
|
||||
mLocalBytes.appendAll(aOther.mLocalBytes);
|
||||
}
|
||||
|
||||
ReadOnlyTargetBytes(const ReadOnlyTargetBytes& aOther,
|
||||
const uint32_t aOffsetFromOther)
|
||||
: mMMPolicy(aOther.mMMPolicy)
|
||||
, mBase(aOther.mBase + aOffsetFromOther)
|
||||
{
|
||||
if (aOffsetFromOther >= aOther.mLocalBytes.length()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mLocalBytes.append(aOther.mLocalBytes.begin() + aOffsetFromOther,
|
||||
aOther.mLocalBytes.end());
|
||||
}
|
||||
|
||||
void EnsureLimit(uint32_t aDesiredLimit)
|
||||
{
|
||||
size_t prevSize = mLocalBytes.length();
|
||||
if (aDesiredLimit < prevSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t newSize = aDesiredLimit + 1;
|
||||
if (newSize < kInlineStorage) {
|
||||
// Always try to read as much memory as we can at once
|
||||
newSize = kInlineStorage;
|
||||
}
|
||||
|
||||
bool resizeOk = mLocalBytes.resize(newSize);
|
||||
MOZ_RELEASE_ASSERT(resizeOk);
|
||||
|
||||
bool ok = mMMPolicy.Read(&mLocalBytes[prevSize], mBase + prevSize,
|
||||
newSize - prevSize);
|
||||
if (ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We couldn't pull more bytes than needed (which may happen if those extra
|
||||
// bytes are not accessible). In this case, we try just to get the bare
|
||||
// minimum.
|
||||
newSize = aDesiredLimit + 1;
|
||||
resizeOk = mLocalBytes.resize(newSize);
|
||||
MOZ_RELEASE_ASSERT(resizeOk);
|
||||
|
||||
ok = mMMPolicy.Read(&mLocalBytes[prevSize], mBase + prevSize,
|
||||
newSize - prevSize);
|
||||
MOZ_RELEASE_ASSERT(ok);
|
||||
}
|
||||
|
||||
bool IsValidAtOffset(const int8_t aOffset) const
|
||||
{
|
||||
if (!aOffset) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uintptr_t base = reinterpret_cast<uintptr_t>(mBase);
|
||||
uintptr_t adjusted = base + aOffset;
|
||||
uint32_t pageSize = mMMPolicy.GetPageSize();
|
||||
|
||||
// If |adjusted| is within the same page as |mBase|, we're still valid
|
||||
if ((base / pageSize) == (adjusted / pageSize)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, let's query |adjusted|
|
||||
return mMMPolicy.IsPageAccessible(reinterpret_cast<void*>(adjusted));
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns a pointer to a *potentially local copy* of the target
|
||||
* function's bytes. The returned pointer should not be used for any
|
||||
* pointer arithmetic relating to the target function.
|
||||
*/
|
||||
const uint8_t* GetLocalBytes() const
|
||||
{
|
||||
if (mLocalBytes.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return mLocalBytes.begin();
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns a pointer to the target function's bytes. The returned pointer
|
||||
* may possibly belong to another process, so while it should be used for
|
||||
* pointer arithmetic, it *must not* be dereferenced.
|
||||
*/
|
||||
uintptr_t GetBase() const
|
||||
{
|
||||
return reinterpret_cast<uintptr_t>(mBase);
|
||||
}
|
||||
|
||||
const MMPolicyOutOfProcess& GetMMPolicy() const
|
||||
{
|
||||
return mMMPolicy;
|
||||
}
|
||||
|
||||
ReadOnlyTargetBytes& operator=(const ReadOnlyTargetBytes&) = delete;
|
||||
ReadOnlyTargetBytes& operator=(ReadOnlyTargetBytes&&) = delete;
|
||||
|
||||
private:
|
||||
// In an ideal world, we'd only read 5 bytes on 32-bit and 13 bytes on 64-bit,
|
||||
// to match the minimum bytes that we need to write in in order to patch the
|
||||
// target function. Since the actual opcodes will often require us to pull in
|
||||
// extra bytes above that minimum, we set the inline storage to be larger than
|
||||
// those minima in an effort to give the Vector extra wiggle room before it
|
||||
// needs to touch the heap.
|
||||
#if defined(_M_IX86)
|
||||
static const size_t kInlineStorage = 16;
|
||||
#elif defined(_M_X64)
|
||||
static const size_t kInlineStorage = 32;
|
||||
#endif
|
||||
|
||||
const MMPolicyOutOfProcess& mMMPolicy;
|
||||
Vector<uint8_t, kInlineStorage> mLocalBytes;
|
||||
uint8_t const * const mBase;
|
||||
};
|
||||
|
||||
template <typename MMPolicy>
|
||||
class MOZ_STACK_CLASS ReadOnlyTargetFunction final
|
||||
{
|
||||
template <typename TargetMMPolicy>
|
||||
class TargetBytesPtr
|
||||
class TargetBytesPtr;
|
||||
|
||||
template<>
|
||||
class TargetBytesPtr<MMPolicyInProcess>
|
||||
{
|
||||
public:
|
||||
typedef TargetBytesPtr<TargetMMPolicy> Type;
|
||||
typedef TargetBytesPtr<MMPolicyInProcess> Type;
|
||||
|
||||
static Type Make(const TargetMMPolicy& aMMPolicy, const void* aFunc)
|
||||
static Type Make(const MMPolicyInProcess& aMMPolicy, const void* aFunc)
|
||||
{
|
||||
return Move(TargetBytesPtr(aMMPolicy, aFunc));
|
||||
}
|
||||
@ -326,7 +491,7 @@ class MOZ_STACK_CLASS ReadOnlyTargetFunction final
|
||||
return Move(TargetBytesPtr(aOther, aOffsetFromOther));
|
||||
}
|
||||
|
||||
ReadOnlyTargetBytes<TargetMMPolicy>* operator->()
|
||||
ReadOnlyTargetBytes<MMPolicyInProcess>* operator->()
|
||||
{
|
||||
return &mTargetBytes;
|
||||
}
|
||||
@ -345,7 +510,7 @@ class MOZ_STACK_CLASS ReadOnlyTargetFunction final
|
||||
TargetBytesPtr& operator=(TargetBytesPtr&&) = delete;
|
||||
|
||||
private:
|
||||
TargetBytesPtr(const TargetMMPolicy& aMMPolicy, const void* aFunc)
|
||||
TargetBytesPtr(const MMPolicyInProcess& aMMPolicy, const void* aFunc)
|
||||
: mTargetBytes(aMMPolicy, aFunc)
|
||||
{
|
||||
}
|
||||
@ -356,7 +521,27 @@ class MOZ_STACK_CLASS ReadOnlyTargetFunction final
|
||||
{
|
||||
}
|
||||
|
||||
ReadOnlyTargetBytes<TargetMMPolicy> mTargetBytes;
|
||||
ReadOnlyTargetBytes<MMPolicyInProcess> mTargetBytes;
|
||||
};
|
||||
|
||||
template <>
|
||||
class TargetBytesPtr<MMPolicyOutOfProcess>
|
||||
{
|
||||
public:
|
||||
typedef std::shared_ptr<ReadOnlyTargetBytes<MMPolicyOutOfProcess>> Type;
|
||||
|
||||
static Type Make(const MMPolicyOutOfProcess& aMMPolicy, const void* aFunc)
|
||||
{
|
||||
return Move(std::make_shared<ReadOnlyTargetBytes<MMPolicyOutOfProcess>>(
|
||||
aMMPolicy, aFunc));
|
||||
}
|
||||
|
||||
static Type CopyFromOffset(const Type& aOther,
|
||||
const uint32_t aOffsetFromOther)
|
||||
{
|
||||
return Move(std::make_shared<ReadOnlyTargetBytes<MMPolicyOutOfProcess>>(
|
||||
*aOther, aOffsetFromOther));
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
@ -391,17 +576,17 @@ public:
|
||||
|
||||
uintptr_t GetBaseAddress() const
|
||||
{
|
||||
return reinterpret_cast<uintptr_t>(mTargetBytes->Get());
|
||||
return mTargetBytes->GetBase();
|
||||
}
|
||||
|
||||
uintptr_t GetAddress() const
|
||||
{
|
||||
return reinterpret_cast<uintptr_t>(mTargetBytes->Get() + mOffset);
|
||||
return mTargetBytes->GetBase() + mOffset;
|
||||
}
|
||||
|
||||
uintptr_t AsEncodedPtr() const
|
||||
{
|
||||
return EncodePtr(const_cast<uint8_t*>(mTargetBytes->Get() + mOffset));
|
||||
return EncodePtr(reinterpret_cast<void*>(mTargetBytes->GetBase() + mOffset));
|
||||
}
|
||||
|
||||
static uintptr_t EncodePtr(void* aPtr)
|
||||
@ -418,13 +603,13 @@ public:
|
||||
uint8_t const & operator*() const
|
||||
{
|
||||
mTargetBytes->EnsureLimit(mOffset);
|
||||
return *(mTargetBytes->Get() + mOffset);
|
||||
return *(mTargetBytes->GetLocalBytes() + mOffset);
|
||||
}
|
||||
|
||||
uint8_t const & operator[](uint32_t aIndex) const
|
||||
{
|
||||
mTargetBytes->EnsureLimit(mOffset + aIndex);
|
||||
return *(mTargetBytes->Get() + mOffset + aIndex);
|
||||
return *(mTargetBytes->GetLocalBytes() + mOffset + aIndex);
|
||||
}
|
||||
|
||||
ReadOnlyTargetFunction& operator++()
|
||||
@ -447,16 +632,15 @@ public:
|
||||
uintptr_t ReadDisp32AsAbsolute()
|
||||
{
|
||||
mTargetBytes->EnsureLimit(mOffset + sizeof(int32_t));
|
||||
int32_t disp = *reinterpret_cast<const int32_t*>(mTargetBytes->Get() + mOffset);
|
||||
uintptr_t result = reinterpret_cast<uintptr_t>(
|
||||
mTargetBytes->Get() + mOffset + sizeof(int32_t) + disp);
|
||||
int32_t disp = *reinterpret_cast<const int32_t*>(mTargetBytes->GetLocalBytes() + mOffset);
|
||||
uintptr_t result = mTargetBytes->GetBase() + mOffset + sizeof(int32_t) + disp;
|
||||
mOffset += sizeof(int32_t);
|
||||
return result;
|
||||
}
|
||||
|
||||
uintptr_t OffsetToAbsolute(const uint8_t aOffset) const
|
||||
{
|
||||
return reinterpret_cast<uintptr_t>(mTargetBytes->Get() + mOffset + aOffset);
|
||||
return mTargetBytes->GetBase() + mOffset + aOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -480,7 +664,7 @@ public:
|
||||
|
||||
WritableTargetFunction<MMPolicy> result(
|
||||
mTargetBytes->GetMMPolicy(),
|
||||
reinterpret_cast<uintptr_t>(mTargetBytes->Get() + aOffset),
|
||||
mTargetBytes->GetBase() + aOffset,
|
||||
effectiveLength);
|
||||
|
||||
return Move(result);
|
||||
@ -514,7 +698,7 @@ public:
|
||||
auto ChasePointer()
|
||||
{
|
||||
mTargetBytes->EnsureLimit(mOffset + sizeof(T));
|
||||
const typename RemoveCV<T>::Type result = *reinterpret_cast<const RemoveCV<T>::Type*>(mTargetBytes->Get() + mOffset);
|
||||
const typename RemoveCV<T>::Type result = *reinterpret_cast<const RemoveCV<T>::Type*>(mTargetBytes->GetLocalBytes() + mOffset);
|
||||
return ChasePointerHelper<typename RemoveCV<T>::Type>::Result(mTargetBytes->GetMMPolicy(), result);
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,10 @@ if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
EXPORTS += [
|
||||
'nsWindowsDllInterceptor.h',
|
||||
]
|
||||
EXPORTS.mozilla += [
|
||||
'DynamicallyLinkedFunctionPtr.h',
|
||||
'WindowsMapRemoteView.h',
|
||||
]
|
||||
EXPORTS.mozilla.interceptor += [
|
||||
'interceptor/MMPolicies.h',
|
||||
'interceptor/PatcherBase.h',
|
||||
@ -47,6 +51,7 @@ if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
]
|
||||
SOURCES += [
|
||||
'TimeStamp_windows.cpp',
|
||||
'WindowsMapRemoteView.cpp',
|
||||
]
|
||||
OS_LIBS += ['dbghelp']
|
||||
elif CONFIG['HAVE_CLOCK_MONOTONIC']:
|
||||
|
@ -236,6 +236,11 @@ private:
|
||||
|
||||
using WindowsDllInterceptor = interceptor::WindowsDllInterceptor<>;
|
||||
|
||||
using CrossProcessDllInterceptor = interceptor::WindowsDllInterceptor<
|
||||
mozilla::interceptor::VMSharingPolicyUnique<
|
||||
mozilla::interceptor::MMPolicyOutOfProcess,
|
||||
mozilla::interceptor::kDefaultTrampolineSize>>;
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif /* NS_WINDOWS_DLL_INTERCEPTOR_H_ */
|
||||
|
135
mozglue/tests/interceptor/TestDllInterceptorCrossProcess.cpp
Normal file
135
mozglue/tests/interceptor/TestDllInterceptorCrossProcess.cpp
Normal file
@ -0,0 +1,135 @@
|
||||
/* -*- 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 "nsWindowsDllInterceptor.h"
|
||||
#include "nsWindowsHelpers.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
using std::wstring;
|
||||
|
||||
static void* gOrigReturnResult;
|
||||
|
||||
extern "C" __declspec(dllexport) int
|
||||
ReturnResult()
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
static int
|
||||
ReturnResultHook()
|
||||
{
|
||||
auto origFn = reinterpret_cast<decltype(&ReturnResult)>(gOrigReturnResult);
|
||||
if (origFn() != 2) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ParentMain()
|
||||
{
|
||||
// We'll add the child process to a job so that, in the event of a failure in
|
||||
// this parent process, the child process will be automatically terminated.
|
||||
nsAutoHandle job(::CreateJobObject(nullptr, nullptr));
|
||||
if (!job) {
|
||||
printf("TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Job creation failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo{};
|
||||
jobInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
||||
|
||||
if (!::SetInformationJobObject(job.get(), JobObjectExtendedLimitInformation,
|
||||
&jobInfo, sizeof(jobInfo))) {
|
||||
printf("TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Job config failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
wstring cmdLine(::GetCommandLineW());
|
||||
cmdLine += L" -child";
|
||||
|
||||
STARTUPINFOW si = { sizeof(si) };
|
||||
PROCESS_INFORMATION pi;
|
||||
if (!::CreateProcessW(nullptr, const_cast<LPWSTR>(cmdLine.c_str()), nullptr,
|
||||
nullptr, FALSE, CREATE_SUSPENDED, nullptr, nullptr, &si,
|
||||
&pi)) {
|
||||
printf("TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Failed to spawn child process\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
nsAutoHandle childProcess(pi.hProcess);
|
||||
nsAutoHandle childMainThread(pi.hThread);
|
||||
|
||||
if (!::AssignProcessToJobObject(job.get(), childProcess.get())) {
|
||||
printf("TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Failed to assign child process to job\n");
|
||||
::TerminateProcess(childProcess.get(), 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
mozilla::CrossProcessDllInterceptor intcpt(childProcess.get());
|
||||
intcpt.Init("TestDllInterceptorCrossProcess.exe");
|
||||
|
||||
if (!intcpt.AddHook("ReturnResult",
|
||||
reinterpret_cast<intptr_t>(&ReturnResultHook),
|
||||
&gOrigReturnResult)) {
|
||||
printf("TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Failed to add hook\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("TEST-PASS | DllInterceptorCrossProcess | Hook added\n");
|
||||
|
||||
// Let's save the original hook
|
||||
SIZE_T bytesWritten;
|
||||
if (!::WriteProcessMemory(childProcess.get(), &gOrigReturnResult,
|
||||
&gOrigReturnResult, sizeof(gOrigReturnResult),
|
||||
&bytesWritten)) {
|
||||
printf("TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Failed to write original function pointer\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (::ResumeThread(childMainThread.get()) == static_cast<DWORD>(-1)) {
|
||||
printf("TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Failed to resume child thread\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
BOOL remoteDebugging;
|
||||
bool debugging = ::IsDebuggerPresent() ||
|
||||
(::CheckRemoteDebuggerPresent(childProcess.get(),
|
||||
&remoteDebugging) &&
|
||||
remoteDebugging);
|
||||
|
||||
DWORD waitResult = ::WaitForSingleObject(childProcess.get(),
|
||||
debugging ? INFINITE : 60000);
|
||||
if (waitResult != WAIT_OBJECT_0) {
|
||||
printf("TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Child process failed to finish\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
DWORD childExitCode;
|
||||
if (!::GetExitCodeProcess(childProcess.get(), &childExitCode)) {
|
||||
printf("TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Failed to obtain child process exit code\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (childExitCode) {
|
||||
printf("TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Child process exit code is %u instead of 0\n", childExitCode);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("TEST-PASS | DllInterceptorCrossProcess | Child process exit code is zero\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
if (argc > 1) {
|
||||
return ReturnResult();
|
||||
}
|
||||
|
||||
return ParentMain();
|
||||
}
|
||||
|
@ -4,18 +4,14 @@
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
CppUnitTests([
|
||||
'TestDllInterceptor',
|
||||
])
|
||||
|
||||
DEFINES['NS_NO_XPCOM'] = True
|
||||
|
||||
DisableStlWrapping()
|
||||
GeckoCppUnitTests(
|
||||
[
|
||||
'TestDllInterceptor',
|
||||
'TestDllInterceptorCrossProcess',
|
||||
],
|
||||
linkage=None
|
||||
)
|
||||
|
||||
OS_LIBS += [
|
||||
'ole32',
|
||||
]
|
||||
|
||||
USE_LIBS += [
|
||||
'mfbt',
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user