Bug 1322554: Interpose kernel32!BaseThreadInitThunk to add verification of thread start addresses; r?dmajor draft
authorCarl Corcoran <carlco@gmail.com>
Wed, 24 May 2017 11:56:40 +0200
changeset 583770 346afdca460b87364db9f9f1178a14993de4bb1a
parent 582730 5bc1c758ab57c1885dceab4e7837e58af27b998c
child 630197 86a45e7080ac97058e43d1b5f03d9bbcdd049b68
push id60540
push userbmo:ccorcoran@mozilla.com
push dateWed, 24 May 2017 16:31:14 +0000
reviewersdmajor
bugs1322554
milestone55.0a1
Bug 1322554: Interpose kernel32!BaseThreadInitThunk to add verification of thread start addresses; r?dmajor MozReview-Commit-ID: 8Jm1PXyRrEr
mozglue/build/WindowsDllBlocklist.cpp
toolkit/xre/test/win/TestDllInterceptor.cpp
--- a/mozglue/build/WindowsDllBlocklist.cpp
+++ b/mozglue/build/WindowsDllBlocklist.cpp
@@ -292,16 +292,22 @@ printf_stderr(const char *fmt, ...)
   va_list args;
   va_start(args, fmt);
   vfprintf(fp, fmt, args);
   va_end(args);
 
   fclose(fp);
 }
 
+
+#ifdef _M_IX86
+typedef void (__fastcall* BaseThreadInitThunk_func)(BOOL aIsInitialThread, void* aStartAddress, void* aThreadParam);
+static BaseThreadInitThunk_func stub_BaseThreadInitThunk = nullptr;
+#endif
+
 typedef NTSTATUS (NTAPI *LdrLoadDll_func) (PWCHAR filePath, PULONG flags, PUNICODE_STRING moduleFileName, PHANDLE handle);
 static LdrLoadDll_func stub_LdrLoadDll;
 
 #ifdef _M_AMD64
 typedef decltype(RtlInstallFunctionTableCallback)* RtlInstallFunctionTableCallback_func;
 static RtlInstallFunctionTableCallback_func stub_RtlInstallFunctionTableCallback;
 
 extern uint8_t* sMsMpegJitCodeRegionStart;
@@ -761,16 +767,56 @@ 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);
 }
 
+
+#ifdef _M_IX86
+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);
+}
+
+#endif // _M_IX86
+
+
 static WindowsDllInterceptor NtDllIntercept;
 static WindowsDllInterceptor Kernel32Intercept;
 
 MFBT_API void
 DllBlocklist_Initialize(uint32_t aInitFlags)
 {
   if (sBlocklistInitAttempted) {
     return;
@@ -808,16 +854,28 @@ DllBlocklist_Initialize(uint32_t aInitFl
 #ifdef _M_AMD64
   if (!IsWin8OrLater()) {
     // The crash that this hook works around is only seen on Win7.
     Kernel32Intercept.AddHook("RtlInstallFunctionTableCallback",
                               reinterpret_cast<intptr_t>(patched_RtlInstallFunctionTableCallback),
                               (void**)&stub_RtlInstallFunctionTableCallback);
   }
 #endif
+
+#ifdef _M_IX86 // Minimize impact; crashes in BaseThreadInitThunk are vastly more frequent on x86
+  if(!Kernel32Intercept.AddDetour("BaseThreadInitThunk",
+                                    reinterpret_cast<intptr_t>(patched_BaseThreadInitThunk),
+                                    (void**) &stub_BaseThreadInitThunk)) {
+#ifdef DEBUG
+    printf_stderr("BaseThreadInitThunk hook failed\n");
+#endif
+  }
+#endif // _M_IX86
+
+
 }
 
 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
@@ -514,15 +514,18 @@ int main()
       MaybeTestHook(!IsWin8OrLater(), TestRtlInstallFunctionTableCallback, "kernel32.dll", "RtlInstallFunctionTableCallback") &&
 #endif
       MaybeTestHook(ShouldTestTipTsf(), TestProcessCaretEvents, "tiptsf.dll", "ProcessCaretEvents") &&
 #ifdef _M_IX86
       TestHook(TestSendMessageTimeoutW, "user32.dll", "SendMessageTimeoutW") &&
 #endif
       TestHook(TestTlsAlloc, "kernel32.dll", "TlsAlloc") &&
       TestHook(TestTlsFree, "kernel32.dll", "TlsFree") &&
+#ifdef _M_IX86
+      TestDetour("kernel32.dll", "BaseThreadInitThunk") &&
+#endif
       TestDetour("ntdll.dll", "LdrLoadDll")) {
     printf("TEST-PASS | WindowsDllInterceptor | all checks passed\n");
     return 0;
   }
 
   return 1;
 }