Bug 1322554: Interpose kernel32!BaseThreadInitThunk to add verification of thread start addresses; r?dmajor
MozReview-Commit-ID: 8Jm1PXyRrEr
--- 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;
}