Source code

Revision control

Other Tools

/* -*- 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/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