summaryrefslogtreecommitdiffstats
path: root/widget/windows/ShellHeaderOnlyUtils.h
diff options
context:
space:
mode:
Diffstat (limited to 'widget/windows/ShellHeaderOnlyUtils.h')
-rw-r--r--widget/windows/ShellHeaderOnlyUtils.h183
1 files changed, 183 insertions, 0 deletions
diff --git a/widget/windows/ShellHeaderOnlyUtils.h b/widget/windows/ShellHeaderOnlyUtils.h
new file mode 100644
index 0000000000..ea95077fb3
--- /dev/null
+++ b/widget/windows/ShellHeaderOnlyUtils.h
@@ -0,0 +1,183 @@
+/* -*- 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
+
+#if defined(LIBXUL) && !defined(UNICODE)
+# error \
+ "UNICODE not set - must be set to prevent compile failure in `comdef.h` due to us deleting `FormatMessage` when absent."
+#endif
+
+#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 <type_traits>
+
+#include <comdef.h>
+#include <comutil.h>
+
+#include "mozilla/RefPtr.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(int(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);
+ }
+
+ if (hr == S_FALSE) {
+ // The call succeeded but the window was not found.
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_NOT_FOUND);
+ }
+
+ 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);
+ }
+
+ // Passing the foreground privilege so that the shell can launch an
+ // application in the foreground. This fails with E_ACCESSDENIED if the
+ // current window is shown in the background. We keep a soft assert for
+ // the other failures to investigate how it happened.
+ hr = ::CoAllowSetForegroundWindow(shellDisp, nullptr);
+ MOZ_ASSERT(SUCCEEDED(hr) || hr == E_ACCESSDENIED);
+
+ // 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, aWorkingDir, aVerb, 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();
+}
+
+using UniqueAbsolutePidl =
+ UniquePtr<std::remove_pointer_t<PIDLIST_ABSOLUTE>, CoTaskMemFreeDeleter>;
+
+inline LauncherResult<UniqueAbsolutePidl> ShellParseDisplayName(
+ const wchar_t* aPath) {
+ PIDLIST_ABSOLUTE rawAbsPidl = nullptr;
+ SFGAOF sfgao;
+ HRESULT hr = ::SHParseDisplayName(aPath, nullptr, &rawAbsPidl, 0, &sfgao);
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ return UniqueAbsolutePidl(rawAbsPidl);
+}
+
+} // namespace mozilla
+
+#endif // mozilla_ShellHeaderOnlyUtils_h