Bug 1567614: Part 1 - Refactor launcher process's LaunchUnelevated to delegate to ShellExecuteByExplorer; r=jmathies, a=jcristau
authorAaron Klotz <aklotz@mozilla.com>
Tue, 23 Jul 2019 20:17:58 +0000
changeset 537278 05634b24b8ef1c5808998710ea1375f7cac95981
parent 537277 fdf90a977a136dd0c37ef8b3b4d8402f0862da04
child 537279 51aa10f2e418efada7ba24bc47cf63a8664d6955
push id2122
push userarchaeopteryx@coole-files.de
push dateWed, 07 Aug 2019 14:22:14 +0000
treeherdermozilla-release@02d9056ba76c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjmathies, jcristau
bugs1567614
milestone68.0.2
Bug 1567614: Part 1 - Refactor launcher process's LaunchUnelevated to delegate to ShellExecuteByExplorer; r=jmathies, a=jcristau This is just so that both the launcher process and other Gecko code can share this method. Differential Revision: https://phabricator.services.mozilla.com/D38943
browser/app/winlauncher/LaunchUnelevated.cpp
widget/windows/ShellHeaderOnlyUtils.h
widget/windows/moz.build
--- a/browser/app/winlauncher/LaunchUnelevated.cpp
+++ b/browser/app/winlauncher/LaunchUnelevated.cpp
@@ -6,28 +6,20 @@
 
 #include "LaunchUnelevated.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/CmdLineAndEnvUtils.h"
 #include "mozilla/LauncherResult.h"
 #include "mozilla/mscom/ProcessRuntime.h"
 #include "mozilla/RefPtr.h"
+#include "mozilla/ShellHeaderOnlyUtils.h"
 #include "nsWindowsHelpers.h"
 
-// For _bstr_t and _variant_t
-#include <comdef.h>
-#include <comutil.h>
-
 #include <windows.h>
-#include <exdisp.h>
-#include <objbase.h>
-#include <servprov.h>
-#include <shlobj.h>
-#include <shobjidl.h>
 
 static mozilla::LauncherResult<TOKEN_ELEVATION_TYPE> GetElevationType(
     const nsAutoHandle& aToken) {
   DWORD retLen;
   TOKEN_ELEVATION_TYPE elevationType;
   if (!::GetTokenInformation(aToken.get(), TokenElevationType, &elevationType,
                              sizeof(elevationType), &retLen)) {
     return LAUNCHER_ERROR_FROM_LAST();
@@ -104,105 +96,28 @@ namespace mozilla {
 LauncherVoidResult LaunchUnelevated(int aArgc, wchar_t* aArgv[]) {
   // We need COM to talk to Explorer. Using ProcessRuntime so that
   // process-global COM configuration is done correctly
   mozilla::mscom::ProcessRuntime mscom(GeckoProcessType_Default);
   if (!mscom) {
     return LAUNCHER_ERROR_FROM_HRESULT(mscom.GetHResult());
   }
 
-  // NB: Explorer is a local server, not an inproc server
-  RefPtr<IShellWindows> shellWindows;
-  HRESULT hr =
-      ::CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_LOCAL_SERVER,
-                         IID_IShellWindows, getter_AddRefs(shellWindows));
-  if (FAILED(hr)) {
-    return LAUNCHER_ERROR_FROM_HRESULT(hr);
-  }
-
-  // 1. Find the shell view for the desktop.
-  _variant_t loc(CSIDL_DESKTOP);
-  _variant_t empty;
-  long hwnd;
-  RefPtr<IDispatch> dispDesktop;
-  hr = shellWindows->FindWindowSW(&loc, &empty, SWC_DESKTOP, &hwnd,
-                                  SWFO_NEEDDISPATCH,
-                                  getter_AddRefs(dispDesktop));
-  if (FAILED(hr)) {
-    return LAUNCHER_ERROR_FROM_HRESULT(hr);
-  }
-
-  RefPtr<IServiceProvider> servProv;
-  hr = dispDesktop->QueryInterface(IID_IServiceProvider,
-                                   getter_AddRefs(servProv));
-  if (FAILED(hr)) {
-    return LAUNCHER_ERROR_FROM_HRESULT(hr);
-  }
-
-  RefPtr<IShellBrowser> browser;
-  hr = servProv->QueryService(SID_STopLevelBrowser, IID_IShellBrowser,
-                              getter_AddRefs(browser));
-  if (FAILED(hr)) {
-    return LAUNCHER_ERROR_FROM_HRESULT(hr);
-  }
-
-  RefPtr<IShellView> activeShellView;
-  hr = browser->QueryActiveShellView(getter_AddRefs(activeShellView));
-  if (FAILED(hr)) {
-    return LAUNCHER_ERROR_FROM_HRESULT(hr);
-  }
-
-  // 2. Get the automation object for the desktop.
-  RefPtr<IDispatch> dispView;
-  hr = activeShellView->GetItemObject(SVGIO_BACKGROUND, IID_IDispatch,
-                                      getter_AddRefs(dispView));
-  if (FAILED(hr)) {
-    return LAUNCHER_ERROR_FROM_HRESULT(hr);
-  }
-
-  RefPtr<IShellFolderViewDual> folderView;
-  hr = dispView->QueryInterface(IID_IShellFolderViewDual,
-                                getter_AddRefs(folderView));
-  if (FAILED(hr)) {
-    return LAUNCHER_ERROR_FROM_HRESULT(hr);
-  }
-
-  // 3. Get the interface to IShellDispatch2
-  RefPtr<IDispatch> dispShell;
-  hr = folderView->get_Application(getter_AddRefs(dispShell));
-  if (FAILED(hr)) {
-    return LAUNCHER_ERROR_FROM_HRESULT(hr);
-  }
-
-  RefPtr<IShellDispatch2> shellDisp;
-  hr =
-      dispShell->QueryInterface(IID_IShellDispatch2, getter_AddRefs(shellDisp));
-  if (FAILED(hr)) {
-    return LAUNCHER_ERROR_FROM_HRESULT(hr);
-  }
-
-  // 4. Now call IShellDispatch2::ShellExecute to ask Explorer to re-launch us.
-
   // Omit argv[0] because ShellExecute doesn't need it in params
   UniquePtr<wchar_t[]> cmdLine(MakeCommandLine(aArgc - 1, aArgv + 1));
   if (!cmdLine) {
     return LAUNCHER_ERROR_GENERIC();
   }
 
   _bstr_t exe(aArgv[0]);
   _variant_t args(cmdLine.get());
   _variant_t operation(L"open");
   _variant_t directory;
   _variant_t showCmd(SW_SHOWNORMAL);
-  hr = shellDisp->ShellExecute(exe, args, operation, directory, showCmd);
-  if (FAILED(hr)) {
-    return LAUNCHER_ERROR_FROM_HRESULT(hr);
-  }
-
-  return Ok();
+  return ShellExecuteByExplorer(exe, args, operation, directory, showCmd);
 }
 
 LauncherResult<ElevationState> GetElevationState(
     mozilla::LauncherFlags aFlags, nsAutoHandle& aOutMediumIlToken) {
   aOutMediumIlToken.reset();
 
   const DWORD tokenFlags = TOKEN_QUERY | TOKEN_DUPLICATE |
                            TOKEN_ADJUST_DEFAULT | TOKEN_ASSIGN_PRIMARY;
new file mode 100644
--- /dev/null
+++ b/widget/windows/ShellHeaderOnlyUtils.h
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ShellHeaderOnlyUtils_h
+#define mozilla_ShellHeaderOnlyUtils_h
+
+#include "mozilla/LauncherResult.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+
+#include <objbase.h>
+
+#include <exdisp.h>
+#include <shldisp.h>
+#include <shlobj.h>
+#include <shlwapi.h>
+#include <shobjidl.h>
+#include <shtypes.h>
+// NB: include this after shldisp.h so its macros do not conflict with COM
+// interfaces defined by shldisp.h
+#include <shellapi.h>
+
+#include <comdef.h>
+#include <comutil.h>
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/TypeTraits.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+
+/**
+ * Ask the current user's Desktop to ShellExecute on our behalf, thus causing
+ * the resulting launched process to inherit its security priviliges from
+ * Explorer instead of our process.
+ *
+ * This is useful in two scenarios, in particular:
+ *   * We are running as an elevated user and we want to start something as the
+ *     "normal" user;
+ *   * We are starting a process that is incompatible with our process's
+ *     process mitigation policies. By delegating to Explorer, the child process
+ *     will not be affected by our process mitigations.
+ *
+ * Since this communication happens over DCOM, Explorer's COM DACL governs
+ * whether or not we can execute against it, thus avoiding privilege escalation.
+ */
+inline LauncherVoidResult ShellExecuteByExplorer(const _bstr_t& aPath,
+                                                 const _variant_t& aArgs,
+                                                 const _variant_t& aVerb,
+                                                 const _variant_t& aWorkingDir,
+                                                 const _variant_t& aShowCmd) {
+  // NB: Explorer may be a local server, not an inproc server
+  RefPtr<IShellWindows> shellWindows;
+  HRESULT hr = ::CoCreateInstance(
+      CLSID_ShellWindows, nullptr, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,
+      IID_IShellWindows, getter_AddRefs(shellWindows));
+  if (FAILED(hr)) {
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
+  }
+
+  // 1. Find the shell view for the desktop.
+  _variant_t loc(CSIDL_DESKTOP);
+  _variant_t empty;
+  long hwnd;
+  RefPtr<IDispatch> dispDesktop;
+  hr = shellWindows->FindWindowSW(&loc, &empty, SWC_DESKTOP, &hwnd,
+                                  SWFO_NEEDDISPATCH,
+                                  getter_AddRefs(dispDesktop));
+  if (FAILED(hr)) {
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
+  }
+
+  RefPtr<IServiceProvider> servProv;
+  hr = dispDesktop->QueryInterface(IID_IServiceProvider,
+                                   getter_AddRefs(servProv));
+  if (FAILED(hr)) {
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
+  }
+
+  RefPtr<IShellBrowser> browser;
+  hr = servProv->QueryService(SID_STopLevelBrowser, IID_IShellBrowser,
+                              getter_AddRefs(browser));
+  if (FAILED(hr)) {
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
+  }
+
+  RefPtr<IShellView> activeShellView;
+  hr = browser->QueryActiveShellView(getter_AddRefs(activeShellView));
+  if (FAILED(hr)) {
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
+  }
+
+  // 2. Get the automation object for the desktop.
+  RefPtr<IDispatch> dispView;
+  hr = activeShellView->GetItemObject(SVGIO_BACKGROUND, IID_IDispatch,
+                                      getter_AddRefs(dispView));
+  if (FAILED(hr)) {
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
+  }
+
+  RefPtr<IShellFolderViewDual> folderView;
+  hr = dispView->QueryInterface(IID_IShellFolderViewDual,
+                                getter_AddRefs(folderView));
+  if (FAILED(hr)) {
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
+  }
+
+  // 3. Get the interface to IShellDispatch2
+  RefPtr<IDispatch> dispShell;
+  hr = folderView->get_Application(getter_AddRefs(dispShell));
+  if (FAILED(hr)) {
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
+  }
+
+  RefPtr<IShellDispatch2> shellDisp;
+  hr =
+      dispShell->QueryInterface(IID_IShellDispatch2, getter_AddRefs(shellDisp));
+  if (FAILED(hr)) {
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
+  }
+
+  // shellapi.h macros interfere with the correct naming of the method being
+  // called on IShellDispatch2. Temporarily remove that definition.
+#if defined(ShellExecute)
+#  define MOZ_REDEFINE_SHELLEXECUTE
+#  undef ShellExecute
+#endif  // defined(ShellExecute)
+
+  // 4. Now call IShellDispatch2::ShellExecute to ask Explorer to execute.
+  hr = shellDisp->ShellExecute(aPath, aArgs, aVerb, aWorkingDir, aShowCmd);
+  if (FAILED(hr)) {
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
+  }
+
+  // Restore the macro that was removed prior to IShellDispatch2::ShellExecute
+#if defined(MOZ_REDEFINE_SHELLEXECUTE)
+#  if defined(UNICODE)
+#    define ShellExecute ShellExecuteW
+#  else
+#    define ShellExecute ShellExecuteA
+#  endif
+#  undef MOZ_REDEFINE_SHELLEXECUTE
+#endif  // defined(MOZ_REDEFINE_SHELLEXECUTE)
+
+  return Ok();
+}
+
+}  // namespace mozilla
+
+#endif  // mozilla_ShellHeaderOnlyUtils_h
--- a/widget/windows/moz.build
+++ b/widget/windows/moz.build
@@ -14,16 +14,17 @@ TEST_DIRS += ['tests']
 
 EXPORTS += [
     'nsdefs.h',
     'WindowHook.h',
     'WinUtils.h',
 ]
 
 EXPORTS.mozilla += [
+    'ShellHeaderOnlyUtils.h',
     'WindowsConsole.h',
     'WinHeaderOnlyUtils.h',
 ]
 
 EXPORTS.mozilla.widget += [
     'AudioSession.h',
     'CompositorWidgetChild.h',
     'CompositorWidgetParent.h',