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:
Aaron Klotz 2018-04-04 16:31:43 -06:00
parent 9e9c6ca596
commit 0850bc3ec5
10 changed files with 786 additions and 36 deletions

View File

@ -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"

View File

@ -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

View 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

View 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

View File

@ -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

View File

@ -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);
}

View File

@ -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']:

View File

@ -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_ */

View 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();
}

View File

@ -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',
]