Bug 1567614: Part 1 - Refactor launcher process's LaunchUnelevated to delegate to ShellExecuteByExplorer; r=jmathies
authorAaron Klotz <aklotz@mozilla.com>
Tue, 23 Jul 2019 20:17:58 +0000
changeset 483900 e8bf96e8321a7966be57bf567ce5aad7688f1e2e
parent 483899 20e1a3ecd04a6257d950ceff053b286b0ad748b2
child 483901 cae98337ddda1b78db120235072ef5154cdf65f5
push id36336
push usermalexandru@mozilla.com
push dateWed, 24 Jul 2019 09:54:28 +0000
treeherdermozilla-central@5585edba8fdb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjmathies
bugs1567614
milestone70.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 1567614: Part 1 - Refactor launcher process's LaunchUnelevated to delegate to ShellExecuteByExplorer; r=jmathies 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',