Commit 77363287 authored by alexeypa@chromium.org's avatar alexeypa@chromium.org

[Chromoting] Launch the host process elevated via ShellExecuteEx().

The host process is launched in in two steps now. An instance of remote_service.exe is launched in a user session (CreateProcessAsUser) and then it launches the host requesting elevation (ShellExecuteEx). This is needed because Windows 8 refuses to inject Alt+Tab unless uiAccess='true' is specified in the manifest, which in its turn requires ShellExecuteEx() to be used.

Lifetime of launched processes is controlled by assigning them to a job object.

Message loop changes are required to be able to process job object notifications on the I/O message loop.

BUG=135217

Review URL: https://chromiumcodereview.appspot.com/10831271

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@151973 0039d316-1c4b-4281-b951-d872f2087c98
parent 3a49e50f
......@@ -751,6 +751,10 @@ void MessageLoopForIO::RegisterIOHandler(HANDLE file, IOHandler* handler) {
pump_io()->RegisterIOHandler(file, handler);
}
bool MessageLoopForIO::RegisterJobObject(HANDLE job, IOHandler* handler) {
return pump_io()->RegisterJobObject(job, handler);
}
bool MessageLoopForIO::WaitForIOCompletion(DWORD timeout, IOHandler* filter) {
return pump_io()->WaitForIOCompletion(timeout, filter);
}
......
......@@ -631,7 +631,8 @@ class BASE_EXPORT MessageLoopForIO : public MessageLoop {
#if defined(OS_WIN)
// Please see MessagePumpWin for definitions of these methods.
void RegisterIOHandler(HANDLE file_handle, IOHandler* handler);
void RegisterIOHandler(HANDLE file, IOHandler* handler);
bool RegisterJobObject(HANDLE job, IOHandler* handler);
bool WaitForIOCompletion(DWORD timeout, IOHandler* filter);
protected:
......
......@@ -475,11 +475,26 @@ void MessagePumpForIO::ScheduleDelayedWork(const TimeTicks& delayed_work_time) {
void MessagePumpForIO::RegisterIOHandler(HANDLE file_handle,
IOHandler* handler) {
ULONG_PTR key = reinterpret_cast<ULONG_PTR>(handler);
ULONG_PTR key = HandlerToKey(handler, true);
HANDLE port = CreateIoCompletionPort(file_handle, port_, key, 1);
DPCHECK(port);
}
bool MessagePumpForIO::RegisterJobObject(HANDLE job_handle,
IOHandler* handler) {
// Job object notifications use the OVERLAPPED pointer to carry the message
// data. Mark the completion key correspondingly, so we will not try to
// convert OVERLAPPED* to IOContext*.
ULONG_PTR key = HandlerToKey(handler, false);
JOBOBJECT_ASSOCIATE_COMPLETION_PORT info;
info.CompletionKey = reinterpret_cast<void*>(key);
info.CompletionPort = port_;
return SetInformationJobObject(job_handle,
JobObjectAssociateCompletionPortInformation,
&info,
sizeof(info)) != FALSE;
}
//-----------------------------------------------------------------------------
// MessagePumpForIO private:
......@@ -546,12 +561,16 @@ bool MessagePumpForIO::WaitForIOCompletion(DWORD timeout, IOHandler* filter) {
return true;
}
if (item.context->handler) {
// If |item.has_valid_io_context| is false then |item.context| does not point
// to a context structure, and so should not be dereferenced, although it may
// still hold valid non-pointer data.
if (!item.has_valid_io_context || item.context->handler) {
if (filter && item.handler != filter) {
// Save this item for later
completed_io_.push_back(item);
} else {
DCHECK_EQ(item.context->handler, item.handler);
DCHECK(!item.has_valid_io_context ||
(item.context->handler == item.handler));
WillProcessIOEvent();
item.handler->OnIOCompleted(item.context, item.bytes_transfered,
item.error);
......@@ -577,7 +596,7 @@ bool MessagePumpForIO::GetIOItem(DWORD timeout, IOItem* item) {
item->bytes_transfered = 0;
}
item->handler = reinterpret_cast<IOHandler*>(key);
item->handler = KeyToHandler(key, &item->has_valid_io_context);
item->context = reinterpret_cast<IOContext*>(overlapped);
return true;
}
......@@ -623,4 +642,28 @@ void MessagePumpForIO::DidProcessIOEvent() {
FOR_EACH_OBSERVER(IOObserver, io_observers_, DidProcessIOEvent());
}
// static
ULONG_PTR MessagePumpForIO::HandlerToKey(IOHandler* handler,
bool has_valid_io_context) {
ULONG_PTR key = reinterpret_cast<ULONG_PTR>(handler);
// |IOHandler| is at least pointer-size aligned, so the lowest two bits are
// always cleared. We use the lowest bit to distinguish completion keys with
// and without the associated |IOContext|.
DCHECK((key & 1) == 0);
// Mark the completion key as context-less.
if (!has_valid_io_context)
key = key | 1;
return key;
}
// static
MessagePumpForIO::IOHandler* MessagePumpForIO::KeyToHandler(
ULONG_PTR key,
bool* has_valid_io_context) {
*has_valid_io_context = ((key & 1) == 0);
return reinterpret_cast<IOHandler*>(key & ~static_cast<ULONG_PTR>(1));
}
} // namespace base
......@@ -296,6 +296,12 @@ class BASE_EXPORT MessagePumpForIO : public MessagePumpWin {
// |handler| must be valid as long as there is pending IO for the given file.
void RegisterIOHandler(HANDLE file_handle, IOHandler* handler);
// Register the handler to be used to process job events. The registration
// persists as long as the job object is live, so |handler| must be valid
// until the job object is destroyed. Returns true if the registration
// succeeded, and false otherwise.
bool RegisterJobObject(HANDLE job_handle, IOHandler* handler);
// Waits for the next IO completion that should be processed by |filter|, for
// up to |timeout| milliseconds. Return true if any IO operation completed,
// regardless of the involved handler, and false if the timeout expired. If
......@@ -316,6 +322,11 @@ class BASE_EXPORT MessagePumpForIO : public MessagePumpWin {
IOContext* context;
DWORD bytes_transfered;
DWORD error;
// In some cases |context| can be a non-pointer value casted to a pointer.
// |has_valid_io_context| is true if |context| is a valid IOContext
// pointer, and false otherwise.
bool has_valid_io_context;
};
virtual void DoRunLoop();
......@@ -326,6 +337,14 @@ class BASE_EXPORT MessagePumpForIO : public MessagePumpWin {
void WillProcessIOEvent();
void DidProcessIOEvent();
// Converts an IOHandler pointer to a completion port key.
// |has_valid_io_context| specifies whether completion packets posted to
// |handler| will have valid OVERLAPPED pointers.
static ULONG_PTR HandlerToKey(IOHandler* handler, bool has_valid_io_context);
// Converts a completion port key to an IOHandler pointer.
static IOHandler* KeyToHandler(ULONG_PTR key, bool* has_valid_io_context);
// The completion port associated with this thread.
win::ScopedHandle port_;
// This list will be empty almost always. It stores IO completions that have
......
......@@ -18,7 +18,7 @@ Stoppable::Stoppable(
}
Stoppable::~Stoppable() {
DCHECK_EQ(state_, kStopped);
CHECK_EQ(state_, kStopped);
}
void Stoppable::Stop() {
......
......@@ -32,7 +32,6 @@ void HostUserInterface::Start(ChromotingHost* host,
const base::Closure& disconnect_callback) {
DCHECK(network_task_runner()->BelongsToCurrentThread());
DCHECK(host_ == NULL);
DCHECK(disconnect_callback_.is_null());
host_ = host;
disconnect_callback_ = disconnect_callback;
......@@ -91,7 +90,6 @@ base::SingleThreadTaskRunner* HostUserInterface::ui_task_runner() const {
void HostUserInterface::DisconnectSession() const {
DCHECK(ui_task_runner()->BelongsToCurrentThread());
DCHECK(!disconnect_callback_.is_null());
disconnect_callback_.Run();
}
......@@ -118,7 +116,6 @@ void HostUserInterface::StartForTest(
scoped_ptr<LocalInputMonitor> local_input_monitor) {
DCHECK(network_task_runner()->BelongsToCurrentThread());
DCHECK(host_ == NULL);
DCHECK(disconnect_callback_.is_null());
host_ = host;
disconnect_callback_ = disconnect_callback;
......
......@@ -8,11 +8,12 @@
#include "remoting/host/win/host_service.h"
#include <windows.h>
#include <shellapi.h>
#include <wtsapi32.h>
#include <stdio.h>
#include "base/at_exit.h"
#include "base/base_paths.h"
#include "base/base_switches.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/file_path.h"
......@@ -53,35 +54,42 @@ const char kIoThreadName[] = "I/O thread";
const wchar_t kSessionNotificationWindowClass[] =
L"Chromoting_SessionNotificationWindow";
// Command line actions and switches:
// "run" sumply runs the service as usual.
const wchar_t kRunActionName[] = L"run";
// Command line switches:
// "--console" runs the service interactively for debugging purposes.
const char kConsoleSwitchName[] = "console";
// "--elevate=<binary>" requests <binary> to be launched elevated, presenting
// a UAC prompt if necessary.
const char kElevateSwitchName[] = "elevate";
// "--help" or "--?" prints the usage message.
const char kHelpSwitchName[] = "help";
const char kQuestionSwitchName[] = "?";
const char kUsageMessage[] =
"\n"
"Usage: %s [action] [options]\n"
"\n"
"Actions:\n"
" run - Run the service (default if no action was specified).\n"
"\n"
"Options:\n"
" --console - Run the service interactively for debugging purposes.\n"
" --help, --? - Print this message.\n";
const wchar_t kUsageMessage[] =
L"\n"
L"Usage: %ls [options]\n"
L"\n"
L"Options:\n"
L" --console - Run the service interactively for debugging purposes.\n"
L" --elevate=<...> - Run <...> elevated.\n"
L" --help, --? - Print this message.\n";
// The command line parameters that should be copied from the service's command
// line when launching an elevated child.
const char* kCopiedSwitchNames[] = {
"auth-config", "host-config", "chromoting-ipc", switches::kV,
switches::kVModule };
// Exit codes:
const int kSuccessExitCode = 0;
const int kUsageExitCode = 1;
const int kErrorExitCode = 2;
void usage(const char* program_name) {
fprintf(stderr, kUsageMessage, program_name);
void usage(const FilePath& program_name) {
LOG(INFO) << StringPrintf(kUsageMessage,
UTF16ToWide(program_name.value()).c_str());
}
} // namespace
......@@ -164,17 +172,15 @@ HostService* HostService::GetInstance() {
bool HostService::InitWithCommandLine(const CommandLine* command_line) {
CommandLine::StringVector args = command_line->GetArgs();
// Choose the action to perform.
// Check if launch with elevation was requested.
if (command_line->HasSwitch(kElevateSwitchName)) {
run_routine_ = &HostService::Elevate;
return true;
}
if (!args.empty()) {
if (args.size() > 1) {
LOG(ERROR) << "Invalid command line: more than one action requested.";
return false;
}
if (args[0] != kRunActionName) {
LOG(ERROR) << "Invalid command line: invalid action specified: "
<< args[0];
return false;
}
LOG(ERROR) << "No positional parameters expected.";
return false;
}
// Run interactively if needed.
......@@ -225,6 +231,35 @@ void HostService::RunMessageLoop(MessageLoop* message_loop) {
stopped_event_.Signal();
}
int HostService::Elevate() {
// Get the name of the binary to launch.
FilePath binary =
CommandLine::ForCurrentProcess()->GetSwitchValuePath(kElevateSwitchName);
// Create the child process command line by copying known switches from our
// command line.
CommandLine command_line(CommandLine::NO_PROGRAM);
command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(),
kCopiedSwitchNames,
_countof(kCopiedSwitchNames));
CommandLine::StringType parameters = command_line.GetCommandLineString();
// Launch the child process requesting elevation.
SHELLEXECUTEINFO info;
memset(&info, 0, sizeof(info));
info.cbSize = sizeof(info);
info.lpVerb = L"runas";
info.lpFile = binary.value().c_str();
info.lpParameters = parameters.c_str();
info.nShow = SW_SHOWNORMAL;
if (!ShellExecuteEx(&info)) {
return GetLastError();
}
return kSuccessExitCode;
}
int HostService::RunAsService() {
SERVICE_TABLE_ENTRYW dispatch_table[] = {
{ const_cast<LPWSTR>(kWindowsServiceName), &HostService::ServiceMain },
......@@ -409,14 +444,19 @@ LRESULT CALLBACK HostService::SessionChangeNotificationProc(HWND hwnd,
} // namespace remoting
int main(int argc, char** argv) {
int CALLBACK WinMain(HINSTANCE instance,
HINSTANCE previous_instance,
LPSTR raw_command_line,
int show_command) {
#ifdef OFFICIAL_BUILD
if (remoting::IsUsageStatsAllowed()) {
remoting::InitializeCrashReporting();
}
#endif // OFFICIAL_BUILD
CommandLine::Init(argc, argv);
// CommandLine::Init() ignores the passed |argc| and |argv| on Windows getting
// the command line from GetCommandLineW(), so we can safely pass NULL here.
CommandLine::Init(0, NULL);
// This object instance is required by Chrome code (for example,
// FilePath, LazyInstance, MessageLoop).
......@@ -432,16 +472,15 @@ int main(int argc, char** argv) {
logging::DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS);
const CommandLine* command_line = CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(kHelpSwitchName) ||
command_line->HasSwitch(kQuestionSwitchName)) {
usage(argv[0]);
usage(command_line->GetProgram());
return kSuccessExitCode;
}
remoting::HostService* service = remoting::HostService::GetInstance();
if (!service->InitWithCommandLine(command_line)) {
usage(argv[0]);
usage(command_line->GetProgram());
return kUsageExitCode;
}
......
......@@ -61,6 +61,9 @@ class HostService : public WtsConsoleMonitor {
// RunAsService() and RunInConsole().
void RunMessageLoop(MessageLoop* message_loop);
// Runs the binary specified by the command line, elevated.
int Elevate();
// This function handshakes with the service control manager and starts
// the service.
int RunAsService();
......
......@@ -103,6 +103,7 @@ bool CreateRemoteSessionProcess(
uint32 session_id,
const FilePath::StringType& application_name,
const CommandLine::StringType& command_line,
DWORD creation_flags,
PROCESS_INFORMATION* process_information_out)
{
DCHECK(base::win::GetVersion() == base::win::VERSION_XP);
......@@ -203,8 +204,9 @@ bool CreateRemoteSessionProcess(
CreateProcessRequest* request =
reinterpret_cast<CreateProcessRequest*>(buffer.get());
request->size = size;
request->use_default_token = TRUE;
request->process_id = GetCurrentProcessId();
request->use_default_token = TRUE;
request->creation_flags = creation_flags;
request->startup_info.cb = sizeof(request->startup_info);
size_t buffer_offset = sizeof(CreateProcessRequest);
......@@ -345,7 +347,9 @@ bool CreateSessionToken(uint32 session_id, ScopedHandle* token_out) {
bool LaunchProcessWithToken(const FilePath& binary,
const CommandLine::StringType& command_line,
HANDLE user_token,
ScopedHandle* process_out) {
DWORD creation_flags,
ScopedHandle* process_out,
ScopedHandle* thread_out) {
FilePath::StringType application_name = binary.value();
base::win::ScopedProcessInformation process_info;
......@@ -363,7 +367,7 @@ bool LaunchProcessWithToken(const FilePath& binary,
NULL,
NULL,
FALSE,
0,
creation_flags,
NULL,
NULL,
&startup_info,
......@@ -389,6 +393,7 @@ bool LaunchProcessWithToken(const FilePath& binary,
result = CreateRemoteSessionProcess(session_id,
application_name,
command_line,
creation_flags,
process_info.Receive());
} else {
// Restore the error status returned by CreateProcessAsUser().
......@@ -405,6 +410,7 @@ bool LaunchProcessWithToken(const FilePath& binary,
CHECK(process_info.IsValid());
process_out->Set(process_info.TakeProcessHandle());
thread_out->Set(process_info.TakeThreadHandle());
return true;
}
......
......@@ -23,7 +23,9 @@ bool CreateSessionToken(uint32 session_id, base::win::ScopedHandle* token_out);
bool LaunchProcessWithToken(const FilePath& binary,
const CommandLine::StringType& command_line,
HANDLE user_token,
base::win::ScopedHandle* process_out);
DWORD creation_flags,
base::win::ScopedHandle* process_out,
base::win::ScopedHandle* thread_out);
} // namespace remoting
......
......@@ -11,6 +11,7 @@
#include "base/compiler_specific.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop.h"
#include "base/process.h"
#include "base/time.h"
#include "base/timer.h"
......@@ -35,7 +36,8 @@ class SasInjector;
class WtsConsoleMonitor;
class WtsSessionProcessLauncher
: public Stoppable,
: public base::MessagePumpForIO::IOHandler,
public Stoppable,
public WorkerProcessLauncher::Delegate,
public WtsConsoleObserver {
public:
......@@ -51,6 +53,11 @@ class WtsSessionProcessLauncher
virtual ~WtsSessionProcessLauncher();
// base::MessagePumpForIO::IOHandler implementation.
virtual void OnIOCompleted(base::MessagePumpForIO::IOContext* context,
DWORD bytes_transferred,
DWORD error) OVERRIDE;
// WorkerProcessLauncher::Delegate implementation.
virtual bool DoLaunchProcess(
const std::string& channel_name,
......@@ -68,6 +75,22 @@ class WtsSessionProcessLauncher
virtual void DoStop() OVERRIDE;
private:
// Drains the completion port queue to make sure that all job object
// notifications has been received.
void DrainJobNotifications();
// Notifies that the completion port queue has been drained.
void DrainJobNotificationsCompleted();
// Creates and initializes the job object that will sandbox the launched child
// processes.
void InitializeJob();
// Notifies that the job object initialization is complete.
void InitializeJobCompleted(scoped_ptr<base::win::ScopedHandle> job);
void OnJobNotification(DWORD message, DWORD pid);
// Attempts to launch the host process in the current console session.
// Schedules next launch attempt if creation of the process fails for any
// reason.
......@@ -103,6 +126,22 @@ class WtsSessionProcessLauncher
scoped_ptr<WorkerProcessLauncher> launcher_;
// The job object used to control the lifetime of child processes.
base::win::ScopedHandle job_;
// A waiting handle that becomes signalled once all process associated with
// the job have been terminated.
base::win::ScopedHandle process_exit_event_;
enum JobState {
kJobUninitialized,
kJobRunning,
kJobStopping,
kJobStopped
};
JobState job_state_;
base::win::ScopedHandle worker_process_;
// The token to be used to launch a process in a different session.
......
......@@ -632,6 +632,8 @@
'AdditionalDependencies': [
'wtsapi32.lib',
],
# 2 == /SUBSYSTEM:WINDOWS
'SubSystem': '2',
},
},
}, # end of target 'remoting_service'
......@@ -1518,6 +1520,7 @@
'msvs_settings': {
'VCLinkerTool': {
'AdditionalOptions': [
"\"/MANIFESTUAC:level='requireAdministrator' uiAccess='true'\"",
"\"/manifestdependency:type='win32' "
"name='Microsoft.Windows.Common-Controls' "
"version='6.0.0.0' "
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment