Bug 1535704: Part 2 - Add a hook to sandbox target initialization that catches mscom's attempts to resolve user32 when Win32k lockdown is enabled; r=bobowen
authorAaron Klotz <aklotz@mozilla.com>
Mon, 22 Apr 2019 21:38:36 +0000
changeset 530688 d49e716056f8835ac626f1bb9e6aaf2844c545fb
parent 530687 ad1f76c891a3bf85e961fb811a73c58cabecacea
child 530689 d303f2ce387ee0c666aa9173ef3ba841493149c9
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbobowen
bugs1535704
milestone68.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 1535704: Part 2 - Add a hook to sandbox target initialization that catches mscom's attempts to resolve user32 when Win32k lockdown is enabled; r=bobowen Differential Revision: https://phabricator.services.mozilla.com/D27833
mozglue/tests/interceptor/TestDllInterceptor.cpp
mozglue/tests/interceptor/moz.build
security/sandbox/win/SandboxInitialization.cpp
--- a/mozglue/tests/interceptor/TestDllInterceptor.cpp
+++ b/mozglue/tests/interceptor/TestDllInterceptor.cpp
@@ -4,17 +4,19 @@
 
 #include <shlobj.h>
 #include <stdio.h>
 #include <commdlg.h>
 #define SECURITY_WIN32
 #include <security.h>
 #include <wininet.h>
 #include <schnlsp.h>
+#include <winternl.h>
 
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
 #include "mozilla/TypeTraits.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WindowsVersion.h"
 #include "nsWindowsDllInterceptor.h"
 #include "nsWindowsHelpers.h"
 
 NTSTATUS NTAPI NtFlushBuffersFile(HANDLE, PIO_STATUS_BLOCK);
 NTSTATUS NTAPI NtReadFile(HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID,
@@ -44,16 +46,18 @@ NTSTATUS NTAPI NtMapViewOfSection(
 
 // These pointers are disguised as PVOID to avoid pulling in obscure headers
 PVOID NTAPI LdrResolveDelayLoadedAPI(PVOID, PVOID, PVOID, PVOID, PVOID, ULONG);
 void CALLBACK ProcessCaretEvents(HWINEVENTHOOK, DWORD, HWND, LONG, LONG, DWORD,
                                  DWORD);
 void __fastcall BaseThreadInitThunk(BOOL aIsInitialThread, void* aStartAddress,
                                     void* aThreadParam);
 
+BOOL WINAPI ApiSetQueryApiSetPresence(PCUNICODE_STRING, PBOOLEAN);
+
 using namespace mozilla;
 
 struct payload {
   UINT64 a;
   UINT64 b;
   UINT64 c;
 
   bool operator==(const payload& other) const {
@@ -408,47 +412,58 @@ bool MaybeTestHook(const bool cond, cons
       reinterpret_cast<bool (*)(typename ReturnType<decltype(&func)>::Type)>( \
           NULL))
 
 bool ShouldTestTipTsf() {
   if (!IsWin8OrLater()) {
     return false;
   }
 
-  nsModuleHandle shell32(LoadLibraryW(L"shell32.dll"));
-  if (!shell32) {
-    return true;
-  }
-
-  auto pSHGetKnownFolderPath =
-      reinterpret_cast<decltype(&SHGetKnownFolderPath)>(
-          GetProcAddress(shell32, "SHGetKnownFolderPath"));
+  mozilla::DynamicallyLinkedFunctionPtr<decltype(&SHGetKnownFolderPath)>
+    pSHGetKnownFolderPath(L"shell32.dll", "SHGetKnownFolderPath");
   if (!pSHGetKnownFolderPath) {
-    return true;
+    return false;
   }
 
   PWSTR commonFilesPath = nullptr;
   if (FAILED(pSHGetKnownFolderPath(FOLDERID_ProgramFilesCommon, 0, nullptr,
                                    &commonFilesPath))) {
-    return true;
+    return false;
   }
 
   wchar_t fullPath[MAX_PATH + 1] = {};
   wcscpy(fullPath, commonFilesPath);
   wcscat(fullPath, L"\\Microsoft Shared\\Ink\\tiptsf.dll");
   CoTaskMemFree(commonFilesPath);
 
   if (!LoadLibraryW(fullPath)) {
     return false;
   }
 
   // Leak the module so that it's loaded for the interceptor test
   return true;
 }
 
+static const wchar_t gEmptyUnicodeStringLiteral[] = L"";
+static UNICODE_STRING gEmptyUnicodeString;
+static BOOLEAN gIsPresent;
+
+bool HasApiSetQueryApiSetPresence() {
+  mozilla::DynamicallyLinkedFunctionPtr<decltype(&ApiSetQueryApiSetPresence)>
+    func(L"Api-ms-win-core-apiquery-l1-1-0.dll", "ApiSetQueryApiSetPresence");
+  if (!func) {
+    return false;
+  }
+
+  // Prepare gEmptyUnicodeString for the test
+  ::RtlInitUnicodeString(&gEmptyUnicodeString, gEmptyUnicodeStringLiteral);
+
+  return true;
+}
+
 #if defined(_M_X64)
 
 // Use VMSharingPolicyUnique for the TenByteInterceptor, as it needs to
 // reserve its trampoline memory in a special location.
 using TenByteInterceptor = mozilla::interceptor::WindowsDllInterceptor<
     mozilla::interceptor::VMSharingPolicyUnique<
         mozilla::interceptor::MMPolicyInProcess,
         mozilla::interceptor::kDefaultTrampolineSize>>;
@@ -677,17 +692,19 @@ extern "C" int wmain(int argc, wchar_t* 
 
       TEST_HOOK(sspicli.dll, AcquireCredentialsHandleA, NotEquals, SEC_E_OK) &&
       TEST_HOOK(sspicli.dll, QueryCredentialsAttributesA, NotEquals,
                 SEC_E_OK) &&
 #if !defined(_M_ARM64)
       TEST_HOOK(sspicli.dll, FreeCredentialsHandle, NotEquals, SEC_E_OK) &&
 #endif
       TEST_DETOUR_SKIP_EXEC(kernel32.dll, BaseThreadInitThunk) &&
-      TEST_DETOUR_SKIP_EXEC(ntdll.dll, LdrLoadDll) && TestTenByteDetour()) {
+      TEST_DETOUR_SKIP_EXEC(ntdll.dll, LdrLoadDll) &&
+      MAYBE_TEST_HOOK_PARAMS(HasApiSetQueryApiSetPresence(), Api-ms-win-core-apiquery-l1-1-0.dll, ApiSetQueryApiSetPresence, Equals, FALSE, &gEmptyUnicodeString, &gIsPresent) &&
+      TestTenByteDetour()) {
     printf("TEST-PASS | WindowsDllInterceptor | all checks passed\n");
 
     LARGE_INTEGER end, freq;
     QueryPerformanceCounter(&end);
 
     QueryPerformanceFrequency(&freq);
 
     LARGE_INTEGER result;
--- a/mozglue/tests/interceptor/moz.build
+++ b/mozglue/tests/interceptor/moz.build
@@ -16,16 +16,17 @@ if CONFIG['OS_TARGET'] == 'WINNT' and CO
     GeckoCppUnitTests(
         [
           'TestDllInterceptorCrossProcess',
         ],
         linkage=None
     )
 
 OS_LIBS += [
+    'ntdll',
     'ole32',
 ]
 
 if CONFIG['OS_TARGET'] == 'WINNT' and CONFIG['CC_TYPE'] in ('gcc', 'clang'):
     # This allows us to use wmain as the entry point on mingw
     LDFLAGS += [
         '-municode',
     ]
--- a/security/sandbox/win/SandboxInitialization.cpp
+++ b/security/sandbox/win/SandboxInitialization.cpp
@@ -4,17 +4,19 @@
  * 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 "SandboxInitialization.h"
 
 #include "base/memory/ref_counted.h"
 #include "nsWindowsDllInterceptor.h"
 #include "sandbox/win/src/sandbox_factory.h"
+#include "mozilla/DebugOnly.h"
 #include "mozilla/sandboxing/permissionsService.h"
+#include "mozilla/WindowsProcessMitigations.h"
 
 namespace mozilla {
 namespace sandboxing {
 
 typedef BOOL(WINAPI* CloseHandle_func)(HANDLE hObject);
 static WindowsDllInterceptor::FuncHookType<CloseHandle_func> stub_CloseHandle;
 
 typedef BOOL(WINAPI* DuplicateHandle_func)(
@@ -41,17 +43,36 @@ static BOOL WINAPI patched_DuplicateHand
     base::win::OnHandleBeingClosed(hSourceHandle);
   }
 
   return stub_DuplicateHandle(hSourceProcessHandle, hSourceHandle,
                               hTargetProcessHandle, lpTargetHandle,
                               dwDesiredAccess, bInheritHandle, dwOptions);
 }
 
+typedef BOOL (WINAPI* ApiSetQueryApiSetPresence_func)(PCUNICODE_STRING, PBOOLEAN);
+static WindowsDllInterceptor::FuncHookType<ApiSetQueryApiSetPresence_func> stub_ApiSetQueryApiSetPresence;
+
+static const WCHAR gApiSetNtUserWindowStation[] =
+  L"ext-ms-win-ntuser-windowstation-l1-1-0";
+
+static BOOL WINAPI patched_ApiSetQueryApiSetPresence(
+    PCUNICODE_STRING aNamespace, PBOOLEAN aPresent) {
+  if (aNamespace && aPresent && !wcsncmp(aNamespace->Buffer,
+                                         gApiSetNtUserWindowStation,
+                                         aNamespace->Length / sizeof(WCHAR))) {
+    *aPresent = FALSE;
+    return TRUE;
+  }
+
+  return stub_ApiSetQueryApiSetPresence(aNamespace, aPresent);
+}
+
 static WindowsDllInterceptor Kernel32Intercept;
+static WindowsDllInterceptor gApiQueryIntercept;
 
 static bool EnableHandleCloseMonitoring() {
   Kernel32Intercept.Init("kernel32.dll");
   bool hooked = stub_CloseHandle.Set(Kernel32Intercept, "CloseHandle",
                                      &patched_CloseHandle);
   if (!hooked) {
     return false;
   }
@@ -60,16 +81,36 @@ static bool EnableHandleCloseMonitoring(
                                     &patched_DuplicateHandle);
   if (!hooked) {
     return false;
   }
 
   return true;
 }
 
+/**
+ * There is a bug in COM that causes its initialization to fail when user32.dll
+ * is loaded but Win32k lockdown is enabled. COM uses ApiSetQueryApiSetPresence
+ * to make this check. When we are under Win32k lockdown, we hook
+ * ApiSetQueryApiSetPresence and force it to tell the caller that the DLL of
+ * interest is not present.
+ */
+static void EnableApiQueryInterception() {
+  if (!IsWin32kLockedDown()) {
+    return;
+  }
+
+  gApiQueryIntercept.Init(L"Api-ms-win-core-apiquery-l1-1-0.dll");
+  DebugOnly<bool> hookSetOk =
+    stub_ApiSetQueryApiSetPresence.Set(gApiQueryIntercept,
+                                       "ApiSetQueryApiSetPresence",
+                                       &patched_ApiSetQueryApiSetPresence);
+  MOZ_ASSERT(hookSetOk);
+}
+
 static bool ShouldDisableHandleVerifier() {
 #if defined(_X86_) && (defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG))
   // Chromium only has the verifier enabled for 32-bit and our close monitoring
   // hooks cause debug assertions for 64-bit anyway.
   // For x86 keep the verifier enabled by default only for Nightly or debug.
   return false;
 #else
   return !getenv("MOZ_ENABLE_HANDLE_VERIFIER");
@@ -83,16 +124,18 @@ static void InitializeHandleVerifier() {
     base::win::DisableHandleVerifier();
   }
 }
 
 static sandbox::TargetServices* InitializeTargetServices() {
   // This might disable the verifier, so we want to do it before it is used.
   InitializeHandleVerifier();
 
+  EnableApiQueryInterception();
+
   sandbox::TargetServices* targetServices =
       sandbox::SandboxFactory::GetTargetServices();
   if (!targetServices) {
     return nullptr;
   }
 
   if (targetServices->Init() != sandbox::SBOX_ALL_OK) {
     return nullptr;