Bug 1310056: Compatibility hack for mitigating RPC_E_CANTCALLOUT_ININPUTSYNCCALL; draft
authorAaron Klotz <aklotz@mozilla.com>
Mon, 07 Nov 2016 15:30:48 -0700
changeset 485453 d6e9cb94a48146c0cbee3dda236f4cf074230c18
parent 485452 4e6b145ed79fee8460d98d2449f00e45c4dc15f0
child 485454 dd4a91eab0e8efbd81335a40c35b6b7b6507d6fb
push id45733
push useraklotz@mozilla.com
push dateThu, 16 Feb 2017 17:45:58 +0000
bugs1310056
milestone54.0a1
Bug 1310056: Compatibility hack for mitigating RPC_E_CANTCALLOUT_ININPUTSYNCCALL; MozReview-Commit-ID: MralbBmln2
accessible/windows/msaa/Compatibility.cpp
--- a/accessible/windows/msaa/Compatibility.cpp
+++ b/accessible/windows/msaa/Compatibility.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "Compatibility.h"
 
+#include "nsWindowsDllInterceptor.h"
 #include "nsWinUtils.h"
 #include "Statistics.h"
 
 #include "mozilla/Preferences.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
@@ -42,16 +43,86 @@ IsModuleVersionLessThan(HMODULE aModuleH
     (dwLeftMost == aMajor && dwSecondRight < aMinor));
 }
 
 
 ////////////////////////////////////////////////////////////////////////////////
 // Compatibility
 ////////////////////////////////////////////////////////////////////////////////
 
+static WindowsDllInterceptor sUser32Interceptor;
+static decltype(&InSendMessageEx) sInSendMessageExStub = nullptr;
+static bool sInSendMessageExHackEnabled = false;
+static PVOID sVectoredExceptionHandler = nullptr;
+
+#if defined(_MSC_VER)
+#include <intrin.h>
+#pragma intrinsic(_ReturnAddress)
+#define RETURN_ADDRESS() _ReturnAddress()
+#elif defined(__GNUC__) || defined(__clang__)
+#define RETURN_ADDRESS() __builtin_extract_return_addr(__builtin_return_address(0))
+#endif
+
+static inline bool
+IsCurrentThreadInBlockingMessageSend(const DWORD aStateBits)
+{
+  // From the MSDN docs for InSendMessageEx
+  return (aStateBits & (ISMEX_REPLIED | ISMEX_SEND)) == ISMEX_SEND;
+}
+
+/**
+ * COM assumes that if you're invoking a proxy from an STA thread while
+ * InSendMessageEx reports that the calling thread is blocked, that you'll
+ * deadlock your own process. It returns the RPC_E_CANTCALLOUT_ININPUTSYNCCALL
+ * error code. This is not actually true in our case: we are calling into
+ * the multithreaded apartment via ALPC. In this hook, we check to see if the
+ * caller is COM, and if so, we lie to it.
+ *
+ * This hack is necessary for ATs who invoke COM proxies from within
+ * WH_CALLWNDPROC hooks or a WndProc handling a sent (as opposed to posted)
+ * message.
+ */
+static DWORD WINAPI
+InSendMessageExHook(LPVOID lpReserved)
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+  DWORD result = sInSendMessageExStub(lpReserved);
+  if (NS_IsMainThread() && sInSendMessageExHackEnabled &&
+      IsCurrentThreadInBlockingMessageSend(result)) {
+    // We want to take a strong reference to the dll so that it is never
+    // unloaded/reloaded from this point forward, hence we use LoadLibrary
+    // and not GetModuleHandle.
+    static HMODULE comModule = LoadLibrary(L"combase.dll");
+    MOZ_ASSERT(comModule);
+    if (!comModule) {
+      return result;
+    }
+    // Check if InSendMessageEx is being called from code within combase.dll
+    HMODULE callingModule;
+    if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
+                          GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+                          reinterpret_cast<LPCWSTR>(RETURN_ADDRESS()),
+                          &callingModule) && callingModule == comModule) {
+      result = ISMEX_NOTIFY;
+    }
+  }
+  return result;
+}
+
+static LONG CALLBACK
+DetectInSendMessageExCompat(PEXCEPTION_POINTERS aExceptionInfo)
+{
+  DWORD exceptionCode = aExceptionInfo->ExceptionRecord->ExceptionCode;
+  if (exceptionCode == RPC_E_CANTCALLOUT_ININPUTSYNCCALL &&
+      NS_IsMainThread()) {
+    sInSendMessageExHackEnabled = true;
+  }
+  return EXCEPTION_CONTINUE_SEARCH;
+}
+
 uint32_t Compatibility::sConsumers = Compatibility::UNKNOWN;
 
 void
 Compatibility::Init()
 {
   // Note we collect some AT statistics/telemetry here for convenience.
 
   HMODULE jawsHandle = ::GetModuleHandleW(L"jhook");
@@ -103,10 +174,23 @@ Compatibility::Init()
 
   // Turn off new tab switching for Jaws and WE.
   if (sConsumers & (JAWS | OLDJAWS | WE)) {
     // Check to see if the pref for disallowing CtrlTab is already set. If so,
     // bail out (respect the user settings). If not, set it.
     if (!Preferences::HasUserValue("browser.ctrlTab.disallowForScreenReaders"))
       Preferences::SetBool("browser.ctrlTab.disallowForScreenReaders", true);
   }
+
+  if (BrowserTabsRemoteAutostart()) {
+    sUser32Interceptor.Init("user32.dll");
+    if (!sInSendMessageExStub) {
+      sUser32Interceptor.AddHook("InSendMessageEx",
+                                 reinterpret_cast<intptr_t>(&InSendMessageExHook),
+                                 (void**)&sInSendMessageExStub);
+    }
+    if (!sVectoredExceptionHandler) {
+      sVectoredExceptionHandler =
+        AddVectoredExceptionHandler(TRUE, &DetectInSendMessageExCompat);
+    }
+  }
 }