Bug 1322554: Interpose kernel32!BaseThreadInitThunk to add verification of thread start addresses; r=dmajor draft
authorCarl Corcoran <carlco@gmail.com>
Sun, 09 Apr 2017 09:35:38 +0200
changeset 577978 ee7acd115195
parent 577977 a6a6e4bb30f6
child 577979 83788c160e8c
child 577984 e500e015de1c
child 580147 b86738155f66
push id58847
push userbmo:ccorcoran@mozilla.com
push dateMon, 15 May 2017 18:25:53 +0000
reviewersdmajor
bugs1322554
milestone55.0a1
Bug 1322554: Interpose kernel32!BaseThreadInitThunk to add verification of thread start addresses; r=dmajor MozReview-Commit-ID: I1P2I2um76i
mozglue/build/WindowsDllBlocklist.cpp
toolkit/xre/test/win/TestDllInterceptor.cpp
xpcom/build/nsWindowsDllInterceptor.h
--- a/mozglue/build/WindowsDllBlocklist.cpp
+++ b/mozglue/build/WindowsDllBlocklist.cpp
@@ -294,16 +294,20 @@ printf_stderr(const char *fmt, ...)
   vfprintf(fp, fmt, args);
   va_end(args);
 
   fclose(fp);
 }
 
 namespace {
 
+typedef void (__fastcall* BaseThreadInitThunk_func)(BOOL aIsInitialThread, void* aStartAddress, void* aThreadParam);
+
+static BaseThreadInitThunk_func stub_BaseThreadInitThunk = nullptr;
+
 typedef NTSTATUS (NTAPI *LdrLoadDll_func) (PWCHAR filePath, PULONG flags, PUNICODE_STRING moduleFileName, PHANDLE handle);
 
 static LdrLoadDll_func stub_LdrLoadDll = 0;
 
 template <class T>
 struct RVAMap {
   RVAMap(HANDLE map, DWORD offset) {
     SYSTEM_INFO info;
@@ -725,17 +729,53 @@ continue_loading:
   // Prevent the stack walker from suspending this thread when LdrLoadDll
   // holds the RtlLookupFunctionEntry lock.
   AutoSuppressStackWalking suppress;
 #endif
 
   return stub_LdrLoadDll(filePath, flags, moduleFileName, handle);
 }
 
+static bool
+ShouldBlockThread(void* aStartAddress)
+{
+  // Allows crashfirefox.exe to continue to work. Also if your threadproc is null, this crash is intentional.
+  if (aStartAddress == 0)
+    return false;
+
+  bool shouldBlock = false;
+  MEMORY_BASIC_INFORMATION startAddressInfo = {0};
+  if (VirtualQuery(aStartAddress, &startAddressInfo, sizeof(startAddressInfo))) {
+    shouldBlock |= startAddressInfo.State != MEM_COMMIT;
+    shouldBlock |= startAddressInfo.Protect != PAGE_EXECUTE_READ;
+  }
+
+  return shouldBlock;
+}
+
+// Allows blocked threads to still run normally through BaseThreadInitThunk, in case there's any magic there that we shouldn't skip.
+static DWORD WINAPI
+NopThreadProc(void* /* aThreadParam */)
+{
+  return 0;
+}
+
+static MOZ_NORETURN void __fastcall
+patched_BaseThreadInitThunk(BOOL aIsInitialThread, void* aStartAddress,
+                            void* aThreadParam)
+{
+  if (ShouldBlockThread(aStartAddress)) {
+    aStartAddress = NopThreadProc;
+  }
+
+  stub_BaseThreadInitThunk(aIsInitialThread, aStartAddress, aThreadParam);
+}
+
 WindowsDllInterceptor NtDllIntercept;
+WindowsDllInterceptor Kernel32DllIntercept;
 
 } // namespace
 
 MFBT_API void
 DllBlocklist_Initialize(uint32_t aInitFlags)
 {
   if (sBlocklistInitAttempted) {
     return;
@@ -762,16 +802,26 @@ DllBlocklist_Initialize(uint32_t aInitFl
   bool ok = NtDllIntercept.AddDetour("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
   }
+
+  Kernel32DllIntercept.Init("kernel32.dll");
+  ok = Kernel32DllIntercept.AddHook("BaseThreadInitThunk",
+                                    reinterpret_cast<intptr_t>(patched_BaseThreadInitThunk),
+                                    (void**) &stub_BaseThreadInitThunk);
+  if (!ok) {
+#ifdef DEBUG
+    printf_stderr("BaseThreadInitThunk hook failed\n");
+#endif
+  }
 }
 
 MFBT_API void
 DllBlocklist_WriteNotes(HANDLE file)
 {
   DWORD nBytes;
 
   WriteFile(file, kBlockedDllsParameter, kBlockedDllsParameterLen, &nBytes, nullptr);
--- a/toolkit/xre/test/win/TestDllInterceptor.cpp
+++ b/toolkit/xre/test/win/TestDllInterceptor.cpp
@@ -484,15 +484,16 @@ int main()
       TestHook(TestGetKeyState, "user32.dll", "GetKeyState") &&    // see Bug 1316415
       TestHook(TestLdrUnloadDll, "ntdll.dll", "LdrUnloadDll") &&
       TestHook(TestLdrResolveDelayLoadedAPI, "ntdll.dll", "LdrResolveDelayLoadedAPI") &&
 #endif
       MaybeTestHook(ShouldTestTipTsf(), TestProcessCaretEvents, "tiptsf.dll", "ProcessCaretEvents") &&
 #ifdef _M_IX86
       TestHook(TestSendMessageTimeoutW, "user32.dll", "SendMessageTimeoutW") &&
 #endif
+      TestDetour("kernel32.dll", "BaseThreadInitThunk") &&
       TestDetour("ntdll.dll", "LdrLoadDll")) {
     printf("TEST-PASS | WindowsDllInterceptor | all checks passed\n");
     return 0;
   }
 
   return 1;
 }
--- a/xpcom/build/nsWindowsDllInterceptor.h
+++ b/xpcom/build/nsWindowsDllInterceptor.h
@@ -572,16 +572,17 @@ protected:
     return numBytes;
   }
 
 #if defined(_M_X64)
   // To patch for JMP and JE
 
   enum JumpType {
    Je,
+   Jne,
    Jmp,
    Call
   };
 
   struct JumpPatch {
     JumpPatch()
       : mHookOffset(0), mJumpAddress(0), mType(JumpType::Jmp)
     {
@@ -595,16 +596,21 @@ protected:
     size_t GenerateJump(uint8_t* aCode)
     {
       size_t offset = mHookOffset;
       if (mType == JumpType::Je) {
         // JNE RIP+14
         aCode[offset]     = 0x75;
         aCode[offset + 1] = 14;
         offset += 2;
+      } else if (mType == JumpType::Jne) {
+        // JE RIP+14
+        aCode[offset]     = 0x74;
+        aCode[offset + 1] = 14;
+        offset += 2;
       }
 
       // Near call/jmp, absolute indirect, address given in r/m32
       if (mType == JumpType::Call) {
         // CALL [RIP+0]
         aCode[offset] = 0xff;
         aCode[offset + 1] = 0x15;
         // The offset to jump destination -- ie it is placed 2 bytes after the offset.
@@ -878,18 +884,18 @@ protected:
 
         if (origBytes[nOrigBytes] == 0x33) {
           // xor r32, r32
           COPY_CODES(2);
         } else {
           MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
           return;
         }
-      } else if ((origBytes[nOrigBytes] & 0xfb) == 0x48) {
-        // REX.W | REX.WR
+      } else if ((origBytes[nOrigBytes] & 0xfa) == 0x48) {
+        // REX.W | REX.WR | REX.WRB | REX.WB
         COPY_CODES(1);
 
         if (origBytes[nOrigBytes] == 0x81 &&
             (origBytes[nOrigBytes + 1] & 0xf8) == 0xe8) {
           // sub r, dword
           COPY_CODES(6);
         } else if (origBytes[nOrigBytes] == 0x83 &&
                    (origBytes[nOrigBytes + 1] & 0xf8) == 0xe8) {
@@ -1099,16 +1105,19 @@ protected:
         BYTE subOpcode = 0;
         int nModRmSibBytes = CountModRmSib(&origBytes[nOrigBytes + 1], &subOpcode);
         if (nModRmSibBytes < 0 || subOpcode != 0) {
           // Unsupported
           MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
           return;
         }
         COPY_CODES(2 + nModRmSibBytes);
+      } else if (origBytes[nOrigBytes] == 0x85) {
+        // test r/m32, r32
+        COPY_CODES(2);
       } else if (origBytes[nOrigBytes] == 0xd1 &&
                   (origBytes[nOrigBytes+1] & kMaskMod) == kModReg) {
         // bit shifts/rotates : (SA|SH|RO|RC)(R|L) r32
         // (e.g. 0xd1 0xe0 is SAL, 0xd1 0xc8 is ROR)
         COPY_CODES(2);
       } else if (origBytes[nOrigBytes] == 0xc3) {
         // ret
         COPY_CODES(1);
@@ -1120,31 +1129,38 @@ protected:
         // CALL (0xe8) or JMP (0xe9) 32bit offset
         foundJmp = origBytes[nOrigBytes] == 0xe9;
         JumpPatch jump(nTrampBytes,
                        (intptr_t)(origBytes + nOrigBytes + 5 +
                                   *(reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 1))),
                        origBytes[nOrigBytes] == 0xe8 ? JumpType::Call : JumpType::Jmp);
         nTrampBytes = jump.GenerateJump(tramp);
         nOrigBytes += 5;
+      } else if (origBytes[nOrigBytes] == 0x75) {
+        // jne rel8
+        char offset = origBytes[nOrigBytes + 1];
+        JumpPatch jump(nTrampBytes, (intptr_t)(origBytes + nOrigBytes + 2 +
+                       offset), JumpType::Jne);
+        nTrampBytes = jump.GenerateJump(tramp);
+        nOrigBytes += 2;
       } else if (origBytes[nOrigBytes] == 0xff) {
         COPY_CODES(1);
         if ((origBytes[nOrigBytes] & (kMaskMod|kMaskReg)) == 0xf0) {
           // push r64
           COPY_CODES(1);
         } else if (origBytes[nOrigBytes] == 0x25) {
           // jmp absolute indirect m32
           foundJmp = true;
           int32_t offset = *(reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 1));
           int64_t* ptrToJmpDest = reinterpret_cast<int64_t*>(origBytes + nOrigBytes + 5 + offset);
           intptr_t jmpDest = static_cast<intptr_t>(*ptrToJmpDest);
           JumpPatch jump(nTrampBytes, jmpDest, JumpType::Jmp);
           nTrampBytes = jump.GenerateJump(tramp);
           nOrigBytes += 5;
-        } else if ((origBytes[nOrigBytes] & (kMaskMod|kMaskReg)) == BuildModRmByte(kModReg, 2, 0))
+        } else if ((origBytes[nOrigBytes] & (kMaskMod|kMaskReg)) == BuildModRmByte(kModReg, 2, 0)) {
           // CALL reg (ff nn)
           COPY_CODES(1);
         } else {
           MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
           return;
         }
       } else {
         MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");