Bug 1451511: Add cross-process function hooking to DLL interceptor; r=handyman
authorAaron Klotz <aklotz@mozilla.com>
Wed, 04 Apr 2018 16:31:43 -0600
changeset 472492 eea27e04c541c8f3f1221764b00a4f95de23ef78
parent 472491 184a937c196ee10f884b5e7f6a94b32783d3fec2
child 472493 0247bf6522996220bf123f9d8443986fa71e554d
push id1728
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:12:27 +0000
treeherdermozilla-release@c296fde26f5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershandyman
bugs1451511
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1451511: Add cross-process function hooking to DLL interceptor; r=handyman
ipc/mscom/AgileReference.cpp
ipc/mscom/DynamicallyLinkedFunctionPtr.h
mozglue/misc/DynamicallyLinkedFunctionPtr.h
mozglue/misc/WindowsMapRemoteView.cpp
mozglue/misc/WindowsMapRemoteView.h
mozglue/misc/interceptor/MMPolicies.h
mozglue/misc/interceptor/TargetFunction.h
mozglue/misc/moz.build
mozglue/misc/nsWindowsDllInterceptor.h
mozglue/tests/interceptor/TestDllInterceptorCrossProcess.cpp
mozglue/tests/interceptor/moz.build
--- a/ipc/mscom/AgileReference.cpp
+++ b/ipc/mscom/AgileReference.cpp
@@ -1,18 +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 http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/mscom/AgileReference.h"
 
-#include "DynamicallyLinkedFunctionPtr.h"
 #include "mozilla/DebugOnly.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Move.h"
 
 #if NTDDI_VERSION < NTDDI_WINBLUE
 
 // Declarations from Windows SDK specific to Windows 8.1
 
 enum AgileReferenceOptions
rename from ipc/mscom/DynamicallyLinkedFunctionPtr.h
rename to mozglue/misc/DynamicallyLinkedFunctionPtr.h
--- a/ipc/mscom/DynamicallyLinkedFunctionPtr.h
+++ b/mozglue/misc/DynamicallyLinkedFunctionPtr.h
@@ -1,22 +1,21 @@
 /* -*- 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 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;
 
 template <typename R, typename... Args>
 class DynamicallyLinkedFunctionPtr<R (__stdcall*)(Args...)>
 {
   typedef R (__stdcall* FunctionPtrT)(Args...);
@@ -63,13 +62,12 @@ public:
     return !!mFunction;
   }
 
 private:
   HMODULE       mModule;
   FunctionPtrT  mFunction;
 };
 
-} // namespace mscom
 } // namespace mozilla
 
-#endif // mozilla_mscom_DynamicallyLinkedFunctionPtr_h
+#endif // mozilla_DynamicallyLinkedFunctionPtr_h
 
new file mode 100644
--- /dev/null
+++ b/mozglue/misc/WindowsMapRemoteView.cpp
@@ -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
+
new file mode 100644
--- /dev/null
+++ b/mozglue/misc/WindowsMapRemoteView.h
@@ -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
--- a/mozglue/misc/interceptor/MMPolicies.h
+++ b/mozglue/misc/interceptor/MMPolicies.h
@@ -4,16 +4,17 @@
  * 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_interceptor_MMPolicies_h
 #define mozilla_interceptor_MMPolicies_h
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Types.h"
+#include "mozilla/WindowsMapRemoteView.h"
 
 #include <windows.h>
 
 namespace mozilla {
 namespace interceptor {
 
 class MMPolicyBase
 {
@@ -122,17 +123,22 @@ public:
   }
 
   bool Protect(void* aVAddress, size_t aSize, uint32_t aProtFlags,
                uint32_t* aPrevProtFlags) const
   {
     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;
   }
 
   /**
    * @return true if the page that hosts aVAddress is accessible.
    */
   bool IsPageAccessible(void* aVAddress) const
   {
@@ -212,13 +218,277 @@ protected:
   }
 
 private:
   uint8_t*  mBase;
   uint32_t  mReservationSize;
   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
 
 #endif // mozilla_interceptor_MMPolicies_h
 
--- a/mozglue/misc/interceptor/TargetFunction.h
+++ b/mozglue/misc/interceptor/TargetFunction.h
@@ -221,16 +221,23 @@ public:
 
 private:
   const MMPolicy& mMMPolicy;
   const uintptr_t mFunc;
   const size_t mNumBytes;
   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)
   static const size_t kInlineStorage = 32;
 #endif
   Vector<uint8_t, kInlineStorage> mLocalBytes;
   bool mAccumulatedStatus;
 };
@@ -283,55 +290,213 @@ public:
     if ((base / pageSize) == (adjusted / pageSize)) {
       return true;
     }
 
     // Otherwise, let's query |adjusted|
     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;
   }
 
   ReadOnlyTargetBytes& operator=(const ReadOnlyTargetBytes&) = delete;
   ReadOnlyTargetBytes& operator=(ReadOnlyTargetBytes&&) = delete;
 
 private:
   const MMPolicyInProcess&  mMMPolicy;
   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));
     }
 
     static Type CopyFromOffset(const TargetBytesPtr& aOther,
                                const uint32_t aOffsetFromOther)
     {
       return Move(TargetBytesPtr(aOther, aOffsetFromOther));
     }
 
-    ReadOnlyTargetBytes<TargetMMPolicy>* operator->()
+    ReadOnlyTargetBytes<MMPolicyInProcess>* operator->()
     {
       return &mTargetBytes;
     }
 
     TargetBytesPtr(TargetBytesPtr&& aOther)
       : mTargetBytes(Move(aOther.mTargetBytes))
     {
     }
@@ -340,28 +505,48 @@ class MOZ_STACK_CLASS ReadOnlyTargetFunc
       : mTargetBytes(aOther.mTargetBytes)
     {
     }
 
     TargetBytesPtr& operator=(const TargetBytesPtr&) = delete;
     TargetBytesPtr& operator=(TargetBytesPtr&&) = delete;
 
   private:
-    TargetBytesPtr(const TargetMMPolicy& aMMPolicy, const void* aFunc)
+    TargetBytesPtr(const MMPolicyInProcess& aMMPolicy, const void* aFunc)
       : mTargetBytes(aMMPolicy, aFunc)
     {
     }
 
     TargetBytesPtr(const TargetBytesPtr& aOther,
                    const uint32_t aOffsetFromOther)
       : mTargetBytes(aOther.mTargetBytes, aOffsetFromOther)
     {
     }
 
-    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:
   ReadOnlyTargetFunction(const MMPolicy& aMMPolicy, const void* aFunc)
     : mTargetBytes(TargetBytesPtr<MMPolicy>::Make(aMMPolicy, aFunc))
     , mOffset(0)
   {
   }
@@ -386,50 +571,50 @@ public:
 
   ReadOnlyTargetFunction operator+(const uint32_t aOffset) const
   {
     return ReadOnlyTargetFunction(*this, mOffset + aOffset);
   }
 
   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)
   {
     return reinterpret_cast<uintptr_t>(::EncodePointer(aPtr));
   }
 
   static uintptr_t DecodePtr(uintptr_t aEncodedPtr)
   {
     return reinterpret_cast<uintptr_t>(
       ::DecodePointer(reinterpret_cast<PVOID>(aEncodedPtr)));
   }
 
   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++()
   {
     ++mOffset;
     return *this;
   }
 
@@ -442,26 +627,25 @@ public:
   uint32_t GetOffset() const
   {
     return mOffset;
   }
 
   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;
   }
 
   /**
    * This method promotes the code referenced by this object to be writable.
    *
    * @param aLen    The length of the function's code to make writable. If set
    *                to zero, this object's current offset is used as the length.
    * @param aOffset The result's base address will be offset from this
@@ -475,17 +659,17 @@ public:
     MOZ_RELEASE_ASSERT(effectiveLength, "Cannot Promote a zero-length function");
 
     if (!mTargetBytes->IsValidAtOffset(aOffset)) {
       return WritableTargetFunction<MMPolicy>(mTargetBytes->GetMMPolicy());
     }
 
     WritableTargetFunction<MMPolicy> result(
       mTargetBytes->GetMMPolicy(),
-      reinterpret_cast<uintptr_t>(mTargetBytes->Get() + aOffset),
+      mTargetBytes->GetBase() + aOffset,
       effectiveLength);
 
     return Move(result);
   }
 
 private:
   template <typename T>
   struct ChasePointerHelper
@@ -509,17 +693,17 @@ private:
   };
 
 public:
   // Keep chasing pointers until T is not a pointer type anymore
   template <typename T>
   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);
   }
 
   uintptr_t ChasePointerFromDisp()
   {
     uintptr_t ptrFromDisp = ReadDisp32AsAbsolute();
     ReadOnlyTargetFunction<MMPolicy> ptr(mTargetBytes->GetMMPolicy(),
                                          reinterpret_cast<const void*>(ptrFromDisp));
--- a/mozglue/misc/moz.build
+++ b/mozglue/misc/moz.build
@@ -31,27 +31,32 @@ SOURCES += [
 OS_LIBS += CONFIG['REALTIME_LIBS']
 
 DEFINES['IMPL_MFBT'] = True
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     EXPORTS += [
         'nsWindowsDllInterceptor.h',
     ]
+    EXPORTS.mozilla += [
+        'DynamicallyLinkedFunctionPtr.h',
+        'WindowsMapRemoteView.h',
+    ]
     EXPORTS.mozilla.interceptor += [
         'interceptor/MMPolicies.h',
         'interceptor/PatcherBase.h',
         'interceptor/PatcherDetour.h',
         'interceptor/PatcherNopSpace.h',
         'interceptor/TargetFunction.h',
         'interceptor/Trampoline.h',
         'interceptor/VMSharingPolicies.h',
     ]
     SOURCES += [
         'TimeStamp_windows.cpp',
+        'WindowsMapRemoteView.cpp',
     ]
     OS_LIBS += ['dbghelp']
 elif CONFIG['HAVE_CLOCK_MONOTONIC']:
     SOURCES += [
         'TimeStamp_posix.cpp',
     ]
 elif CONFIG['OS_ARCH'] == 'Darwin':
     SOURCES += [
--- a/mozglue/misc/nsWindowsDllInterceptor.h
+++ b/mozglue/misc/nsWindowsDllInterceptor.h
@@ -231,11 +231,16 @@ private:
     return mDetourPatcher.AddHook(aProc, aHookDest, aOrigFunc);
   }
 };
 
 } // namespace interceptor
 
 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_ */
new file mode 100644
--- /dev/null
+++ b/mozglue/tests/interceptor/TestDllInterceptorCrossProcess.cpp
@@ -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();
+}
+
--- a/mozglue/tests/interceptor/moz.build
+++ b/mozglue/tests/interceptor/moz.build
@@ -1,21 +1,17 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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 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',
-]