Skip to content

Commit

Permalink
Expose individual elevator objects.
Browse files Browse the repository at this point in the history
* This change exposes individual elevator class factories via the Elevator.
* A more tenable approach as compared to the former commands-based approach.

Design document: Design document link will be added to the linked bug soon.

Bug: 833687
Change-Id: Iea4b981c7b4375788c8333a4fe5dc0ac51c1585d
Reviewed-on: https://chromium-review.googlesource.com/1106045
Reviewed-by: Nico Weber <thakis@chromium.org>
Reviewed-by: Sorin Jianu <sorin@chromium.org>
Reviewed-by: Greg Thompson <grt@chromium.org>
Commit-Queue: S. Ganesh <ganesh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#571576}
  • Loading branch information
GitHubGanesh authored and Commit Bot committed Jun 29, 2018
1 parent 62daa3a commit ddcc97f
Show file tree
Hide file tree
Showing 13 changed files with 236 additions and 347 deletions.
17 changes: 7 additions & 10 deletions chrome/elevation_service/elevation_service_idl.idl
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,16 @@ import "ocidl.idl";
object,
oleautomation,
uuid(A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C),
helpstring("IRegisteredCommandElevator Interface"),
helpstring("IElevator Interface"),
pointer_default(unique)
]
interface IRegisteredCommandElevator : IUnknown
interface IElevator : IUnknown
{
// Runs a command elevated.
// Returns the class factory for a specific elevator.
//
// @param event_id Unique id for the command
// @param caller_proc_id The process id of the calling process
// @param proc_handle The process handle valid in the caller's context
HRESULT LaunchCommand([in, string] const WCHAR* cmd_id,
[in] DWORD caller_proc_id,
[out] ULONG_PTR* proc_handle);
// @param elevator_id Unique id for the elevator.
HRESULT GetElevatorFactory([in, string] const WCHAR* elevator_id,
[out] IClassFactory** factory);
};

[
Expand All @@ -32,5 +29,5 @@ interface IRegisteredCommandElevator : IUnknown
library ElevatorLib {
importlib("stdole2.tlb");

interface IRegisteredCommandElevator;
interface IElevator;
};
187 changes: 12 additions & 175 deletions chrome/elevation_service/elevator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,188 +9,25 @@
#include "base/win/registry.h"
#include "base/win/scoped_handle.h"
#include "base/win/scoped_process_information.h"
#include "chrome/elevation_service/service_main.h"
#include "chrome/install_static/install_util.h"

namespace elevation_service {

namespace {
IFACEMETHODIMP Elevator::GetElevatorFactory(const base::char16* elevator_id,
IClassFactory** factory) {
DCHECK(elevator_id);
DCHECK(factory);

// This registry key contains the commands that the Elevator can run.
constexpr base::char16 kCommandsRegistryKey[] = L"Commands";
constexpr base::char16 kPathAndName[] = L"PathAndName";
constexpr base::char16 kArguments[] = L"Arguments";
*factory = nullptr;

HRESULT HRESULTFromLastError() {
const DWORD error_code = ::GetLastError();
return (error_code != ERROR_SUCCESS) ? HRESULT_FROM_WIN32(error_code) :
E_FAIL;
}

// TODO(ganesh): we need to have the installer write into the proper key for the
// bitness (i.e., no KEY_WOW64 specifier) and make sure it deletes a stale value
// in the opposite bitness hive to cover cases where a 32-bit install is updated
// to 64-bit.
HRESULT GetCommandToLaunch(base::StringPiece16 cmd_id,
base::FilePath* path_and_name,
base::string16* args) {
base::win::RegKey key;

auto registry_key_path =
install_static::GetRegistryPath() + L"\\" + kCommandsRegistryKey + L"\\";
cmd_id.AppendToString(&registry_key_path);

LONG result =
key.Open(HKEY_LOCAL_MACHINE, registry_key_path.c_str(), KEY_QUERY_VALUE);
if (result != ERROR_SUCCESS)
return HRESULT_FROM_WIN32(result);

base::string16 string_path_and_name;
result = key.ReadValue(kPathAndName, &string_path_and_name);
if (result != ERROR_SUCCESS)
return HRESULT_FROM_WIN32(result);

*path_and_name = base::FilePath(string_path_and_name);
if (!path_and_name->IsAbsolute())
return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);

result = key.ReadValue(kArguments, args);
if (result != ERROR_FILE_NOT_FOUND)
return HRESULT_FROM_WIN32(result);

return S_OK;
}

HRESULT StartProcess(const base::FilePath& path_and_name,
const base::string16& args,
PROCESS_INFORMATION* pi) {
DCHECK(pi);

STARTUPINFO si = {sizeof(si)};
si.dwFlags = STARTF_FORCEOFFFEEDBACK;

// We have had problems in the past with subprocs preventing chrome from being
// uninstalled because their cwd was within Google\Chrome\Application\. Hence,
// making the starting directory the Temp directory. This should be
// \\Windows\\Temp for the Service.
const base::char16* starting_directory = nullptr;
base::FilePath temp_dir;
if (base::GetTempDir(&temp_dir))
starting_directory = temp_dir.value().c_str();

base::string16 command_line = L"\"" + path_and_name.value() + L"\" " + args;
command_line.push_back(L'\0');
bool success =
::CreateProcess(path_and_name.value().c_str(), // Application path/name
&command_line[0], // Command line
nullptr, // Process handle not inheritable
nullptr, // Thread handle not inheritable
false, // Set handle inheritance to FALSE
0, // No creation flags
nullptr, // Use parent's environment block
starting_directory, // Use TMP
&si, // Pointer to STARTUPINFO struct
pi); // Pointer to PROCESS_INFORMATION structure

if (!success) {
HRESULT hr = HRESULTFromLastError();
LOG(ERROR) << "StartProcess::CreateProcess failed; hr: " << hr
<< ", command_line: " << path_and_name << ", args:" << args;
return hr;
}

return S_OK;
}

HRESULT OpenCallingProcess(DWORD proc_id, base::Process* process) {
DCHECK(process);

HRESULT hr = ::CoImpersonateClient();
if (FAILED(hr))
return hr;

*process = base::Process::OpenWithAccess(proc_id, PROCESS_DUP_HANDLE);
hr = process->IsValid() ? S_OK : HRESULTFromLastError();

::CoRevertToSelf();
return hr;
}

HRESULT LaunchCmd(const base::FilePath& path_and_name,
const base::string16& args,
const base::Process& calling_process,
base::win::ScopedHandle* proc_handle) {
DCHECK(!path_and_name.empty());
DCHECK(proc_handle);

PROCESS_INFORMATION process_info = {};
HRESULT hr = StartProcess(path_and_name, args, &process_info);
if (FAILED(hr)) {
LOG(ERROR) << "failed to launch app: " << path_and_name << ", args:" << args
<< "; hr: " << hr;
return hr;
}
base::win::ScopedProcessInformation pi(process_info);

// DuplicateHandle call will close the source handle regardless of any error
// status returned.
DCHECK(pi.process_handle());

HANDLE duplicate_proc_handle = nullptr;

constexpr DWORD desired_access =
PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE;
bool res = ::DuplicateHandle(
::GetCurrentProcess(), // Current process.
pi.TakeProcessHandle(), // Process handle to duplicate.
calling_process.Handle(), // Process receiving the handle.
&duplicate_proc_handle, // Duplicated handle.
desired_access, // Access requested for the new handle.
FALSE, // Don't inherit the new handle.
DUPLICATE_CLOSE_SOURCE) != 0; // Closes the source handle.
if (!res) {
hr = HRESULTFromLastError();
LOG(ERROR) << "failed to duplicate the handle; hr: " << hr;
return hr;
}

proc_handle->Set(duplicate_proc_handle);

return S_OK;
}

} // namespace

IFACEMETHODIMP Elevator::LaunchCommand(const WCHAR* cmd_id,
DWORD caller_proc_id,
ULONG_PTR* proc_handle) {
DCHECK(cmd_id);
DCHECK(proc_handle);

const base::StringPiece16 id = cmd_id;
if (id.empty() || id.find(L'\\') != base::StringPiece16::npos || !proc_handle)
return E_INVALIDARG;

base::Process calling_process;
HRESULT hr = OpenCallingProcess(caller_proc_id, &calling_process);
if (FAILED(hr)) {
LOG(ERROR) << "failed to open caller's handle; hr: " << hr
<< ", cmd_id: " << cmd_id << ", pid: " << caller_proc_id;
return hr;
}

base::FilePath path_and_name;
base::string16 args;
hr = GetCommandToLaunch(id, &path_and_name, &args);
if (FAILED(hr))
return hr;

base::win::ScopedHandle scoped_proc_handle;
hr = LaunchCmd(path_and_name, args, calling_process, &scoped_proc_handle);
if (FAILED(hr))
return hr;
elevation_service::ServiceMain* service =
elevation_service::ServiceMain::GetInstance();
Microsoft::WRL::ComPtr<IClassFactory> f =
service->GetElevatorFactory(elevator_id);
f.CopyTo(factory);

*proc_handle = reinterpret_cast<ULONG_PTR>(scoped_proc_handle.Take());
return hr;
return *factory ? S_OK : E_INVALIDARG;
}

Elevator::~Elevator() = default;
Expand Down
7 changes: 4 additions & 3 deletions chrome/elevation_service/elevator.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,20 @@
#include <wrl/module.h>

#include "base/macros.h"
#include "base/strings/string16.h"
#include "chrome/elevation_service/elevation_service_idl.h"

namespace elevation_service {

class Elevator
: public Microsoft::WRL::RuntimeClass<
Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
IRegisteredCommandElevator> {
IElevator> {
public:
Elevator() = default;

IFACEMETHOD(LaunchCommand)
(const WCHAR* cmd_id, DWORD caller_proc_id, ULONG_PTR* proc_handle);
IFACEMETHOD(GetElevatorFactory)(const base::char16* elevator_id,
IClassFactory** factory);

private:
~Elevator() override;
Expand Down
29 changes: 29 additions & 0 deletions chrome/elevation_service/service_main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ HRESULT ServiceMain::RegisterClassObjects() {
auto& module = Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::Create(
this, &ServiceMain::SignalExit);

// Register the Elevator class factories.
RegisterElevatorFactories();

// We hand-register a unique CLSID for each Chrome channel.
Microsoft::WRL::ComPtr<IUnknown> factory;
unsigned int flags = Microsoft::WRL::ModuleType::OutOfProc;
Expand Down Expand Up @@ -109,6 +112,9 @@ void ServiceMain::UnregisterClassObjects() {
module.UnregisterCOMObject(nullptr, cookies_, base::size(cookies_));
if (FAILED(hr))
LOG(ERROR) << "NotificationActivator unregistration failed; hr: " << hr;

// Unregister the Elevator class factories.
UnregisterElevatorFactories();
}

bool ServiceMain::IsExitSignaled() {
Expand Down Expand Up @@ -245,4 +251,27 @@ void ServiceMain::SignalExit() {
exit_signal_.Signal();
}

void ServiceMain::RegisterElevatorFactories() {
// Elevators will register their class factories here by calling
// RegisterElevatorFactory().
}

void ServiceMain::UnregisterElevatorFactories() {
factories_.clear();
}

void ServiceMain::RegisterElevatorFactory(const base::string16& id,
IClassFactory* factory) {
DCHECK(factory);
DCHECK(!base::ContainsKey(factories_, id));

factories_.emplace(id, factory);
}

Microsoft::WRL::ComPtr<IClassFactory> ServiceMain::GetElevatorFactory(
const base::string16& id) {
auto it = factories_.find(id);
return it != factories_.end() ? it->second : nullptr;
}

} // namespace elevation_service
19 changes: 19 additions & 0 deletions chrome/elevation_service/service_main.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
#define CHROME_ELEVATION_SERVICE_SERVICE_MAIN_H_

#include <windows.h>
#include <wrl/implements.h>

#include "base/containers/flat_map.h"
#include "base/no_destructor.h"
#include "base/synchronization/waitable_event.h"

Expand Down Expand Up @@ -41,6 +43,10 @@ class ServiceMain {
// to exit.
bool IsExitSignaled();

// Returns the class factory for the elevator with the specified id.
Microsoft::WRL::ComPtr<IClassFactory> GetElevatorFactory(
const base::string16& id);

private:
ServiceMain();
~ServiceMain();
Expand Down Expand Up @@ -80,6 +86,14 @@ class ServiceMain {
// Called when the last object is released or if the service is asked to exit.
void SignalExit();

// Registers class factories for all supported elevators.
void RegisterElevatorFactories();
void UnregisterElevatorFactories();

// Registers |factory| as the factory for the elevator identified by |id|.
void RegisterElevatorFactory(const base::string16& id,
IClassFactory* factory);

// The action routine to be executed.
int (ServiceMain::*run_routine_)();

Expand All @@ -93,6 +107,11 @@ class ServiceMain {
// service control manager asks the service to exit.
base::WaitableEvent exit_signal_;

using FactoryMap = base::flat_map<base::string16,
Microsoft::WRL::ComPtr<IClassFactory>>;
// The map containing all the Elevator class factories.
FactoryMap factories_;

friend class base::NoDestructor<ServiceMain>;

DISALLOW_COPY_AND_ASSIGN(ServiceMain);
Expand Down
Loading

0 comments on commit ddcc97f

Please sign in to comment.