Bug 1322554: Interpose kernel32!BaseThreadInitThunk to add verification of thread start addresses; r=dmajor
☠☠ backed out by 943594e7f5d8 ☠ ☠
authorCarl Corcoran <carlco@gmail.com>
Wed, 29 Mar 2017 17:07:37 +0200
changeset 350510 23de710cae6f
parent 350509 a66571f6f5b0
child 350511 495a39e11056
push id39826
push userihsiao@mozilla.com
push dateThu, 30 Mar 2017 09:42:19 +0000
treeherderautoland@23de710cae6f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdmajor
bugs1322554
milestone55.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 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
@@ -277,16 +277,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;
@@ -697,17 +701,53 @@ patched_LdrLoadDll (PWCHAR filePath, PUL
 continue_loading:
 #ifdef DEBUG_very_verbose
   printf_stderr("LdrLoadDll: continuing load... ('%S')\n", moduleFileName->Buffer);
 #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()
 {
   if (sBlocklistInitAttempted) {
     return;
@@ -733,16 +773,26 @@ DllBlocklist_Initialize()
   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
@@ -463,15 +463,16 @@ int main()
       TestHook(TestGetOpenFileNameW, "comdlg32.dll", "GetOpenFileNameW") &&
 #ifdef _M_X64
       TestHook(TestGetKeyState, "user32.dll", "GetKeyState") &&    // see Bug 1316415
 #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) {
@@ -1058,16 +1064,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);
@@ -1079,16 +1088,23 @@ 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;