Bug 951827 - Part 4: Support absolute jumps in hooks. r=ehsan
authorDavid Major <dmajor@mozilla.com>
Fri, 14 Feb 2014 14:58:12 -0800
changeset 170033 8fb155711dfec3c7d6fef608e926aeafd0a19844
parent 170032 1109dba48e6bb0a5db97a54833a6e38b064b0337
child 170034 16b840c2de31fcdef577b6208eacae4bbd60245a
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersehsan
bugs951827
milestone30.0a1
Bug 951827 - Part 4: Support absolute jumps in hooks. r=ehsan
mozglue/build/WindowsDllBlocklist.cpp
toolkit/xre/nsWindowsDllInterceptor.h
--- a/mozglue/build/WindowsDllBlocklist.cpp
+++ b/mozglue/build/WindowsDllBlocklist.cpp
@@ -609,17 +609,18 @@ DllBlocklist_Initialize()
   if (GetModuleHandleA("user32.dll")) {
     sUser32BeforeBlocklist = true;
   }
 
   NtDllIntercept.Init("ntdll.dll");
 
   ReentrancySentinel::InitializeStatics();
 
-  bool ok = NtDllIntercept.AddHook("LdrLoadDll", reinterpret_cast<intptr_t>(patched_LdrLoadDll), (void**) &stub_LdrLoadDll);
+  // Use a shared hook since other software may also hook this API (bug 951827)
+  bool ok = NtDllIntercept.AddSharedHook("LdrLoadDll", reinterpret_cast<intptr_t>(patched_LdrLoadDll), (void**) &stub_LdrLoadDll);
 
   if (!ok) {
     sBlocklistInitFailed = true;
 #ifdef DEBUG
     printf_stderr ("LdrLoadDll hook failed, no dll blocklisting active\n");
 #endif
   }
 }
--- a/toolkit/xre/nsWindowsDllInterceptor.h
+++ b/toolkit/xre/nsWindowsDllInterceptor.h
@@ -36,18 +36,23 @@
  * Unfortunately nop space patching doesn't work on functions which don't have
  * this magic prelude (and in particular, x86-64 never has the prelude).  So
  * when we can't use the built-in nop space, we fall back to using a detour,
  * which works as follows:
  *
  * 1. Save first N bytes of OrigFunction to trampoline, where N is a
  *    number of bytes >= 5 that are instruction aligned.
  *
- * 2. Replace first 5 bytes of OrigFunction with a jump to the Hook
+ * 2. (Usually) Replace first 5 bytes of OrigFunction with a jump to the hook
  *    function.
+ *    (Special "shared" mode) Replace first 6 bytes of OrigFunction with an
+ *    indirect jump to the hook function. "Shared" means that other software
+ *    also tries to hook the same function. The indirect jump uses an absolute
+ *    address, which allows us to coexist with other hooks that don't know how
+ *    to relocate our 5-byte PC-relative jump.
  *
  * 3. After N bytes of the trampoline, add a jump to OrigFunction+N to
  *    continue original program flow.
  *
  * 4. Hook function needs to call the trampoline during its execution,
  *    to invoke the original function (so address of trampoline is
  *    returned).
  *
@@ -214,16 +219,22 @@ public:
   }
 #endif
 };
 
 class WindowsDllDetourPatcher
 {
   typedef unsigned char *byteptr_t;
 public:
+  enum JumpType
+  {
+    JUMP_DONTCARE,
+    JUMP_ABSOLUTE
+  };
+
   WindowsDllDetourPatcher() 
     : mModule(0), mHookPage(0), mMaxHooks(0), mCurHooks(0)
   {
   }
 
   ~WindowsDllDetourPatcher()
   {
     int i;
@@ -241,17 +252,18 @@ public:
 
       // If CreateTrampoline failed, we may have an empty trampoline.
       if (!origBytes) {
         continue;
       }
 
       // If other code has changed this function, it is not safe to modify.
 #if defined(_M_IX86)
-      if (*origBytes != opTrampolineRelativeJump) {
+      if (tramp->jumpType != JUMP_ABSOLUTE &&
+          *origBytes != opTrampolineRelativeJump) {
         continue;
       }
 #elif defined(_M_X64)
       if (*((uint16_t*)origBytes) != opTrampolineRegLoad) {
         continue;
       }
 #else
 #error "Unknown processor type"
@@ -262,17 +274,22 @@ public:
       if (!VirtualProtectEx(GetCurrentProcess(), origBytes, nBytes, PAGE_EXECUTE_READWRITE, &op)) {
         //printf ("VirtualProtectEx failed! %d\n", GetLastError());
         continue;
       }
       // Remove the hook by making the original function jump directly
       // in the trampoline.
       intptr_t dest = (intptr_t)(&tramp->code[0]);
 #if defined(_M_IX86)
-      *((intptr_t*)(origBytes+1)) = dest - (intptr_t)(origBytes+5); // target displacement
+      if (tramp->jumpType == JUMP_ABSOLUTE) {
+        // Absolute jumps on x86 are done indirectly via tramp->jumpTarget
+        tramp->jumpTarget = dest;
+      } else {
+        *((intptr_t*)(origBytes+1)) = dest - (intptr_t)(origBytes+5); // target displacement
+      }
 #elif defined(_M_X64)
       *((intptr_t*)(origBytes+2)) = dest;
 #else
 #error "Unknown processor type"
 #endif
       // restore protection; if this fails we can't really do anything about it
       VirtualProtectEx(GetCurrentProcess(), origBytes, nBytes, op, &op);
     }
@@ -317,74 +334,80 @@ public:
       return;
 
     DWORD op;
     VirtualProtectEx(GetCurrentProcess(), mHookPage, mMaxHooks * kHookSize, PAGE_EXECUTE_READ, &op);
 
     mModule = 0;
   }
 
-  bool AddHook(const char *pname, intptr_t hookDest, void **origFunc)
+  bool AddHook(const char *pname, intptr_t hookDest, JumpType jumpType, void **origFunc)
   {
     if (!mModule)
       return false;
 
     void *pAddr = (void *) GetProcAddress(mModule, pname);
     if (!pAddr) {
       //printf ("GetProcAddress failed\n");
       return false;
     }
 
-    CreateTrampoline(pAddr, hookDest, origFunc);
+    CreateTrampoline(pAddr, hookDest, jumpType, origFunc);
     if (!*origFunc) {
       //printf ("CreateTrampoline failed\n");
       return false;
     }
 
     return true;
   }
 
 protected:
   const static int kPageSize = 4096;
   const static int kHookSize = 128;
   const static int kCodeSize = 100;
 
   const static uint8_t opTrampolineRelativeJump = 0xe9;
+  const static uint16_t opTrampolineIndirectJump = 0x25ff;
   const static uint16_t opTrampolineRegLoad = 0xbb49;
 
   HMODULE mModule;
   byteptr_t mHookPage;
   int mMaxHooks;
   int mCurHooks;
 
   struct Trampoline
   {
     void *origFunction;
+    JumpType jumpType;
+    intptr_t jumpTarget;
     uint8_t code[kCodeSize];
   };
 
   static_assert(sizeof(Trampoline) <= kHookSize, "Trampolines too big");
 
   void CreateTrampoline(void *origFunction,
                         intptr_t dest,
+                        JumpType jumpType,
                         void **outTramp)
   {
     *outTramp = nullptr;
 
     Trampoline *tramp = FindTrampolineSpace();
     if (!tramp)
       return;
 
     byteptr_t origBytes = (byteptr_t) origFunction;
 
     int nBytes = 0;
     int pJmp32 = -1;
 
 #if defined(_M_IX86)
-    while (nBytes < 5) {
+    const int bytesNeeded = (jumpType == JUMP_ABSOLUTE) ? 6 : 5;
+
+    while (nBytes < bytesNeeded) {
       // Understand some simple instructions that might be found in a
       // prologue; we might need to extend this as necessary.
       //
       // Note!  If we ever need to understand jump instructions, we'll
       // need to rewrite the displacement argument.
       if (origBytes[nBytes] >= 0x88 && origBytes[nBytes] <= 0x8B) {
         // various MOVs
         unsigned char b = origBytes[nBytes+1];
@@ -593,16 +616,18 @@ protected:
     if (nBytes > kCodeSize) {
       //printf ("Too big!");
       return;
     }
 
     // We keep the address of the original function in the first bytes of
     // the trampoline buffer
     tramp->origFunction = origFunction;
+    tramp->jumpType = jumpType;
+    tramp->jumpTarget = dest;
 
     memcpy(&tramp->code[0], origFunction, nBytes);
 
     // OrigFunction+N, the target of the trampoline
     byteptr_t trampDest = origBytes + nBytes;
 
 #if defined(_M_IX86)
     if (pJmp32 >= 0) {
@@ -644,18 +669,25 @@ protected:
     DWORD op;
     if (!VirtualProtectEx(GetCurrentProcess(), origFunction, nBytes, PAGE_EXECUTE_READWRITE, &op)) {
       //printf ("VirtualProtectEx failed! %d\n", GetLastError());
       return;
     }
 
 #if defined(_M_IX86)
     // now modify the original bytes
-    origBytes[0] = opTrampolineRelativeJump; // jmp
-    *((intptr_t*)(origBytes+1)) = dest - (intptr_t)(origBytes+5); // target displacement
+    if (jumpType == JUMP_ABSOLUTE) {
+      // Indirect jump with absolute address of pointer
+      // jmp dword ptr [&tramp->jumpTarget]
+      *((uint16_t*)(origBytes)) = opTrampolineIndirectJump;
+      *((intptr_t*)(origBytes+2)) = (intptr_t)&tramp->jumpTarget;
+    } else {
+      origBytes[0] = opTrampolineRelativeJump; // jmp rel32
+      *((intptr_t*)(origBytes+1)) = dest - (intptr_t)(origBytes+5); // target displacement
+    }
 #elif defined(_M_X64)
     // mov r11, address
     *((uint16_t*)(origBytes)) = opTrampolineRegLoad;
     *((intptr_t*)(origBytes+2)) = dest;
 
     // jmp r11
     origBytes[10] = 0x41;
     origBytes[11] = 0xff;
@@ -680,16 +712,17 @@ protected:
 };
 
 } // namespace internal
 
 class WindowsDllInterceptor
 {
   internal::WindowsDllNopSpacePatcher mNopSpacePatcher;
   internal::WindowsDllDetourPatcher mDetourPatcher;
+  typedef internal::WindowsDllDetourPatcher::JumpType JumpType;
 
   const char *mModuleName;
   int mNHooks;
 
 public:
   WindowsDllInterceptor()
     : mModuleName(nullptr)
     , mNHooks(0)
@@ -727,17 +760,39 @@ public:
       return true;
     }
 
     if (!mDetourPatcher.Initialized()) {
       // printf("Initializing detour patcher.\n");
       mDetourPatcher.Init(mModuleName, mNHooks);
     }
 
-    bool rv = mDetourPatcher.AddHook(pname, hookDest, origFunc);
+    bool rv = mDetourPatcher.AddHook(pname, hookDest, JumpType::JUMP_DONTCARE,
+                                     origFunc);
+
     // printf("detourPatcher returned %d\n", rv);
     return rv;
   }
+
+  bool AddSharedHook(const char *pname, intptr_t hookDest, void **origFunc)
+  {
+    if (!mModuleName) {
+      return false;
+    }
+
+    // Skip the nop-space patcher and use only the detour patcher. Nop-space
+    // patches use relative jumps, which are not safe to share.
+
+    if (!mDetourPatcher.Initialized()) {
+      mDetourPatcher.Init(mModuleName, mNHooks);
+    }
+
+    bool rv = mDetourPatcher.AddHook(pname, hookDest, JumpType::JUMP_ABSOLUTE,
+                                     origFunc);
+
+    return rv;
+  }
+
 };
 
 } // namespace mozilla
 
 #endif /* NS_WINDOWS_DLL_INTERCEPTOR_H_ */