diff options
author | alexeypa@chromium.org <alexeypa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-10 19:00:06 +0000 |
---|---|---|
committer | alexeypa@chromium.org <alexeypa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-10 19:00:06 +0000 |
commit | 9733976130e9df953c41469be9cbfcbdd4667887 (patch) | |
tree | b37e7ab428f5edb9e14108cbe589f1051ddf465e /remoting | |
parent | 51dc5a56a725a1aa13d8d87d9be27238d644445d (diff) | |
download | chromium_src-9733976130e9df953c41469be9cbfcbdd4667887.zip chromium_src-9733976130e9df953c41469be9cbfcbdd4667887.tar.gz chromium_src-9733976130e9df953c41469be9cbfcbdd4667887.tar.bz2 |
[Chromoting] Reimplement the worker process launcher to address issues with job control and launching of elevated processes.
This CL makes three changes:
1. We create a new job object every time we launch a worker into a new session, under Vista and above. This addresses the limitation that a job object used with processes from one session cannot later be re-used to manage processes from a different session.
2. We launch and manage worker processes directly on XP/W2K3, without a job object. We don't need a job object since these platforms don't support/need elevation anyway. This addresses the limitation that processes created via CreateRemoteSessionProcess() cannot be assigned to a job object.
3. We allow WorkerProcessLauncher instances to be dropped synchronously from the caller's perspective; the class takes care of asynchronous tear-down tasks transparently. This addresses a race condition in tear-down of old worker processes and creation of new ones arising from delayed session change notifications.
The new layout assigns the following responsibilities:
- WorkerProcessLauncher is used to manage a worker process, taking care of setting up the IPC channel, and re-launching it w/ exponential back-off. It supports fire-and-forget tear-down.
- WorkerProcessLauncher defers launch & termination to a caller-supplied Delegate, responsible for launching the process as the caller requires.
- Our current "single-process" service launches the host process via WtsConsoleSessionProcessDriver, which tracks which session is currently attached to the console and uses WtsSessionProcessDelegate instances to have WorkerProcessLauncher re-launch the host into the new console session whenever it is switched. WtsSessionProcessDelegate takes care of OS-version-specific process management tasks.
- Our experimental "multi-process" service launches the host's network process, undependent of session lifetimes, by passing an UnprivilegedProcessDelegate to WorkerProcessLauncher. This launches the host process in the same session as the caller.
BUG=153005, 148781, 149098
TEST=remoting_unittests.WorkerProcessLauncherTest
Review URL: https://chromiumcodereview.appspot.com/11040065
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@161140 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting')
19 files changed, 1615 insertions, 1192 deletions
diff --git a/remoting/host/daemon_process.cc b/remoting/host/daemon_process.cc index 607fca7..720ed4b 100644 --- a/remoting/host/daemon_process.cc +++ b/remoting/host/daemon_process.cc @@ -43,6 +43,12 @@ bool DaemonProcess::OnMessageReceived(const IPC::Message& message) { return false; } +void DaemonProcess::OnPermanentError() { + DCHECK(main_task_runner()->BelongsToCurrentThread()); + + Stop(); +} + DaemonProcess::DaemonProcess( scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, diff --git a/remoting/host/daemon_process.h b/remoting/host/daemon_process.h index a52be9e..b7bcc55 100644 --- a/remoting/host/daemon_process.h +++ b/remoting/host/daemon_process.h @@ -46,6 +46,7 @@ class DaemonProcess // WorkerProcessIpcDelegate implementation. virtual void OnChannelConnected() OVERRIDE; virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + virtual void OnPermanentError() OVERRIDE; // Sends an IPC message to the network process. The message will be dropped // unless the network process is connected over the IPC channel. diff --git a/remoting/host/daemon_process_win.cc b/remoting/host/daemon_process_win.cc index b299ed1..c29ebb6 100644 --- a/remoting/host/daemon_process_win.cc +++ b/remoting/host/daemon_process_win.cc @@ -8,6 +8,8 @@ #include "base/bind.h" #include "base/bind_helpers.h" #include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" #include "base/path_service.h" #include "base/single_thread_task_runner.h" #include "base/time.h" @@ -16,6 +18,7 @@ #include "base/win/scoped_handle.h" #include "remoting/host/host_exit_codes.h" #include "remoting/host/win/launch_process_with_token.h" +#include "remoting/host/win/unprivileged_process_delegate.h" #include "remoting/host/win/worker_process_launcher.h" using base::win::ScopedHandle; @@ -23,22 +26,9 @@ using base::TimeDelta; namespace { -// The minimum and maximum delays between attempts to launch the networking -// process. -const int kMaxLaunchDelaySeconds = 60; -const int kMinLaunchDelaySeconds = 1; - const FilePath::CharType kMe2meHostBinaryName[] = FILE_PATH_LITERAL("remoting_host.exe"); -// The IPC channel name is passed to the networking process in the command line. -const char kDaemonPipeSwitchName[] = "daemon-pipe"; - -// The command line parameters that should be copied from the service's command -// line to the network process. -const char* kCopiedSwitchNames[] = { - "host-config", switches::kV, switches::kVModule }; - // The security descriptor of the daemon IPC endpoint. It gives full access // to LocalSystem and denies access by anyone else. const char kDaemonPipeSecurityDescriptor[] = "O:SYG:SYD:(A;;GA;;;SY)"; @@ -47,26 +37,17 @@ const char kDaemonPipeSecurityDescriptor[] = "O:SYG:SYD:(A;;GA;;;SY)"; namespace remoting { -class DaemonProcessWin : public DaemonProcess, - public WorkerProcessLauncher::Delegate { +class DaemonProcessWin : public DaemonProcess { public: DaemonProcessWin(scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, const base::Closure& stopped_callback); virtual ~DaemonProcessWin(); - virtual void OnChannelConnected() OVERRIDE; - // Sends an IPC message to the worker process. This method can be called only // after successful Start() and until Stop() is called or an error occurred. virtual void Send(IPC::Message* message) OVERRIDE; - // WorkerProcessLauncher::Delegate implementation. - virtual bool DoLaunchProcess( - const std::string& channel_name, - ScopedHandle* process_exit_event_out) OVERRIDE; - virtual void DoKillProcess(DWORD exit_code) OVERRIDE; - protected: // Stoppable implementation. virtual void DoStop() OVERRIDE; @@ -75,25 +56,8 @@ class DaemonProcessWin : public DaemonProcess, virtual void LaunchNetworkProcess() OVERRIDE; private: - // Called when the launcher reports the worker process has stopped. - void OnLauncherStopped(); - - // True if the network process is connected to the daemon. - bool connected_; - - // Time of the last launch attempt. - base::Time launch_time_; - - // Current backoff delay. - base::TimeDelta launch_backoff_; - - // Timer used to schedule the next attempt to launch the process. - base::OneShotTimer<DaemonProcessWin> timer_; - scoped_ptr<WorkerProcessLauncher> launcher_; - ScopedHandle network_process_; - DISALLOW_COPY_AND_ASSIGN(DaemonProcessWin); }; @@ -101,8 +65,7 @@ DaemonProcessWin::DaemonProcessWin( scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, const base::Closure& stopped_callback) - : DaemonProcess(main_task_runner, io_task_runner, stopped_callback), - connected_(false) { + : DaemonProcess(main_task_runner, io_task_runner, stopped_callback) { } DaemonProcessWin::~DaemonProcessWin() { @@ -115,162 +78,38 @@ DaemonProcessWin::~DaemonProcessWin() { void DaemonProcessWin::LaunchNetworkProcess() { DCHECK(main_task_runner()->BelongsToCurrentThread()); DCHECK(launcher_.get() == NULL); - DCHECK(!network_process_.IsValid()); - DCHECK(!timer_.IsRunning()); - - launch_time_ = base::Time::Now(); - launcher_.reset(new WorkerProcessLauncher( - this, this, - base::Bind(&DaemonProcessWin::OnLauncherStopped, base::Unretained(this)), - main_task_runner(), - io_task_runner())); - launcher_->Start(kDaemonPipeSecurityDescriptor); -} - -void DaemonProcessWin::OnChannelConnected() { - connected_ = true; - DaemonProcess::OnChannelConnected(); -} - -void DaemonProcessWin::Send(IPC::Message* message) { - if (connected_) { - launcher_->Send(message); - } else { - delete message; - } -} - -bool DaemonProcessWin::DoLaunchProcess( - const std::string& channel_name, - ScopedHandle* process_exit_event_out) { - DCHECK(main_task_runner()->BelongsToCurrentThread()); - DCHECK(!network_process_.IsValid()); // Construct the host binary name. FilePath dir_path; if (!PathService::Get(base::DIR_EXE, &dir_path)) { LOG(ERROR) << "Failed to get the executable file name."; - return false; - } - FilePath host_binary = dir_path.Append(kMe2meHostBinaryName); - - // Create the host process command line, passing the name of the IPC channel - // to use and copying known switches from the service's command line. - CommandLine command_line(host_binary); - command_line.AppendSwitchNative(kDaemonPipeSwitchName, - UTF8ToWide(channel_name)); - command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(), - kCopiedSwitchNames, - _countof(kCopiedSwitchNames)); - - ScopedHandle token; - if (!OpenProcessToken(GetCurrentProcess(), - MAXIMUM_ALLOWED, - token.Receive())) { - LOG_GETLASTERROR(FATAL) << "Failed to open process token"; - return false; - } - - // Try to launch the process and attach an object watcher to the returned - // handle so that we get notified when the process terminates. - // TODO(alexeypa): Pass a restricted process token. - // See http://crbug.com/134694. - ScopedHandle worker_thread; - if (!LaunchProcessWithToken(host_binary, - command_line.GetCommandLineString(), - token, - 0, - &network_process_, - &worker_thread)) { - return false; - } - - ScopedHandle process_exit_event; - if (!DuplicateHandle(GetCurrentProcess(), - network_process_, - GetCurrentProcess(), - process_exit_event.Receive(), - SYNCHRONIZE, - FALSE, - 0)) { - LOG_GETLASTERROR(ERROR) << "Failed to duplicate a handle"; - DoKillProcess(CONTROL_C_EXIT); - return false; + Stop(); + return; } - *process_exit_event_out = process_exit_event.Pass(); - return true; + scoped_ptr<UnprivilegedProcessDelegate> delegate( + new UnprivilegedProcessDelegate(main_task_runner(), io_task_runner(), + dir_path.Append(kMe2meHostBinaryName))); + launcher_.reset(new WorkerProcessLauncher(main_task_runner(), + io_task_runner(), + delegate.Pass(), + this, + kDaemonPipeSecurityDescriptor)); } -void DaemonProcessWin::DoKillProcess(DWORD exit_code) { - DCHECK(main_task_runner()->BelongsToCurrentThread()); - CHECK(network_process_.IsValid()); - - TerminateProcess(network_process_, exit_code); -} - -void DaemonProcessWin::DoStop() { - DCHECK(main_task_runner()->BelongsToCurrentThread()); - - timer_.Stop(); - - if (launcher_.get() != NULL) { - launcher_->Stop(); - } - - // Early exit if we're still waiting for |launcher_| to stop. +void DaemonProcessWin::Send(IPC::Message* message) { if (launcher_.get() != NULL) { - return; + launcher_->Send(message); + } else { + delete message; } - - DaemonProcess::DoStop(); } -void DaemonProcessWin::OnLauncherStopped() { +void DaemonProcessWin::DoStop() { DCHECK(main_task_runner()->BelongsToCurrentThread()); - CHECK(network_process_.IsValid()); - - DWORD exit_code = CONTROL_C_EXIT; - if (!::GetExitCodeProcess(network_process_, &exit_code)) { - LOG_GETLASTERROR(INFO) - << "Failed to query the exit code of the worker process"; - exit_code = CONTROL_C_EXIT; - } - - network_process_.Close(); - connected_ = false; - launcher_.reset(NULL); - - // Do not relaunch the network process if the caller has asked us to stop. - if (stoppable_state() != Stoppable::kRunning) { - Stop(); - return; - } - // Stop trying to restart the worker process if its process exited due to - // misconfiguration. - if (kMinPermanentErrorExitCode <= exit_code && - exit_code <= kMaxPermanentErrorExitCode) { - Stop(); - return; - } - - // Expand the backoff interval if the process has died quickly or reset it - // if it was up longer than the maximum backoff delay. - base::TimeDelta delta = base::Time::Now() - launch_time_; - if (delta < base::TimeDelta() || - delta >= base::TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)) { - launch_backoff_ = base::TimeDelta(); - } else { - launch_backoff_ = std::max( - launch_backoff_ * 2, TimeDelta::FromSeconds(kMinLaunchDelaySeconds)); - launch_backoff_ = std::min( - launch_backoff_, TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); - } - - // Try to launch the worker process. - timer_.Start(FROM_HERE, launch_backoff_, - this, &DaemonProcessWin::LaunchNetworkProcess); + launcher_.reset(); + DaemonProcess::DoStop(); } scoped_ptr<DaemonProcess> DaemonProcess::Create( diff --git a/remoting/host/win/host_service.cc b/remoting/host/win/host_service.cc index b882d52..d8ff0d1 100644 --- a/remoting/host/win/host_service.cc +++ b/remoting/host/win/host_service.cc @@ -39,7 +39,7 @@ #include "remoting/host/win/wts_console_observer.h" #if !defined(REMOTING_MULTI_PROCESS) -#include "remoting/host/win/wts_session_process_launcher.h" +#include "remoting/host/win/wts_console_session_process_driver.h" #endif // !defined(REMOTING_MULTI_PROCESS) using base::StringPrintf; @@ -213,8 +213,8 @@ void HostService::CreateLauncher( #else // !defined(REMOTING_MULTI_PROCESS) - // Create the session process launcher. - child_.reset(new WtsSessionProcessLauncher( + // Create the console session process driver. + child_.reset(new WtsConsoleSessionProcessDriver( base::Bind(&HostService::OnChildStopped, base::Unretained(this)), this, main_task_runner_, @@ -249,7 +249,7 @@ int HostService::Elevate() { CommandLine command_line(CommandLine::NO_PROGRAM); command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(), kCopiedSwitchNames, - _countof(kCopiedSwitchNames)); + arraysize(kCopiedSwitchNames)); CommandLine::StringType parameters = command_line.GetCommandLineString(); // Launch the child process requesting elevation. diff --git a/remoting/host/win/host_service.h b/remoting/host/win/host_service.h index 80bbc90..ce8b479 100644 --- a/remoting/host/win/host_service.h +++ b/remoting/host/win/host_service.h @@ -26,12 +26,6 @@ class AutoThreadTaskRunner; class Stoppable; class WtsConsoleObserver; -#if defined(REMOTING_MULTI_PROCESS) -class DaemonProcess; -#else // !defined(REMOTING_MULTI_PROCESS) -class WtsSessionProcessLauncher; -#endif // !defined(REMOTING_MULTI_PROCESS) - class HostService : public WtsConsoleMonitor { public: static HostService* GetInstance(); diff --git a/remoting/host/win/launch_process_with_token.cc b/remoting/host/win/launch_process_with_token.cc index 191f896..db2f40a 100644 --- a/remoting/host/win/launch_process_with_token.cc +++ b/remoting/host/win/launch_process_with_token.cc @@ -32,11 +32,6 @@ const WINSTATIONINFOCLASS kCreateProcessPipeNameClass = const int kPipeBusyWaitTimeoutMs = 2000; const int kPipeConnectMaxAttempts = 3; -// The minimum and maximum delays between attempts to inject host process into -// a session. -const int kMaxLaunchDelaySeconds = 60; -const int kMinLaunchDelaySeconds = 1; - // Name of the default session desktop. const char kDefaultDesktopName[] = "winsta0\\default"; diff --git a/remoting/host/win/unprivileged_process_delegate.cc b/remoting/host/win/unprivileged_process_delegate.cc new file mode 100644 index 0000000..74d539d --- /dev/null +++ b/remoting/host/win/unprivileged_process_delegate.cc @@ -0,0 +1,117 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// This file implements the Windows service controlling Me2Me host processes +// running within user sessions. + +#include "remoting/host/win/unprivileged_process_delegate.h" + +#include "base/base_switches.h" +#include "base/command_line.h" +#include "base/logging.h" +#include "base/single_thread_task_runner.h" +#include "base/utf_string_conversions.h" +#include "base/win/scoped_handle.h" +#include "remoting/host/win/launch_process_with_token.h" + +using base::win::ScopedHandle; + +namespace { + +// The command line switch specifying the name of the daemon IPC endpoint. +const char kDaemonIpcSwitchName[] = "daemon-pipe"; + +// The command line parameters that should be copied from the service's command +// line to the host process. +const char* kCopiedSwitchNames[] = { + "host-config", switches::kV, switches::kVModule }; + +} // namespace + +namespace remoting { + +UnprivilegedProcessDelegate::UnprivilegedProcessDelegate( + scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, + const FilePath& binary_path) + : main_task_runner_(main_task_runner), + io_task_runner_(io_task_runner), + binary_path_(binary_path) { +} + +UnprivilegedProcessDelegate::~UnprivilegedProcessDelegate() { + KillProcess(CONTROL_C_EXIT); +} + +DWORD UnprivilegedProcessDelegate::GetExitCode() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + DWORD exit_code = CONTROL_C_EXIT; + if (worker_process_.IsValid()) { + if (!::GetExitCodeProcess(worker_process_, &exit_code)) { + LOG_GETLASTERROR(INFO) + << "Failed to query the exit code of the worker process"; + exit_code = CONTROL_C_EXIT; + } + } + + return exit_code; +} + +void UnprivilegedProcessDelegate::KillProcess(DWORD exit_code) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + if (worker_process_.IsValid()) { + TerminateProcess(worker_process_, exit_code); + } +} + +bool UnprivilegedProcessDelegate::LaunchProcess( + const std::string& channel_name, + ScopedHandle* process_exit_event_out) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + // Create the command line passing the name of the IPC channel to use and + // copying known switches from the caller's command line. + CommandLine command_line(binary_path_); + command_line.AppendSwitchNative(kDaemonIpcSwitchName, + UTF8ToWide(channel_name)); + command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(), + kCopiedSwitchNames, + arraysize(kCopiedSwitchNames)); + + // Try to launch the process. + // TODO(alexeypa): Pass a restricted process token. + // See http://crbug.com/134694. + ScopedHandle worker_thread; + worker_process_.Close(); + if (!LaunchProcessWithToken(command_line.GetProgram(), + command_line.GetCommandLineString(), + NULL, + 0, + &worker_process_, + &worker_thread)) { + return false; + } + + // Return a handle that the caller can wait on to get notified when + // the process terminates. + ScopedHandle process_exit_event; + if (!DuplicateHandle(GetCurrentProcess(), + worker_process_, + GetCurrentProcess(), + process_exit_event.Receive(), + SYNCHRONIZE, + FALSE, + 0)) { + LOG_GETLASTERROR(ERROR) << "Failed to duplicate a handle"; + KillProcess(CONTROL_C_EXIT); + return false; + } + + *process_exit_event_out = process_exit_event.Pass(); + return true; +} + +} // namespace remoting diff --git a/remoting/host/win/unprivileged_process_delegate.h b/remoting/host/win/unprivileged_process_delegate.h new file mode 100644 index 0000000..0108371 --- /dev/null +++ b/remoting/host/win/unprivileged_process_delegate.h @@ -0,0 +1,55 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef REMOTING_HOST_WIN_UNPRIVILEGED_PROCESS_DELEGATE_H_ +#define REMOTING_HOST_WIN_UNPRIVILEGED_PROCESS_DELEGATE_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/file_path.h" +#include "base/memory/ref_counted.h" +#include "remoting/host/win/worker_process_launcher.h" + +namespace base { +class SingleThreadTaskRunner; +} // namespace base + +namespace remoting { + +// Implements logic for launching and monitoring a worker process under a less +// privileged user account. +class UnprivilegedProcessDelegate : public WorkerProcessLauncher::Delegate { + public: + UnprivilegedProcessDelegate( + scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, + const FilePath& binary_path); + virtual ~UnprivilegedProcessDelegate(); + + // WorkerProcessLauncher::Delegate implementation. + virtual DWORD GetExitCode() OVERRIDE; + virtual void KillProcess(DWORD exit_code) OVERRIDE; + virtual bool LaunchProcess( + const std::string& channel_name, + base::win::ScopedHandle* process_exit_event_out) OVERRIDE; + + private: + // The task runner all public methods of this class should be called on. + scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; + + // The task runner serving job object notifications. + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; + + // Path to the worker process binary. + FilePath binary_path_; + + // The handle of the worker process, if launched. + base::win::ScopedHandle worker_process_; + + DISALLOW_COPY_AND_ASSIGN(UnprivilegedProcessDelegate); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_WIN_UNPRIVILEGED_PROCESS_DELEGATE_H_ diff --git a/remoting/host/win/worker_process_launcher.cc b/remoting/host/win/worker_process_launcher.cc index 834b8bb..b06b4a3 100644 --- a/remoting/host/win/worker_process_launcher.cc +++ b/remoting/host/win/worker_process_launcher.cc @@ -4,11 +4,9 @@ #include "remoting/host/win/worker_process_launcher.h" -#include <windows.h> #include <sddl.h> #include <limits> -#include "base/base_switches.h" #include "base/bind.h" #include "base/bind_helpers.h" #include "base/logging.h" @@ -17,77 +15,219 @@ #include "base/rand_util.h" #include "base/stringprintf.h" #include "base/time.h" +#include "base/timer.h" #include "base/utf_string_conversions.h" +#include "base/win/object_watcher.h" #include "base/win/scoped_handle.h" +#include "ipc/ipc_channel.h" #include "ipc/ipc_channel_proxy.h" #include "ipc/ipc_message.h" +#include "net/base/backoff_entry.h" +#include "remoting/host/host_exit_codes.h" #include "remoting/host/worker_process_ipc_delegate.h" using base::win::ScopedHandle; - -namespace { +using base::TimeDelta; // Match the pipe name prefix used by Chrome IPC channels so that the client // could use Chrome IPC APIs instead of connecting manually. const char kChromePipeNamePrefix[] = "\\\\.\\pipe\\chrome."; -} // namespace +// The minimum and maximum delays between attempts to inject host process into +// a session. +const int kMaxLaunchDelaySeconds = 60; +const int kMinLaunchDelaySeconds = 1; + +const net::BackoffEntry::Policy kDefaultBackoffPolicy = { + // Number of initial errors (in sequence) to ignore before applying + // exponential back-off rules. + 0, + + // Initial delay for exponential back-off in ms. + 1000, + + // Factor by which the waiting time will be multiplied. + 2, + + // Fuzzing percentage. ex: 10% will spread requests randomly + // between 90%-100% of the calculated time. + 0, + + // Maximum amount of time we are willing to delay our request in ms. + 60000, + + // Time to keep an entry from being discarded even when it + // has no significant state, -1 to never discard. + -1, + + // Don't use initial delay unless the last request was an error. + false, +}; + namespace remoting { +// Launches a worker process that is controlled via an IPC channel. All +// interaction with the spawned process is through the IPC::Listener and Send() +// method. In case of error the channel is closed and the worker process is +// terminated. +class WorkerProcessLauncher::Core + : public base::RefCountedThreadSafe<WorkerProcessLauncher::Core>, + public base::win::ObjectWatcher::Delegate, + public IPC::Listener { + public: + // Creates the launcher that will use |launcher_delegate| to manage the worker + // process and |worker_delegate| to handle IPCs. The caller must ensure that + // |worker_delegate| remains valid until Stoppable::Stop() method has been + // called. + // + // The caller should call all the methods on this class on + // the |caller_task_runner| thread. Methods of both delegate interfaces are + // called on the |caller_task_runner| thread as well. |io_task_runner| is used + // to perform background IPC I/O. + Core(scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, + scoped_ptr<WorkerProcessLauncher::Delegate> launcher_delegate, + WorkerProcessIpcDelegate* worker_delegate, + const std::string& pipe_security_descriptor); + + // Launches the worker process. + void Start(); + + // Stops the worker process asynchronously. The caller can drop the reference + // to |this| as soon as Stop() returns. + void Stop(); + + // Sends an IPC message to the worker process. The message will be silently + // dropped if Send() is called before Start() or after stutdown has been + // initiated. + void Send(IPC::Message* message); + + // base::win::ObjectWatcher::Delegate implementation. + virtual void OnObjectSignaled(HANDLE object) OVERRIDE; + + // IPC::Listener implementation. + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + virtual void OnChannelConnected(int32 peer_pid) OVERRIDE; + virtual void OnChannelError() OVERRIDE; + + private: + friend class base::RefCountedThreadSafe<Core>; + virtual ~Core(); + + // Creates the server end of the Chromoting IPC channel. + bool CreatePipeForIpcChannel(const std::string& channel_name, + const std::string& pipe_security_descriptor, + base::win::ScopedHandle* pipe_out); + + // Generates random channel ID. + std::string GenerateRandomChannelId(); + + // Attempts to launch the worker process. Schedules next launch attempt if + // creation of the process fails. + void LaunchWorker(); + + // Records a successfull launch attempt. + void RecordSuccessfullLaunch(); + + // Stops the worker process asynchronously and schedules next launch attempt + // unless Stop() has been called already. + void StopWorker(); + + // All public methods are called on the |caller_task_runner| thread. + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_; + + // The task runner is used perform background IPC I/O. + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; + + // Implements specifics of launching a worker process. + scoped_ptr<WorkerProcessLauncher::Delegate> launcher_delegate_; + + // Handles IPC messages sent by the worker process. + WorkerProcessIpcDelegate* worker_delegate_; + + // The IPC channel connecting to the launched process. + scoped_ptr<IPC::ChannelProxy> ipc_channel_; + + // The timer used to delay termination of the process in the case of an IPC + // error. + scoped_ptr<base::OneShotTimer<Core> > ipc_error_timer_; + + // Launch backoff state. + net::BackoffEntry launch_backoff_; + + // Timer used to delay recording a successfull launch. + scoped_ptr<base::OneShotTimer<Core> > launch_success_timer_; + + // Timer used to schedule the next attempt to launch the process. + scoped_ptr<base::OneShotTimer<Core> > launch_timer_; + + // Used to determine when the launched process terminates. + base::win::ObjectWatcher process_watcher_; + + // A waiting handle that becomes signalled once the launched process has + // been terminated. + base::win::ScopedHandle process_exit_event_; + + // The server end of the pipe. + base::win::ScopedHandle pipe_; + + // The security descriptor (as SDDL) of the server end of the pipe. + std::string pipe_security_descriptor_; + + // Self reference to keep the object alive while the worker process is being + // terminated. + scoped_refptr<Core> self_; + + // True when Stop() has been called. + bool stopping_; + + DISALLOW_COPY_AND_ASSIGN(Core); +}; + WorkerProcessLauncher::Delegate::~Delegate() { } -WorkerProcessLauncher::WorkerProcessLauncher( - Delegate* launcher_delegate, +WorkerProcessLauncher::Core::Core( + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, + scoped_ptr<WorkerProcessLauncher::Delegate> launcher_delegate, WorkerProcessIpcDelegate* worker_delegate, - const base::Closure& stopped_callback, - scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, - scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner) - : Stoppable(main_task_runner, stopped_callback), - launcher_delegate_(launcher_delegate), + const std::string& pipe_security_descriptor) + : caller_task_runner_(caller_task_runner), + io_task_runner_(io_task_runner), + launcher_delegate_(launcher_delegate.Pass()), worker_delegate_(worker_delegate), - main_task_runner_(main_task_runner), - ipc_task_runner_(ipc_task_runner) { + launch_backoff_(&kDefaultBackoffPolicy), + pipe_security_descriptor_(pipe_security_descriptor), + stopping_(false) { + DCHECK(caller_task_runner_->BelongsToCurrentThread()); + + // base::OneShotTimer must be destroyed on the same thread it was created on. + ipc_error_timer_.reset(new base::OneShotTimer<Core>()); + launch_success_timer_.reset(new base::OneShotTimer<Core>()); + launch_timer_.reset(new base::OneShotTimer<Core>()); } -WorkerProcessLauncher::~WorkerProcessLauncher() { - DCHECK(main_task_runner_->BelongsToCurrentThread()); -} +void WorkerProcessLauncher::Core::Start() { + DCHECK(caller_task_runner_->BelongsToCurrentThread()); + DCHECK(!stopping_); -void WorkerProcessLauncher::Start(const std::string& pipe_sddl) { - DCHECK(main_task_runner_->BelongsToCurrentThread()); - DCHECK(ipc_channel_.get() == NULL); - DCHECK(!pipe_.IsValid()); - DCHECK(!process_exit_event_.IsValid()); - DCHECK(process_watcher_.GetWatchedObject() == NULL); + LaunchWorker(); +} - std::string channel_name = GenerateRandomChannelId(); - if (CreatePipeForIpcChannel(channel_name, pipe_sddl, &pipe_)) { - // Wrap the pipe into an IPC channel. - ipc_channel_.reset(new IPC::ChannelProxy( - IPC::ChannelHandle(pipe_), - IPC::Channel::MODE_SERVER, - this, - ipc_task_runner_)); +void WorkerProcessLauncher::Core::Stop() { + DCHECK(caller_task_runner_->BelongsToCurrentThread()); - // Launch the process and attach an object watcher to the returned process - // handle so that we get notified if the process terminates. - if (launcher_delegate_->DoLaunchProcess(channel_name, - &process_exit_event_)) { - if (process_watcher_.StartWatching(process_exit_event_, this)) { - return; - } - - launcher_delegate_->DoKillProcess(CONTROL_C_EXIT); - } + if (!stopping_) { + stopping_ = true; + worker_delegate_ = NULL; + StopWorker(); } - - Stop(); } -void WorkerProcessLauncher::Send(IPC::Message* message) { - DCHECK(main_task_runner_->BelongsToCurrentThread()); +void WorkerProcessLauncher::Core::Send(IPC::Message* message) { + DCHECK(caller_task_runner_->BelongsToCurrentThread()); if (ipc_channel_.get()) { ipc_channel_->Send(message); @@ -96,27 +236,24 @@ void WorkerProcessLauncher::Send(IPC::Message* message) { } } -void WorkerProcessLauncher::OnObjectSignaled(HANDLE object) { - DCHECK(main_task_runner_->BelongsToCurrentThread()); +void WorkerProcessLauncher::Core::OnObjectSignaled(HANDLE object) { + DCHECK(caller_task_runner_->BelongsToCurrentThread()); DCHECK(process_watcher_.GetWatchedObject() == NULL); - Stop(); + StopWorker(); } -bool WorkerProcessLauncher::OnMessageReceived(const IPC::Message& message) { - DCHECK(main_task_runner_->BelongsToCurrentThread()); +bool WorkerProcessLauncher::Core::OnMessageReceived( + const IPC::Message& message) { + DCHECK(caller_task_runner_->BelongsToCurrentThread()); DCHECK(ipc_channel_.get() != NULL); - DCHECK(pipe_.IsValid()); - DCHECK(process_exit_event_.IsValid()); return worker_delegate_->OnMessageReceived(message); } -void WorkerProcessLauncher::OnChannelConnected(int32 peer_pid) { - DCHECK(main_task_runner_->BelongsToCurrentThread()); +void WorkerProcessLauncher::Core::OnChannelConnected(int32 peer_pid) { + DCHECK(caller_task_runner_->BelongsToCurrentThread()); DCHECK(ipc_channel_.get() != NULL); - DCHECK(pipe_.IsValid()); - DCHECK(process_exit_event_.IsValid()); // |peer_pid| is send by the client and cannot be trusted. // GetNamedPipeClientProcessId() is not available on XP. The pipe's security @@ -129,47 +266,27 @@ void WorkerProcessLauncher::OnChannelConnected(int32 peer_pid) { worker_delegate_->OnChannelConnected(); } -void WorkerProcessLauncher::OnChannelError() { - DCHECK(main_task_runner_->BelongsToCurrentThread()); +void WorkerProcessLauncher::Core::OnChannelError() { + DCHECK(caller_task_runner_->BelongsToCurrentThread()); DCHECK(ipc_channel_.get() != NULL); - DCHECK(pipe_.IsValid()); - DCHECK(process_exit_event_.IsValid()); // Schedule a delayed termination of the worker process. Usually, the pipe is // disconnected when the worker process is about to exit. Waiting a little bit // here allows the worker to exit completely and so, notify - // |process_watcher_|. As the result DoKillProcess() will not be called and + // |process_watcher_|. As the result KillProcess() will not be called and // the original exit code reported by the worker process will be retrieved. - ipc_error_timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(5), - this, &WorkerProcessLauncher::Stop); + ipc_error_timer_->Start(FROM_HERE, base::TimeDelta::FromSeconds(5), + this, &Core::StopWorker); } -void WorkerProcessLauncher::DoStop() { - DCHECK(main_task_runner_->BelongsToCurrentThread()); - - ipc_channel_.reset(); - pipe_.Close(); - - // Kill the process if it has been started already. - if (process_watcher_.GetWatchedObject() != NULL) { - launcher_delegate_->DoKillProcess(CONTROL_C_EXIT); - return; - } - - ipc_error_timer_.Stop(); - - DCHECK(ipc_channel_.get() == NULL); - DCHECK(!pipe_.IsValid()); - DCHECK(process_watcher_.GetWatchedObject() == NULL); - - process_exit_event_.Close(); - CompleteStopping(); +WorkerProcessLauncher::Core::~Core() { + DCHECK(stopping_); } // Creates the server end of the Chromoting IPC channel. -bool WorkerProcessLauncher::CreatePipeForIpcChannel( +bool WorkerProcessLauncher::Core::CreatePipeForIpcChannel( const std::string& channel_name, - const std::string& pipe_sddl, + const std::string& pipe_security_descriptor, ScopedHandle* pipe_out) { // Create security descriptor for the channel. SECURITY_ATTRIBUTES security_attributes; @@ -178,7 +295,7 @@ bool WorkerProcessLauncher::CreatePipeForIpcChannel( ULONG security_descriptor_length = 0; if (!ConvertStringSecurityDescriptorToSecurityDescriptor( - UTF8ToUTF16(pipe_sddl).c_str(), + UTF8ToUTF16(pipe_security_descriptor).c_str(), SDDL_REVISION_1, reinterpret_cast<PSECURITY_DESCRIPTOR*>( &security_attributes.lpSecurityDescriptor), @@ -218,10 +335,130 @@ bool WorkerProcessLauncher::CreatePipeForIpcChannel( } // N.B. Copied from src/content/common/child_process_host_impl.cc -std::string WorkerProcessLauncher::GenerateRandomChannelId() { +std::string WorkerProcessLauncher::Core::GenerateRandomChannelId() { return base::StringPrintf("%d.%p.%d", base::GetCurrentProcId(), this, base::RandInt(0, std::numeric_limits<int>::max())); } +void WorkerProcessLauncher::Core::LaunchWorker() { + DCHECK(caller_task_runner_->BelongsToCurrentThread()); + DCHECK(ipc_channel_.get() == NULL); + DCHECK(!launch_success_timer_->IsRunning()); + DCHECK(!launch_timer_->IsRunning()); + DCHECK(!pipe_.IsValid()); + DCHECK(!process_exit_event_.IsValid()); + DCHECK(process_watcher_.GetWatchedObject() == NULL); + + std::string channel_name = GenerateRandomChannelId(); + if (CreatePipeForIpcChannel(channel_name, pipe_security_descriptor_, + &pipe_)) { + // Wrap the pipe into an IPC channel. + ipc_channel_.reset(new IPC::ChannelProxy( + IPC::ChannelHandle(pipe_), + IPC::Channel::MODE_SERVER, + this, + io_task_runner_)); + + // Launch the process and attach an object watcher to the returned process + // handle so that we get notified if the process terminates. + if (launcher_delegate_->LaunchProcess(channel_name, &process_exit_event_)) { + if (process_watcher_.StartWatching(process_exit_event_, this)) { + // Record a successful launch once the process has been running for at + // least two seconds. + launch_success_timer_->Start(FROM_HERE, base::TimeDelta::FromSeconds(2), + this, &Core::RecordSuccessfullLaunch); + return; + } + + launcher_delegate_->KillProcess(CONTROL_C_EXIT); + } + } + + launch_backoff_.InformOfRequest(false); + StopWorker(); +} + +void WorkerProcessLauncher::Core::RecordSuccessfullLaunch() { + DCHECK(caller_task_runner_->BelongsToCurrentThread()); + + launch_backoff_.InformOfRequest(true); +} + +void WorkerProcessLauncher::Core::StopWorker() { + DCHECK(caller_task_runner_->BelongsToCurrentThread()); + + // Record a launch failure if the process exited too soon. + if (launch_success_timer_->IsRunning()) { + launch_success_timer_->Stop(); + launch_backoff_.InformOfRequest(false); + } + + // Keep |this| alive until the worker process is terminated. + self_ = this; + + ipc_channel_.reset(); + pipe_.Close(); + + // Kill the process if it has been started already. + if (process_watcher_.GetWatchedObject() != NULL) { + launcher_delegate_->KillProcess(CONTROL_C_EXIT); + return; + } + + ipc_error_timer_->Stop(); + + DCHECK(ipc_channel_.get() == NULL); + DCHECK(!pipe_.IsValid()); + DCHECK(process_watcher_.GetWatchedObject() == NULL); + + process_exit_event_.Close(); + + // Do not relaunch the worker process if the caller has asked us to stop. + if (stopping_) { + ipc_error_timer_.reset(); + launch_timer_.reset(); + self_ = NULL; + return; + } + + self_ = NULL; + + // Stop trying to restart the worker process if it exited due to + // misconfiguration. + DWORD exit_code = launcher_delegate_->GetExitCode(); + if (kMinPermanentErrorExitCode <= exit_code && + exit_code <= kMaxPermanentErrorExitCode) { + // |delegate_| must be valid because Stop() hasn't been called yet and + // |running_| is true. |worker_delegate_| is valid here because Stop() + // hasn't been called yet (|stopping_| is false). + worker_delegate_->OnPermanentError(); + return; + } + + // Schedule the next attempt to launch the worker process. + launch_timer_->Start(FROM_HERE, launch_backoff_.GetTimeUntilRelease(), + this, &Core::LaunchWorker); +} + +WorkerProcessLauncher::WorkerProcessLauncher( + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, + scoped_ptr<Delegate> launcher_delegate, + WorkerProcessIpcDelegate* worker_delegate, + const std::string& pipe_security_descriptor) { + core_ = new Core(caller_task_runner, io_task_runner, launcher_delegate.Pass(), + worker_delegate, pipe_security_descriptor); + core_->Start(); +} + +WorkerProcessLauncher::~WorkerProcessLauncher() { + core_->Stop(); + core_ = NULL; +} + +void WorkerProcessLauncher::Send(IPC::Message* message) { + core_->Send(message); +} + } // namespace remoting diff --git a/remoting/host/win/worker_process_launcher.h b/remoting/host/win/worker_process_launcher.h index d53f860..fb05910 100644 --- a/remoting/host/win/worker_process_launcher.h +++ b/remoting/host/win/worker_process_launcher.h @@ -5,25 +5,17 @@ #ifndef REMOTING_HOST_WIN_WORKER_PROCESS_LAUNCHER_H_ #define REMOTING_HOST_WIN_WORKER_PROCESS_LAUNCHER_H_ -#include <windows.h> - #include "base/basictypes.h" #include "base/compiler_specific.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" -#include "base/process.h" -#include "base/timer.h" #include "base/win/scoped_handle.h" -#include "base/win/object_watcher.h" -#include "ipc/ipc_channel.h" -#include "remoting/base/stoppable.h" namespace base { class SingleThreadTaskRunner; } // namespace base namespace IPC { -class ChannelProxy; class Message; } // namespace IPC @@ -32,100 +24,55 @@ namespace remoting { class WorkerProcessIpcDelegate; // Launches a worker process that is controlled via an IPC channel. All -// interaction with the spawned process is through the IPC::Listener and Send() -// method. In case of error the channel is closed and the worker process is -// terminated. -// -// WorkerProcessLauncher object is good for one process launch attempt only. -class WorkerProcessLauncher - : public Stoppable, - public base::win::ObjectWatcher::Delegate, - public IPC::Listener { +// interaction with the spawned process is through WorkerProcessIpcDelegate and +// Send() method. In case of error the channel is closed and the worker process +// is terminated. +class WorkerProcessLauncher { public: class Delegate { public: virtual ~Delegate(); + // Returns the exit code of the worker process. + virtual DWORD GetExitCode() = 0; + + // Terminates the worker process with the given exit code. + virtual void KillProcess(DWORD exit_code) = 0; + // Starts the worker process and passes |channel_name| to it. // |process_exit_event_out| receives a handle that becomes signalled once // the launched process has been terminated. - virtual bool DoLaunchProcess( + virtual bool LaunchProcess( const std::string& channel_name, base::win::ScopedHandle* process_exit_event_out) = 0; - - // Terminates the worker process with the given exit code. - virtual void DoKillProcess(DWORD exit_code) = 0; }; // Creates the launcher that will use |launcher_delegate| to manage the worker - // process and |worker_delegate| to handle IPCs. + // process and |worker_delegate| to handle IPCs. The caller must ensure that + // |worker_delegate| remains valid until Stoppable::Stop() method has been + // called. // - // |stopped_callback| and |main_task_runner| are passed to the underlying - // |Stoppable| implementation. The caller should call all the methods on this - // class on the |main_task_runner| thread. |ipc_task_runner| is used to - // perform background IPC I/O. + // The caller should call all the methods on this class on + // the |caller_task_runner| thread. Methods of both delegate interfaces are + // called on the |caller_task_runner| thread as well. |io_task_runner| is used + // to perform background IPC I/O. WorkerProcessLauncher( - Delegate* launcher_delegate, + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, + scoped_ptr<Delegate> launcher_delegate, WorkerProcessIpcDelegate* worker_delegate, - const base::Closure& stopped_callback, - scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, - scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner); - virtual ~WorkerProcessLauncher(); - - // Starts the worker process. - void Start(const std::string& pipe_sddl); + const std::string& pipe_security_descriptor); + ~WorkerProcessLauncher(); // Sends an IPC message to the worker process. The message will be silently // dropped if Send() is called before Start() or after stutdown has been // initiated. void Send(IPC::Message* message); - // base::win::ObjectWatcher::Delegate implementation. - virtual void OnObjectSignaled(HANDLE object) OVERRIDE; - - // IPC::Listener implementation. - virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; - virtual void OnChannelConnected(int32 peer_pid) OVERRIDE; - virtual void OnChannelError() OVERRIDE; - - protected: - // Stoppable implementation. - virtual void DoStop() OVERRIDE; - private: - // Creates the server end of the Chromoting IPC channel. - bool CreatePipeForIpcChannel(const std::string& channel_name, - const std::string& pipe_sddl, - base::win::ScopedHandle* pipe_out); - - // Generates random channel ID. - std::string GenerateRandomChannelId(); - - Delegate* launcher_delegate_; - WorkerProcessIpcDelegate* worker_delegate_; - - // The main service message loop. - scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; - - // Message loop used by the IPC channel. - scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner_; - - // Used to determine when the launched process terminates. - base::win::ObjectWatcher process_watcher_; - - // A waiting handle that becomes signalled once the launched process has - // been terminated. - base::win::ScopedHandle process_exit_event_; - - // The IPC channel connecting to the launched process. - scoped_ptr<IPC::ChannelProxy> ipc_channel_; - - // The timer used to delay termination of the process in the case of an IPC - // error. - base::OneShotTimer<WorkerProcessLauncher> ipc_error_timer_; - - // The server end of the pipe. - base::win::ScopedHandle pipe_; + // The actual implementation resides in WorkerProcessLauncher::Core class. + class Core; + scoped_refptr<Core> core_; DISALLOW_COPY_AND_ASSIGN(WorkerProcessLauncher); }; diff --git a/remoting/host/win/worker_process_launcher_unittest.cc b/remoting/host/win/worker_process_launcher_unittest.cc new file mode 100644 index 0000000..7c60eee --- /dev/null +++ b/remoting/host/win/worker_process_launcher_unittest.cc @@ -0,0 +1,335 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop.h" +#include "base/win/scoped_handle.h" +#include "ipc/ipc_channel.h" +#include "ipc/ipc_channel_proxy.h" +#include "ipc/ipc_listener.h" +#include "ipc/ipc_message.h" +#include "remoting/base/auto_thread_task_runner.h" +#include "remoting/host/host_exit_codes.h" +#include "remoting/host/win/worker_process_launcher.h" +#include "remoting/host/worker_process_ipc_delegate.h" +#include "testing/gmock_mutant.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::win::ScopedHandle; +using testing::_; +using testing::AnyNumber; +using testing::CreateFunctor; +using testing::DoAll; +using testing::Expectation; +using testing::Invoke; +using testing::Return; +using testing::ReturnPointee; + +namespace remoting { + +namespace { + +const char kIpcSecurityDescriptor[] = "D:(A;;GA;;;AU)"; + +class MockProcessLauncherDelegate + : public WorkerProcessLauncher::Delegate { + public: + MockProcessLauncherDelegate() {} + virtual ~MockProcessLauncherDelegate() {} + + // WorkerProcessLauncher::Delegate implementation + MOCK_METHOD0(GetExitCode, DWORD()); + MOCK_METHOD1(KillProcess, void(DWORD)); + MOCK_METHOD2(LaunchProcess, bool(const std::string&, ScopedHandle*)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockProcessLauncherDelegate); +}; + +class MockIpcDelegate + : public WorkerProcessIpcDelegate { + public: + MockIpcDelegate() {} + virtual ~MockIpcDelegate() {} + + // WorkerProcessIpcDelegate implementation + MOCK_METHOD0(OnChannelConnected, void()); + MOCK_METHOD1(OnMessageReceived, bool(const IPC::Message&)); + MOCK_METHOD0(OnPermanentError, void()); + + private: + DISALLOW_COPY_AND_ASSIGN(MockIpcDelegate); +}; + +} // namespace + +class WorkerProcessLauncherTest + : public testing::Test, + public IPC::Listener { + public: + WorkerProcessLauncherTest(); + virtual ~WorkerProcessLauncherTest(); + + virtual void SetUp() OVERRIDE; + virtual void TearDown() OVERRIDE; + + // IPC::Listener implementation + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + + // WorkerProcessLauncher::Delegate mocks + void KillProcess(DWORD exit_code); + bool LaunchProcess(const std::string& channel_name, + ScopedHandle* process_exit_event_out); + bool LaunchProcessAndConnect(const std::string& channel_name, + ScopedHandle* process_exit_event_out); + + void ConnectTo(const std::string& channel_name); + void Disconnect(); + + // Starts the worker. + void StartWorker(); + + // Stops the worker. + void StopWorker(); + + // Quits |message_loop_|. + void QuitMainMessageLoop(); + + protected: + MessageLoop message_loop_; + scoped_refptr<AutoThreadTaskRunner> task_runner_; + + // Exit code of the worker process. + DWORD exit_code_; + + MockIpcDelegate ipc_delegate_; + scoped_ptr<MockProcessLauncherDelegate> launcher_delegate_; + + // Client end of the IPC channel. + scoped_ptr<IPC::ChannelProxy> ipc_channel_; + + // The worker process launcher. + scoped_ptr<WorkerProcessLauncher> launcher_; + + // The event signalling termination of the worker process. + ScopedHandle process_exit_event_; +}; + + +WorkerProcessLauncherTest::WorkerProcessLauncherTest() + : message_loop_(MessageLoop::TYPE_IO) { +} + +WorkerProcessLauncherTest::~WorkerProcessLauncherTest() { +} + +void WorkerProcessLauncherTest::SetUp() { + task_runner_ = new AutoThreadTaskRunner( + message_loop_.message_loop_proxy(), + base::Bind(&WorkerProcessLauncherTest::QuitMainMessageLoop, + base::Unretained(this))); + + exit_code_ = STILL_ACTIVE; + + // Set up process launcher delegate + launcher_delegate_.reset(new MockProcessLauncherDelegate()); + EXPECT_CALL(*launcher_delegate_, GetExitCode()) + .Times(AnyNumber()) + .WillRepeatedly(ReturnPointee(&exit_code_)); + EXPECT_CALL(*launcher_delegate_, KillProcess(_)) + .Times(AnyNumber()) + .WillRepeatedly(Invoke(this, &WorkerProcessLauncherTest::KillProcess)); + + // Set up IPC delegate. + EXPECT_CALL(ipc_delegate_, OnMessageReceived(_)) + .Times(AnyNumber()) + .WillRepeatedly(Return(false)); +} + +void WorkerProcessLauncherTest::TearDown() { +} + +bool WorkerProcessLauncherTest::OnMessageReceived(const IPC::Message& message) { + return false; +} + +void WorkerProcessLauncherTest::KillProcess(DWORD exit_code) { + exit_code_ = exit_code; + BOOL result = SetEvent(process_exit_event_); + EXPECT_TRUE(result); +} + +bool WorkerProcessLauncherTest::LaunchProcess( + const std::string& channel_name, + ScopedHandle* process_exit_event_out) { + return LaunchProcessAndConnect("", process_exit_event_out); +} + +bool WorkerProcessLauncherTest::LaunchProcessAndConnect( + const std::string& channel_name, + ScopedHandle* process_exit_event_out) { + process_exit_event_.Set(CreateEvent(NULL, TRUE, FALSE, NULL)); + if (!process_exit_event_.IsValid()) + return false; + + if (!channel_name.empty()) { + task_runner_->PostTask( + FROM_HERE, + base::Bind(&WorkerProcessLauncherTest::ConnectTo, + base::Unretained(this), + channel_name)); + } + + exit_code_ = STILL_ACTIVE; + return DuplicateHandle(GetCurrentProcess(), + process_exit_event_, + GetCurrentProcess(), + process_exit_event_out->Receive(), + 0, + FALSE, + DUPLICATE_SAME_ACCESS) != FALSE; +} + +void WorkerProcessLauncherTest::ConnectTo(const std::string& channel_name) { + ipc_channel_.reset(new IPC::ChannelProxy( + IPC::ChannelHandle(channel_name), + IPC::Channel::MODE_CLIENT, + this, + task_runner_)); +} + +void WorkerProcessLauncherTest::Disconnect() { + ipc_channel_.reset(); +} + +void WorkerProcessLauncherTest::StartWorker() { + launcher_.reset(new WorkerProcessLauncher(task_runner_, + task_runner_, + launcher_delegate_.Pass(), + &ipc_delegate_, + kIpcSecurityDescriptor)); +} + +void WorkerProcessLauncherTest::StopWorker() { + launcher_.reset(); + Disconnect(); + task_runner_ = NULL; +} + +void WorkerProcessLauncherTest::QuitMainMessageLoop() { + message_loop_.PostTask(FROM_HERE, MessageLoop::QuitClosure()); +} + +TEST_F(WorkerProcessLauncherTest, Start) { + EXPECT_CALL(*launcher_delegate_, LaunchProcess(_, _)) + .Times(1) + .WillRepeatedly(Invoke(this, &WorkerProcessLauncherTest::LaunchProcess)); + + EXPECT_CALL(ipc_delegate_, OnChannelConnected()) + .Times(0); + EXPECT_CALL(ipc_delegate_, OnPermanentError()) + .Times(0); + + StartWorker(); + StopWorker(); + message_loop_.Run(); + + EXPECT_EQ(exit_code_, CONTROL_C_EXIT); +} + +// Starts and connects to the worker process. Expect OnChannelConnected to be +// called. +TEST_F(WorkerProcessLauncherTest, StartAndConnect) { + EXPECT_CALL(*launcher_delegate_, LaunchProcess(_, _)) + .Times(1) + .WillRepeatedly(Invoke( + this, &WorkerProcessLauncherTest::LaunchProcessAndConnect)); + + EXPECT_CALL(ipc_delegate_, OnChannelConnected()) + .Times(1) + .WillOnce(Invoke(this, &WorkerProcessLauncherTest::StopWorker)); + EXPECT_CALL(ipc_delegate_, OnPermanentError()) + .Times(0); + + StartWorker(); + message_loop_.Run(); + + EXPECT_EQ(exit_code_, CONTROL_C_EXIT); +} + +// Kills the worker process after the 1st connect and expects it to be +// restarted. +TEST_F(WorkerProcessLauncherTest, Restart) { + EXPECT_CALL(*launcher_delegate_, LaunchProcess(_, _)) + .Times(2) + .WillRepeatedly(Invoke( + this, &WorkerProcessLauncherTest::LaunchProcessAndConnect)); + Expectation first_connect = + EXPECT_CALL(ipc_delegate_, OnChannelConnected()) + .Times(2) + .WillOnce(Invoke(CreateFunctor( + this, &WorkerProcessLauncherTest::KillProcess, CONTROL_C_EXIT))) + .WillOnce(Invoke(this, &WorkerProcessLauncherTest::StopWorker)); + + EXPECT_CALL(ipc_delegate_, OnPermanentError()) + .Times(0); + + StartWorker(); + message_loop_.Run(); + + EXPECT_EQ(exit_code_, CONTROL_C_EXIT); +} + +// Drops the IPC channel to the worker process after the 1st connect and expects +// the worker process to be restarted. +TEST_F(WorkerProcessLauncherTest, DropIpcChannel) { + EXPECT_CALL(*launcher_delegate_, LaunchProcess(_, _)) + .Times(2) + .WillRepeatedly(Invoke( + this, &WorkerProcessLauncherTest::LaunchProcessAndConnect)); + + Expectation first_connect = + EXPECT_CALL(ipc_delegate_, OnChannelConnected()) + .Times(2) + .WillOnce(Invoke(this, &WorkerProcessLauncherTest::Disconnect)) + .WillOnce(Invoke(this, &WorkerProcessLauncherTest::StopWorker)); + + EXPECT_CALL(ipc_delegate_, OnPermanentError()) + .Times(0); + + StartWorker(); + message_loop_.Run(); + + EXPECT_EQ(exit_code_, CONTROL_C_EXIT); +} + +// Returns a permanent error exit code and expects OnPermanentError() to be +// invoked. +TEST_F(WorkerProcessLauncherTest, PermanentError) { + EXPECT_CALL(*launcher_delegate_, LaunchProcess(_, _)) + .Times(1) + .WillRepeatedly(Invoke( + this, &WorkerProcessLauncherTest::LaunchProcessAndConnect)); + + base::Closure terminate_permanently = + base::Bind(&WorkerProcessLauncherTest::KillProcess, + base::Unretained(this), + kInvalidHostConfigurationExitCode); + EXPECT_CALL(ipc_delegate_, OnChannelConnected()) + .Times(1) + .WillRepeatedly(Invoke(&terminate_permanently, &base::Closure::Run)); + + EXPECT_CALL(ipc_delegate_, OnPermanentError()) + .Times(1) + .WillOnce(Invoke(this, &WorkerProcessLauncherTest::StopWorker)); + + StartWorker(); + message_loop_.Run(); + + EXPECT_EQ(exit_code_, kInvalidHostConfigurationExitCode); +} + +} // namespace remoting diff --git a/remoting/host/win/wts_console_session_process_driver.cc b/remoting/host/win/wts_console_session_process_driver.cc new file mode 100644 index 0000000..5bd7cd5 --- /dev/null +++ b/remoting/host/win/wts_console_session_process_driver.cc @@ -0,0 +1,125 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "remoting/host/win/wts_console_session_process_driver.h" + +#include <sddl.h> +#include <limits> + +#include "base/base_switches.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/file_path.h" +#include "base/logging.h" +#include "base/single_thread_task_runner.h" +#include "base/path_service.h" +#include "ipc/ipc_message.h" +#include "remoting/host/win/worker_process_launcher.h" +#include "remoting/host/win/wts_console_monitor.h" +#include "remoting/host/win/wts_session_process_delegate.h" + +namespace { + +const FilePath::CharType kMe2meHostBinaryName[] = + FILE_PATH_LITERAL("remoting_host.exe"); + +// The security descriptor of the daemon IPC endpoint. It gives full access +// to LocalSystem and denies access by anyone else. +const char kDaemonIpcSecurityDescriptor[] = "O:SYG:SYD:(A;;GA;;;SY)"; + +} // namespace + +namespace remoting { + +WtsConsoleSessionProcessDriver::WtsConsoleSessionProcessDriver( + const base::Closure& stopped_callback, + WtsConsoleMonitor* monitor, + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner) + : Stoppable(caller_task_runner, stopped_callback), + caller_task_runner_(caller_task_runner), + io_task_runner_(io_task_runner), + monitor_(monitor) { + monitor_->AddWtsConsoleObserver(this); +} + +WtsConsoleSessionProcessDriver::~WtsConsoleSessionProcessDriver() { + DCHECK(caller_task_runner_->BelongsToCurrentThread()); + + // Make sure that the object is completely stopped. The same check exists + // in Stoppable::~Stoppable() but this one helps us to fail early and + // predictably. + CHECK_EQ(stoppable_state(), Stoppable::kStopped); + + monitor_->RemoveWtsConsoleObserver(this); + + CHECK(launcher_.get() == NULL); +} + +void WtsConsoleSessionProcessDriver::OnChannelConnected() { + DCHECK(caller_task_runner_->BelongsToCurrentThread()); +} + +bool WtsConsoleSessionProcessDriver::OnMessageReceived( + const IPC::Message& message) { + DCHECK(caller_task_runner_->BelongsToCurrentThread()); + + return false; +} + +void WtsConsoleSessionProcessDriver::OnPermanentError() { + DCHECK(caller_task_runner_->BelongsToCurrentThread()); + + Stop(); +} + +void WtsConsoleSessionProcessDriver::OnSessionAttached(uint32 session_id) { + DCHECK(caller_task_runner_->BelongsToCurrentThread()); + + if (stoppable_state() != Stoppable::kRunning) { + return; + } + + DCHECK(launcher_.get() == NULL); + + // Construct the host binary name. + FilePath dir_path; + if (!PathService::Get(base::DIR_EXE, &dir_path)) { + LOG(ERROR) << "Failed to get the executable file name."; + Stop(); + return; + } + FilePath host_binary = dir_path.Append(kMe2meHostBinaryName); + + // Create a Delegate capable of launching an elevated process in the session. + scoped_ptr<WtsSessionProcessDelegate> delegate( + new WtsSessionProcessDelegate(caller_task_runner_, + io_task_runner_, + host_binary, + session_id, + true)); + + // Use the Delegate to launch the host process. + launcher_.reset(new WorkerProcessLauncher(caller_task_runner_, + io_task_runner_, + delegate.Pass(), + this, + kDaemonIpcSecurityDescriptor)); +} + +void WtsConsoleSessionProcessDriver::OnSessionDetached() { + DCHECK(caller_task_runner_->BelongsToCurrentThread()); + DCHECK(launcher_.get() != NULL); + + launcher_.reset(); +} + +void WtsConsoleSessionProcessDriver::DoStop() { + DCHECK(caller_task_runner_->BelongsToCurrentThread()); + + launcher_.reset(); + CompleteStopping(); +} + +} // namespace remoting diff --git a/remoting/host/win/wts_console_session_process_driver.h b/remoting/host/win/wts_console_session_process_driver.h new file mode 100644 index 0000000..90a8f3b --- /dev/null +++ b/remoting/host/win/wts_console_session_process_driver.h @@ -0,0 +1,76 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef REMOTING_HOST_WIN_WTS_CONSOLE_SESSION_PROCESS_DRIVER_H_ +#define REMOTING_HOST_WIN_WTS_CONSOLE_SESSION_PROCESS_DRIVER_H_ + +#include "base/callback_forward.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "remoting/base/stoppable.h" +#include "remoting/host/win/wts_console_observer.h" +#include "remoting/host/worker_process_ipc_delegate.h" + +namespace base { +class SingleThreadTaskRunner; +} // namespace base + +namespace remoting { + +class WorkerProcessLauncher; +class WtsConsoleMonitor; + +// Launches the host in the session attached to the console. When a new session +// attaches to the console relaunches the host in it. +class WtsConsoleSessionProcessDriver + : public Stoppable, + public WorkerProcessIpcDelegate, + public WtsConsoleObserver { + public: + // Constructs a WtsConsoleSessionProcessDriver object. |stopped_callback| will + // be invoked to notify the caller that the object is completely stopped. All + // public methods of this class must be invoked on the |caller_task_runner| + // thread. |monitor| will only be accessed from the |caller_task_runner| + // thread. |io_task_runner| must be an I/O message loop. + WtsConsoleSessionProcessDriver( + const base::Closure& stopped_callback, + WtsConsoleMonitor* monitor, + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner); + + virtual ~WtsConsoleSessionProcessDriver(); + + // WorkerProcessIpcDelegate implementation. + virtual void OnChannelConnected() OVERRIDE; + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + virtual void OnPermanentError() OVERRIDE; + + // WtsConsoleObserver implementation. + virtual void OnSessionAttached(uint32 session_id) OVERRIDE; + virtual void OnSessionDetached() OVERRIDE; + + protected: + // Stoppable implementation. + virtual void DoStop() OVERRIDE; + + private: + // Task runner on which public methods of this class must be called. + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_; + + // Message loop used by the IPC channel. + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; + + // Launches and monitors the worker process. + scoped_ptr<WorkerProcessLauncher> launcher_; + + // Used to unsubscribe from session attach and detach events. + WtsConsoleMonitor* monitor_; + + DISALLOW_COPY_AND_ASSIGN(WtsConsoleSessionProcessDriver); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_WIN_WTS_CONSOLE_SESSION_PROCESS_DRIVER_H_ diff --git a/remoting/host/win/wts_session_process_delegate.cc b/remoting/host/win/wts_session_process_delegate.cc new file mode 100644 index 0000000..54fc92c --- /dev/null +++ b/remoting/host/win/wts_session_process_delegate.cc @@ -0,0 +1,457 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// This file implements the Windows service controlling Me2Me host processes +// running within user sessions. + +#include "remoting/host/win/wts_session_process_delegate.h" + +#include <sddl.h> +#include <limits> + +#include "base/base_switches.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/command_line.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/path_service.h" +#include "base/single_thread_task_runner.h" +#include "base/time.h" +#include "base/timer.h" +#include "base/utf_string_conversions.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "ipc/ipc_channel.h" +#include "ipc/ipc_channel_proxy.h" +#include "ipc/ipc_message.h" +#include "remoting/host/host_exit_codes.h" +#include "remoting/host/win/launch_process_with_token.h" +#include "remoting/host/win/worker_process_launcher.h" +#include "remoting/host/win/wts_console_monitor.h" +#include "remoting/host/worker_process_ipc_delegate.h" + +using base::TimeDelta; +using base::win::ScopedHandle; + +const FilePath::CharType kDaemonBinaryName[] = + FILE_PATH_LITERAL("remoting_daemon.exe"); + +// The command line switch specifying the name of the daemon IPC endpoint. +const char kDaemonIpcSwitchName[] = "daemon-pipe"; + +const char kElevateSwitchName[] = "elevate"; + +// The command line parameters that should be copied from the service's command +// line to the host process. +const char* kCopiedSwitchNames[] = { + "host-config", switches::kV, switches::kVModule }; + +namespace remoting { + +// A private class actually implementing the functionality provided by +// |WtsSessionProcessDelegate|. This class is ref-counted and implements +// asynchronous fire-and-forget shutdown. +class WtsSessionProcessDelegate::Core + : public base::RefCountedThreadSafe<WtsSessionProcessDelegate::Core>, + public base::MessagePumpForIO::IOHandler, + public WorkerProcessLauncher::Delegate { + public: + // The caller must ensure that |delegate| remains valid at least until + // Stop() method has been called. + Core(scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, + const FilePath& binary_path, + bool launch_elevated); + + // base::MessagePumpForIO::IOHandler implementation. + virtual void OnIOCompleted(base::MessagePumpForIO::IOContext* context, + DWORD bytes_transferred, + DWORD error) OVERRIDE; + + // WorkerProcessLauncher::Delegate implementation. + virtual DWORD GetExitCode() OVERRIDE; + virtual void KillProcess(DWORD exit_code) OVERRIDE; + virtual bool LaunchProcess( + const std::string& channel_name, + base::win::ScopedHandle* process_exit_event_out) OVERRIDE; + + // Initializes the object returning true on success. + bool Initialize(uint32 session_id); + + // Stops the object asynchronously. + void Stop(); + + private: + friend class base::RefCountedThreadSafe<Core>; + virtual ~Core(); + + // Drains the completion port queue to make sure that all job object + // notifications have been received. + void DrainJobNotifications(); + + // Notified 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(); + + // Notified that the job object initialization is complete. + void InitializeJobCompleted(scoped_ptr<base::win::ScopedHandle> job); + + // Called to process incoming job object notifications. + void OnJobNotification(DWORD message, DWORD pid); + + // The task runner all public methods of this class should be called on. + scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; + + // The task runner serving job object notifications. + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; + + // Path to the worker process binary. + FilePath binary_path_; + + // The job object used to control the lifetime of child processes. + base::win::ScopedHandle job_; + + // True if the worker process should be launched elevated. + bool launch_elevated_; + + // A handle that becomes signalled once all processes associated with the job + // have been terminated. + base::win::ScopedHandle process_exit_event_; + + // The token to be used to launch a process in a different session. + base::win::ScopedHandle session_token_; + + // True if Stop() has been called. + bool stopping_; + + // The handle of the worker process, if launched. + base::win::ScopedHandle worker_process_; + + DISALLOW_COPY_AND_ASSIGN(Core); +}; + +WtsSessionProcessDelegate::Core::Core( + scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, + const FilePath& binary_path, + bool launch_elevated) + : main_task_runner_(main_task_runner), + io_task_runner_(io_task_runner), + binary_path_(binary_path), + launch_elevated_(launch_elevated), + stopping_(false) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); +} + +void WtsSessionProcessDelegate::Core::OnIOCompleted( + base::MessagePumpForIO::IOContext* context, + DWORD bytes_transferred, + DWORD error) { + DCHECK(io_task_runner_->BelongsToCurrentThread()); + + // |bytes_transferred| is used in job object notifications to supply + // the message ID; |context| carries process ID. + main_task_runner_->PostTask(FROM_HERE, base::Bind( + &Core::OnJobNotification, this, bytes_transferred, + reinterpret_cast<DWORD>(context))); +} + +DWORD WtsSessionProcessDelegate::Core::GetExitCode() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + DWORD exit_code = CONTROL_C_EXIT; + if (worker_process_.IsValid()) { + if (!::GetExitCodeProcess(worker_process_, &exit_code)) { + LOG_GETLASTERROR(INFO) + << "Failed to query the exit code of the worker process"; + exit_code = CONTROL_C_EXIT; + } + } + + return exit_code; +} + +void WtsSessionProcessDelegate::Core::KillProcess(DWORD exit_code) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + if (launch_elevated_) { + if (job_.IsValid()) { + TerminateJobObject(job_, exit_code); + } + } else { + if (worker_process_.IsValid()) { + TerminateProcess(worker_process_, exit_code); + } + } +} + +bool WtsSessionProcessDelegate::Core::LaunchProcess( + const std::string& channel_name, + ScopedHandle* process_exit_event_out) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + CommandLine command_line(CommandLine::NO_PROGRAM); + if (launch_elevated_) { + // The job object is not ready. Retry starting the host process later. + if (!job_.IsValid()) { + return false; + } + + // Construct the helper binary name. + FilePath dir_path; + if (!PathService::Get(base::DIR_EXE, &dir_path)) { + LOG(ERROR) << "Failed to get the executable file name."; + return false; + } + FilePath daemon_binary = dir_path.Append(kDaemonBinaryName); + + // Create the command line passing the name of the IPC channel to use and + // copying known switches from the caller's command line. + command_line.SetProgram(daemon_binary); + command_line.AppendSwitchPath(kElevateSwitchName, binary_path_); + + CHECK(ResetEvent(process_exit_event_)); + } else { + command_line.SetProgram(binary_path_); + } + + // Create the command line passing the name of the IPC channel to use and + // copying known switches from the caller's command line. + command_line.AppendSwitchNative(kDaemonIpcSwitchName, + UTF8ToWide(channel_name)); + command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(), + kCopiedSwitchNames, + arraysize(kCopiedSwitchNames)); + + // Try to launch the process. + ScopedHandle worker_process; + ScopedHandle worker_thread; + if (!LaunchProcessWithToken(command_line.GetProgram(), + command_line.GetCommandLineString(), + session_token_, + CREATE_SUSPENDED | CREATE_BREAKAWAY_FROM_JOB, + &worker_process, + &worker_thread)) { + return false; + } + + HANDLE local_process_exit_event; + if (launch_elevated_) { + if (!AssignProcessToJobObject(job_, worker_process)) { + LOG_GETLASTERROR(ERROR) + << "Failed to assign the worker to the job object"; + TerminateProcess(worker_process, CONTROL_C_EXIT); + return false; + } + + local_process_exit_event = process_exit_event_; + } else { + worker_process_ = worker_process.Pass(); + local_process_exit_event = worker_process_; + } + + if (!ResumeThread(worker_thread)) { + LOG_GETLASTERROR(ERROR) << "Failed to resume the worker thread"; + KillProcess(CONTROL_C_EXIT); + return false; + } + + // Return a handle that the caller can wait on to get notified when + // the process terminates. + ScopedHandle process_exit_event; + if (!DuplicateHandle(GetCurrentProcess(), + local_process_exit_event, + GetCurrentProcess(), + process_exit_event.Receive(), + SYNCHRONIZE, + FALSE, + 0)) { + LOG_GETLASTERROR(ERROR) << "Failed to duplicate a handle"; + KillProcess(CONTROL_C_EXIT); + return false; + } + + *process_exit_event_out = process_exit_event.Pass(); + return true; +} + +bool WtsSessionProcessDelegate::Core::Initialize(uint32 session_id) { + if (base::win::GetVersion() == base::win::VERSION_XP) + launch_elevated_ = false; + + if (launch_elevated_) { + process_exit_event_.Set(CreateEvent(NULL, TRUE, FALSE, NULL)); + if (!process_exit_event_.IsValid()) { + LOG(ERROR) << "Failed to create a nameless event"; + return false; + } + + // To receive job object notifications the job object is registered with + // the completion port represented by |io_task_runner|. The registration has + // to be done on the I/O thread because + // MessageLoopForIO::RegisterJobObject() can only be called via + // MessageLoopForIO::current(). + io_task_runner_->PostTask(FROM_HERE, + base::Bind(&Core::InitializeJob, this)); + } + + // Create a session token for the launched process. + return CreateSessionToken(session_id, &session_token_); +} + +void WtsSessionProcessDelegate::Core::Stop() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + if (!stopping_) { + stopping_ = true; + + // Drain the completion queue to make sure all job object notifications have + // been received. + DrainJobNotificationsCompleted(); + } +} + +WtsSessionProcessDelegate::Core::~Core() { +} + +void WtsSessionProcessDelegate::Core::DrainJobNotifications() { + DCHECK(io_task_runner_->BelongsToCurrentThread()); + + // DrainJobNotifications() is posted after the job object is destroyed, so + // by this time all notifications from the job object have been processed + // already. Let the main thread know that the queue has been drained. + main_task_runner_->PostTask(FROM_HERE, base::Bind( + &Core::DrainJobNotificationsCompleted, this)); +} + +void WtsSessionProcessDelegate::Core::DrainJobNotificationsCompleted() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + if (job_.IsValid()) { + job_.Close(); + + // Drain the completion queue to make sure all job object notification have + // been received. + io_task_runner_->PostTask(FROM_HERE, base::Bind( + &Core::DrainJobNotifications, this)); + } +} + +void WtsSessionProcessDelegate::Core::InitializeJob() { + DCHECK(io_task_runner_->BelongsToCurrentThread()); + + ScopedHandle job; + job.Set(CreateJobObject(NULL, NULL)); + if (!job.IsValid()) { + LOG_GETLASTERROR(ERROR) << "Failed to create a job object"; + return; + } + + // Limit the number of active processes in the job to two (the process + // performing elevation and the host) and make sure that all processes will be + // killed once the job object is destroyed. + JOBOBJECT_EXTENDED_LIMIT_INFORMATION info; + memset(&info, 0, sizeof(info)); + info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_ACTIVE_PROCESS | + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + info.BasicLimitInformation.ActiveProcessLimit = 2; + if (!SetInformationJobObject(job, + JobObjectExtendedLimitInformation, + &info, + sizeof(info))) { + LOG_GETLASTERROR(ERROR) << "Failed to set limits on the job object"; + return; + } + + // Register to receive job notifications via the I/O thread's completion port. + if (!MessageLoopForIO::current()->RegisterJobObject(job, this)) { + LOG_GETLASTERROR(ERROR) + << "Failed to associate the job object with a completion port"; + return; + } + + // ScopedHandle is not compatible with base::Passed, so we wrap it to a scoped + // pointer. + scoped_ptr<ScopedHandle> job_wrapper(new ScopedHandle()); + *job_wrapper = job.Pass(); + + // Let the main thread know that initialization is complete. + main_task_runner_->PostTask(FROM_HERE, base::Bind( + &Core::InitializeJobCompleted, this, base::Passed(&job_wrapper))); +} + +void WtsSessionProcessDelegate::Core::InitializeJobCompleted( + scoped_ptr<ScopedHandle> job) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK(!job_.IsValid()); + + job_ = job->Pass(); +} + +void WtsSessionProcessDelegate::Core::OnJobNotification(DWORD message, + DWORD pid) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + switch (message) { + case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: + CHECK(SetEvent(process_exit_event_)); + break; + + case JOB_OBJECT_MSG_NEW_PROCESS: + // We report the exit code of the worker process to be |CONTROL_C_EXIT| + // if we cannot get the actual exit code. So here we can safely ignore + // the error returned by OpenProcess(). + worker_process_.Set(OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid)); + break; + } +} + +WtsSessionProcessDelegate::WtsSessionProcessDelegate( + scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, + const FilePath& binary_path, + uint32 session_id, + bool launch_elevated) { + core_ = new Core(main_task_runner, io_task_runner, binary_path, + launch_elevated); + if (!core_->Initialize(session_id)) { + core_->Stop(); + core_ = NULL; + } +} + +WtsSessionProcessDelegate::~WtsSessionProcessDelegate() { + core_->Stop(); +} + +DWORD WtsSessionProcessDelegate::GetExitCode() { + if (!core_) + return CONTROL_C_EXIT; + + return core_->GetExitCode(); +} + +void WtsSessionProcessDelegate::KillProcess(DWORD exit_code) { + if (core_) { + core_->KillProcess(exit_code); + } +} + +bool WtsSessionProcessDelegate::LaunchProcess( + const std::string& channel_name, + base::win::ScopedHandle* process_exit_event_out) { + if (!core_) + return false; + + return core_->LaunchProcess(channel_name, process_exit_event_out); +} + +} // namespace remoting diff --git a/remoting/host/win/wts_session_process_delegate.h b/remoting/host/win/wts_session_process_delegate.h new file mode 100644 index 0000000..106820e --- /dev/null +++ b/remoting/host/win/wts_session_process_delegate.h @@ -0,0 +1,51 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef REMOTING_HOST_WIN_WTS_SESSION_PROCESS_DELEGATE_H_ +#define REMOTING_HOST_WIN_WTS_SESSION_PROCESS_DELEGATE_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "remoting/host/win/worker_process_launcher.h" + +class FilePath; + +namespace base { +class SingleThreadTaskRunner; +} // namespace base + +namespace remoting { + +// Implements logic for launching and monitoring a worker process in a different +// session. +class WtsSessionProcessDelegate + : public WorkerProcessLauncher::Delegate { + public: + WtsSessionProcessDelegate( + scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, + const FilePath& binary_path, + uint32 session_id, + bool launch_elevated); + ~WtsSessionProcessDelegate(); + + // WorkerProcessLauncher::Delegate implementation. + virtual DWORD GetExitCode() OVERRIDE; + virtual void KillProcess(DWORD exit_code) OVERRIDE; + virtual bool LaunchProcess( + const std::string& channel_name, + base::win::ScopedHandle* process_exit_event_out) OVERRIDE; + + private: + // The actual implementation resides in WtsSessionProcessDelegate::Core class. + class Core; + scoped_refptr<Core> core_; + + DISALLOW_COPY_AND_ASSIGN(WtsSessionProcessDelegate); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_WIN_WTS_SESSION_PROCESS_DELEGATE_H_ diff --git a/remoting/host/win/wts_session_process_launcher.cc b/remoting/host/win/wts_session_process_launcher.cc deleted file mode 100644 index 9b991fa..0000000 --- a/remoting/host/win/wts_session_process_launcher.cc +++ /dev/null @@ -1,713 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -// -// This file implements the Windows service controlling Me2Me host processes -// running within user sessions. - -#include "remoting/host/win/wts_session_process_launcher.h" - -#include <windows.h> -#include <sddl.h> -#include <limits> - -#include "base/base_switches.h" -#include "base/bind.h" -#include "base/bind_helpers.h" -#include "base/command_line.h" -#include "base/file_path.h" -#include "base/file_util.h" -#include "base/logging.h" -#include "base/single_thread_task_runner.h" -#include "base/path_service.h" -#include "base/rand_util.h" -#include "base/stringprintf.h" -#include "base/utf_string_conversions.h" -#include "base/win/scoped_handle.h" -#include "base/win/windows_version.h" -#include "ipc/ipc_channel_proxy.h" -#include "ipc/ipc_message.h" -#include "ipc/ipc_message_macros.h" -#include "remoting/host/chromoting_messages.h" -#include "remoting/host/host_exit_codes.h" -#include "remoting/host/win/launch_process_with_token.h" -#include "remoting/host/win/wts_console_monitor.h" - -using base::win::ScopedHandle; -using base::TimeDelta; - -namespace { - -// The minimum and maximum delays between attempts to inject host process into -// a session. -const int kMaxLaunchDelaySeconds = 60; -const int kMinLaunchDelaySeconds = 1; - -const FilePath::CharType kMe2meHostBinaryName[] = - FILE_PATH_LITERAL("remoting_host.exe"); - -const FilePath::CharType kDaemonBinaryName[] = - FILE_PATH_LITERAL("remoting_daemon.exe"); - -// The command line switch specifying the name of the daemon IPC endpoint. -const char kDaemonIpcSwitchName[] = "daemon-pipe"; - -const char kElevateSwitchName[] = "elevate"; - -// The command line parameters that should be copied from the service's command -// line to the host process. -const char* kCopiedSwitchNames[] = { - "host-config", switches::kV, switches::kVModule }; - -// The security descriptor of the daemon IPC endpoint. It gives full access -// to LocalSystem and denies access by anyone else. -const char kDaemonIpcSecurityDescriptor[] = "O:SYG:SYD:(A;;GA;;;SY)"; - -} // namespace - -namespace remoting { - -class WtsSessionProcessLauncherImpl - : public base::RefCountedThreadSafe<WtsSessionProcessLauncherImpl>, - public WorkerProcessLauncher::Delegate { - public: - // Returns the exit code of the worker process. - virtual DWORD GetExitCode() = 0; - - // Stops the object asynchronously. - virtual void Stop() = 0; - - protected: - friend class base::RefCountedThreadSafe<WtsSessionProcessLauncherImpl>; - virtual ~WtsSessionProcessLauncherImpl(); -}; - -namespace { - -// Implements |WorkerProcessLauncher::Delegate| that starts the specified -// process in a different session via CreateProcessAsUser() and uses -// the returned handle to monitor and terminate the process. -class SingleProcessLauncher : public WtsSessionProcessLauncherImpl { - public: - SingleProcessLauncher( - uint32 session_id, - const FilePath& binary_path, - scoped_refptr<base::SingleThreadTaskRunner> main_task_runner); - - // WorkerProcessLauncher::Delegate implementation. - virtual bool DoLaunchProcess( - const std::string& channel_name, - ScopedHandle* process_exit_event_out) OVERRIDE; - virtual void DoKillProcess(DWORD exit_code) OVERRIDE; - - // WtsSessionProcessLauncherImpl implementation. - virtual DWORD GetExitCode() OVERRIDE; - virtual void Stop() OVERRIDE; - - private: - FilePath binary_path_; - - // The task runner all public methods of this class should be called on. - scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; - - // A handle that becomes signalled once the process associated has been - // terminated. - ScopedHandle process_exit_event_; - - // The token to be used to launch a process in a different session. - ScopedHandle user_token_; - - // The handle of the worker process, if launched. - ScopedHandle worker_process_; - - DISALLOW_COPY_AND_ASSIGN(SingleProcessLauncher); -}; - -// Implements |WorkerProcessLauncher::Delegate| that starts the specified -// process (UAC) elevated in a different session. |ElevatedProcessLauncher| -// utilizes a helper process to bypass limitations of ShellExecute() which -// cannot spawn processes across the session boundary. -class ElevatedProcessLauncher : public WtsSessionProcessLauncherImpl, - public base::MessagePumpForIO::IOHandler { - public: - ElevatedProcessLauncher( - uint32 session_id, - const FilePath& binary_path, - scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, - scoped_refptr<base::SingleThreadTaskRunner> io_task_runner); - - // 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, - ScopedHandle* process_exit_event_out) OVERRIDE; - virtual void DoKillProcess(DWORD exit_code) OVERRIDE; - - // WtsSessionProcessLauncherImpl implementation. - virtual DWORD GetExitCode() OVERRIDE; - virtual void Stop() OVERRIDE; - - private: - // Drains the completion port queue to make sure that all job object - // notifications have been received. - void DrainJobNotifications(); - - // Notified 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(); - - // Notified that the job object initialization is complete. - void InitializeJobCompleted(scoped_ptr<ScopedHandle> job); - - // Called to process incoming job object notifications. - void OnJobNotification(DWORD message, DWORD pid); - - FilePath binary_path_; - - // The task runner all public methods of this class should be called on. - scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; - - // The task runner serving job object notifications. - scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; - - // The job object used to control the lifetime of child processes. - ScopedHandle job_; - - // A waiting handle that becomes signalled once all process associated with - // the job have been terminated. - ScopedHandle process_exit_event_; - - // The token to be used to launch a process in a different session. - ScopedHandle user_token_; - - // The handle of the worker process, if launched. - ScopedHandle worker_process_; - - DISALLOW_COPY_AND_ASSIGN(ElevatedProcessLauncher); -}; - -SingleProcessLauncher::SingleProcessLauncher( - uint32 session_id, - const FilePath& binary_path, - scoped_refptr<base::SingleThreadTaskRunner> main_task_runner) - : binary_path_(binary_path), - main_task_runner_(main_task_runner) { - CHECK(CreateSessionToken(session_id, &user_token_)); -} - -bool SingleProcessLauncher::DoLaunchProcess( - const std::string& channel_name, - ScopedHandle* process_exit_event_out) { - DCHECK(main_task_runner_->BelongsToCurrentThread()); - - // Create the command line passing the name of the IPC channel to use and - // copying known switches from the caller's command line. - CommandLine command_line(binary_path_); - command_line.AppendSwitchNative(kDaemonIpcSwitchName, - UTF8ToWide(channel_name)); - command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(), - kCopiedSwitchNames, - _countof(kCopiedSwitchNames)); - - // Try to launch the process and attach an object watcher to the returned - // handle so that we get notified when the process terminates. - ScopedHandle worker_thread; - worker_process_.Close(); - if (!LaunchProcessWithToken(binary_path_, - command_line.GetCommandLineString(), - user_token_, - 0, - &worker_process_, - &worker_thread)) { - return false; - } - - ScopedHandle process_exit_event; - if (!DuplicateHandle(GetCurrentProcess(), - worker_process_, - GetCurrentProcess(), - process_exit_event.Receive(), - SYNCHRONIZE, - FALSE, - 0)) { - LOG_GETLASTERROR(ERROR) << "Failed to duplicate a handle"; - DoKillProcess(CONTROL_C_EXIT); - return false; - } - - *process_exit_event_out = process_exit_event.Pass(); - return true; -} - -void SingleProcessLauncher::DoKillProcess(DWORD exit_code) { - DCHECK(main_task_runner_->BelongsToCurrentThread()); - - if (worker_process_.IsValid()) { - TerminateProcess(worker_process_, exit_code); - } -} - -DWORD SingleProcessLauncher::GetExitCode() { - DCHECK(main_task_runner_->BelongsToCurrentThread()); - - DWORD exit_code = CONTROL_C_EXIT; - if (worker_process_.IsValid()) { - if (!::GetExitCodeProcess(worker_process_, &exit_code)) { - LOG_GETLASTERROR(INFO) - << "Failed to query the exit code of the worker process"; - exit_code = CONTROL_C_EXIT; - } - - worker_process_.Close(); - } - - return exit_code; -} - -void SingleProcessLauncher::Stop() { - DCHECK(main_task_runner_->BelongsToCurrentThread()); -} - -ElevatedProcessLauncher::ElevatedProcessLauncher( - uint32 session_id, - const FilePath& binary_path, - scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, - scoped_refptr<base::SingleThreadTaskRunner> io_task_runner) - : binary_path_(binary_path), - main_task_runner_(main_task_runner), - io_task_runner_(io_task_runner) { - process_exit_event_.Set(CreateEvent(NULL, TRUE, FALSE, NULL)); - CHECK(process_exit_event_.IsValid()); - - CHECK(CreateSessionToken(session_id, &user_token_)); - - // To receive job object notifications the job object is registered with - // the completion port represented by |io_task_runner|. The registration has - // to be done on the I/O thread because MessageLoopForIO::RegisterJobObject() - // can only be called via MessageLoopForIO::current(). - io_task_runner_->PostTask( - FROM_HERE, - base::Bind(&ElevatedProcessLauncher::InitializeJob, this)); -} - -void ElevatedProcessLauncher::OnIOCompleted( - base::MessagePumpForIO::IOContext* context, - DWORD bytes_transferred, - DWORD error) { - DCHECK(io_task_runner_->BelongsToCurrentThread()); - - // |bytes_transferred| is used in job object notifications to supply - // the message ID; |context| carries process ID. - main_task_runner_->PostTask(FROM_HERE, base::Bind( - &ElevatedProcessLauncher::OnJobNotification, this, bytes_transferred, - reinterpret_cast<DWORD>(context))); -} - -bool ElevatedProcessLauncher::DoLaunchProcess( - const std::string& channel_name, - ScopedHandle* process_exit_event_out) { - DCHECK(main_task_runner_->BelongsToCurrentThread()); - - // The job object is not ready. Retry starting the host process later. - if (!job_.IsValid()) { - return false; - } - - // Construct the helper binary name. - FilePath dir_path; - if (!PathService::Get(base::DIR_EXE, &dir_path)) { - LOG(ERROR) << "Failed to get the executable file name."; - return false; - } - FilePath daemon_binary = dir_path.Append(kDaemonBinaryName); - - // Create the command line passing the name of the IPC channel to use and - // copying known switches from the caller's command line. - CommandLine command_line(daemon_binary); - command_line.AppendSwitchPath(kElevateSwitchName, binary_path_); - command_line.AppendSwitchNative(kDaemonIpcSwitchName, - UTF8ToWide(channel_name)); - command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(), - kCopiedSwitchNames, - _countof(kCopiedSwitchNames)); - - CHECK(ResetEvent(process_exit_event_)); - - // Try to launch the process and attach an object watcher to the returned - // handle so that we get notified when the process terminates. - ScopedHandle worker_process; - ScopedHandle worker_thread; - if (!LaunchProcessWithToken(daemon_binary, - command_line.GetCommandLineString(), - user_token_, - CREATE_SUSPENDED, - &worker_process, - &worker_thread)) { - return false; - } - - if (!AssignProcessToJobObject(job_, worker_process)) { - LOG_GETLASTERROR(ERROR) << "Failed to assign the worker to the job object"; - TerminateProcess(worker_process, CONTROL_C_EXIT); - return false; - } - - if (!ResumeThread(worker_thread)) { - LOG_GETLASTERROR(ERROR) << "Failed to resume the worker thread"; - DoKillProcess(CONTROL_C_EXIT); - return false; - } - - ScopedHandle process_exit_event; - if (!DuplicateHandle(GetCurrentProcess(), - process_exit_event_, - GetCurrentProcess(), - process_exit_event.Receive(), - SYNCHRONIZE, - FALSE, - 0)) { - LOG_GETLASTERROR(ERROR) << "Failed to duplicate a handle"; - DoKillProcess(CONTROL_C_EXIT); - return false; - } - - *process_exit_event_out = process_exit_event.Pass(); - return true; -} - -void ElevatedProcessLauncher::DoKillProcess(DWORD exit_code) { - DCHECK(main_task_runner_->BelongsToCurrentThread()); - - if (job_.IsValid()) { - TerminateJobObject(job_, exit_code); - } -} - -DWORD ElevatedProcessLauncher::GetExitCode() { - DCHECK(main_task_runner_->BelongsToCurrentThread()); - - DWORD exit_code = CONTROL_C_EXIT; - if (worker_process_.IsValid()) { - if (!::GetExitCodeProcess(worker_process_, &exit_code)) { - LOG_GETLASTERROR(INFO) - << "Failed to query the exit code of the worker process"; - exit_code = CONTROL_C_EXIT; - } - - worker_process_.Close(); - } - - return exit_code; -} - -void ElevatedProcessLauncher::Stop() { - DCHECK(main_task_runner_->BelongsToCurrentThread()); - - // Drain the completion queue to make sure all job object notification have - // been received. - DrainJobNotificationsCompleted(); -} - -void ElevatedProcessLauncher::DrainJobNotifications() { - DCHECK(io_task_runner_->BelongsToCurrentThread()); - - // DrainJobNotifications() is posted after the job object is destroyed, so - // by this time all notifications from the job object have been processed - // already. Let the main thread know that the queue has been drained. - main_task_runner_->PostTask(FROM_HERE, base::Bind( - &ElevatedProcessLauncher::DrainJobNotificationsCompleted, this)); -} - -void ElevatedProcessLauncher::DrainJobNotificationsCompleted() { - DCHECK(main_task_runner_->BelongsToCurrentThread()); - - if (job_.IsValid()) { - job_.Close(); - - // Drain the completion queue to make sure all job object notification have - // been received. - io_task_runner_->PostTask(FROM_HERE, base::Bind( - &ElevatedProcessLauncher::DrainJobNotifications, this)); - } -} - -void ElevatedProcessLauncher::InitializeJob() { - DCHECK(io_task_runner_->BelongsToCurrentThread()); - - ScopedHandle job; - job.Set(CreateJobObject(NULL, NULL)); - if (!job.IsValid()) { - LOG_GETLASTERROR(ERROR) << "Failed to create a job object"; - return; - } - - // Limit the number of active processes in the job to two (the process - // performing elevation and the host) and make sure that all processes will be - // killed once the job object is destroyed. - JOBOBJECT_EXTENDED_LIMIT_INFORMATION info; - memset(&info, 0, sizeof(info)); - info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_ACTIVE_PROCESS | - JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; - info.BasicLimitInformation.ActiveProcessLimit = 2; - if (!SetInformationJobObject(job, - JobObjectExtendedLimitInformation, - &info, - sizeof(info))) { - LOG_GETLASTERROR(ERROR) << "Failed to set limits on the job object"; - return; - } - - // Register the job object with the completion port in the I/O thread to - // receive job notifications. - if (!MessageLoopForIO::current()->RegisterJobObject(job, this)) { - LOG_GETLASTERROR(ERROR) - << "Failed to associate the job object with a completion port"; - return; - } - - // ScopedHandle is not compatible with base::Passed, so we wrap it to a scoped - // pointer. - scoped_ptr<ScopedHandle> job_wrapper(new ScopedHandle()); - *job_wrapper = job.Pass(); - - // Let the main thread know that initialization is complete. - main_task_runner_->PostTask(FROM_HERE, base::Bind( - &ElevatedProcessLauncher::InitializeJobCompleted, this, - base::Passed(&job_wrapper))); -} - -void ElevatedProcessLauncher::InitializeJobCompleted( - scoped_ptr<ScopedHandle> job) { - DCHECK(main_task_runner_->BelongsToCurrentThread()); - DCHECK(!job_.IsValid()); - - job_ = job->Pass(); -} - -void ElevatedProcessLauncher::OnJobNotification(DWORD message, DWORD pid) { - DCHECK(main_task_runner_->BelongsToCurrentThread()); - - switch (message) { - case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: - CHECK(SetEvent(process_exit_event_)); - break; - - case JOB_OBJECT_MSG_NEW_PROCESS: - // We report the exit code of the worker process to be |CONTROL_C_EXIT| - // if we cannot get the actual exit code. So here we can safely ignore - // the error returned by OpenProcess(). - worker_process_.Set(OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid)); - break; - } -} - -} // namespace - -WtsSessionProcessLauncherImpl::~WtsSessionProcessLauncherImpl() { -} - -WtsSessionProcessLauncher::WtsSessionProcessLauncher( - const base::Closure& stopped_callback, - WtsConsoleMonitor* monitor, - scoped_refptr<base::SingleThreadTaskRunner> main_message_loop, - scoped_refptr<base::SingleThreadTaskRunner> ipc_message_loop) - : Stoppable(main_message_loop, stopped_callback), - attached_(false), - main_message_loop_(main_message_loop), - ipc_message_loop_(ipc_message_loop), - monitor_(monitor) { - monitor_->AddWtsConsoleObserver(this); - - // Construct the host binary name. - FilePath dir_path; - if (!PathService::Get(base::DIR_EXE, &dir_path)) { - LOG(ERROR) << "Failed to get the executable file name."; - Stop(); - return; - } - host_binary_ = dir_path.Append(kMe2meHostBinaryName); -} - -WtsSessionProcessLauncher::~WtsSessionProcessLauncher() { - // Make sure that the object is completely stopped. The same check exists - // in Stoppable::~Stoppable() but this one helps us to fail early and - // predictably. - CHECK_EQ(stoppable_state(), Stoppable::kStopped); - - monitor_->RemoveWtsConsoleObserver(this); - - CHECK(!attached_); - CHECK(!timer_.IsRunning()); -} - -void WtsSessionProcessLauncher::OnChannelConnected() { - DCHECK(main_message_loop_->BelongsToCurrentThread()); -} - -bool WtsSessionProcessLauncher::OnMessageReceived(const IPC::Message& message) { - DCHECK(main_message_loop_->BelongsToCurrentThread()); - - return false; -} - -void WtsSessionProcessLauncher::OnSessionAttached(uint32 session_id) { - DCHECK(main_message_loop_->BelongsToCurrentThread()); - - if (stoppable_state() != Stoppable::kRunning) { - return; - } - - DCHECK(!attached_); - DCHECK(!timer_.IsRunning()); - - attached_ = true; - - // Reset the backoff timeout every time the session is switched. - launch_backoff_ = base::TimeDelta(); - - // The workaround we use to launch a process in a not yet logged in session - // (see CreateRemoteSessionProcess()) on Windows XP/2K3 does not let us to - // assign the started process to a job. However on Vista+ we have to start - // the host via a helper process. The helper process calls ShellExecute() that - // does not work across the session boundary but it required to launch - // a binary specifying uiAccess='true' in its manifest. - // - // Below we choose which implementation of |WorkerProcessLauncher::Delegate| - // to use. A single process is launched on XP (since uiAccess='true' does not - // have effect any way). Vista+ utilizes a helper process and assign it to - // a job object to control it. - if (new_impl_.get()) { - new_impl_->Stop(); - new_impl_ = NULL; - } - if (base::win::GetVersion() == base::win::VERSION_XP) { - new_impl_ = new SingleProcessLauncher(session_id, host_binary_, - main_message_loop_); - } else { - new_impl_ = new ElevatedProcessLauncher(session_id, host_binary_, - main_message_loop_, - ipc_message_loop_); - } - - if (launcher_.get() == NULL) { - LaunchProcess(); - } -} - -void WtsSessionProcessLauncher::OnSessionDetached() { - DCHECK(main_message_loop_->BelongsToCurrentThread()); - DCHECK(attached_); - - attached_ = false; - launch_backoff_ = base::TimeDelta(); - timer_.Stop(); - - if (launcher_.get() != NULL) { - launcher_->Stop(); - } -} - -void WtsSessionProcessLauncher::DoStop() { - DCHECK(main_message_loop_->BelongsToCurrentThread()); - - if (attached_) { - OnSessionDetached(); - } - - // Don't complete shutdown if |launcher_| is not completely stopped. - if (launcher_.get() != NULL) { - return; - } - - // Tear down implementation objects asynchromously. - if (new_impl_.get()) { - new_impl_->Stop(); - new_impl_ = NULL; - } - if (impl_.get()) { - impl_->Stop(); - impl_ = NULL; - } - - CompleteStopping(); -} - -void WtsSessionProcessLauncher::LaunchProcess() { - DCHECK(main_message_loop_->BelongsToCurrentThread()); - DCHECK(attached_); - DCHECK(launcher_.get() == NULL); - DCHECK(!timer_.IsRunning()); - - // Switch to a new implementation object if needed. - if (new_impl_.get() != NULL) { - if (impl_.get() != NULL) { - impl_->Stop(); - impl_ = NULL; - } - - impl_.swap(new_impl_); - } - - DCHECK(impl_.get() != NULL); - - launch_time_ = base::Time::Now(); - launcher_.reset(new WorkerProcessLauncher( - impl_.get(), this, - base::Bind(&WtsSessionProcessLauncher::OnLauncherStopped, - base::Unretained(this)), - main_message_loop_, - ipc_message_loop_)); - launcher_->Start(kDaemonIpcSecurityDescriptor); -} - -void WtsSessionProcessLauncher::OnLauncherStopped() { - DCHECK(main_message_loop_->BelongsToCurrentThread()); - - // Retrieve the exit code of the worker process. - DWORD exit_code = impl_->GetExitCode(); - - launcher_.reset(NULL); - - // Do not relaunch the worker process if the caller has asked us to stop. - if (stoppable_state() != Stoppable::kRunning) { - Stop(); - return; - } - - // Stop trying to restart the worker process if its process exited due to - // misconfiguration. - if (kMinPermanentErrorExitCode <= exit_code && - exit_code <= kMaxPermanentErrorExitCode) { - Stop(); - return; - } - - // Try to restart the worker process if we are still attached to a session. - if (attached_) { - // Expand the backoff interval if the process has died quickly or reset it - // if it was up longer than the maximum backoff delay. - base::TimeDelta delta = base::Time::Now() - launch_time_; - if (delta < base::TimeDelta() || - delta >= base::TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)) { - launch_backoff_ = base::TimeDelta(); - } else { - launch_backoff_ = std::max( - launch_backoff_ * 2, TimeDelta::FromSeconds(kMinLaunchDelaySeconds)); - launch_backoff_ = std::min( - launch_backoff_, TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); - } - - // Try to launch the worker process. - timer_.Start(FROM_HERE, launch_backoff_, - this, &WtsSessionProcessLauncher::LaunchProcess); - } -} - -} // namespace remoting diff --git a/remoting/host/win/wts_session_process_launcher.h b/remoting/host/win/wts_session_process_launcher.h deleted file mode 100644 index e9a8e54..0000000 --- a/remoting/host/win/wts_session_process_launcher.h +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef REMOTING_HOST_WIN_WTS_SESSION_PROCESS_LAUNCHER_H_ -#define REMOTING_HOST_WIN_WTS_SESSION_PROCESS_LAUNCHER_H_ - -#include <windows.h> - -#include "base/basictypes.h" -#include "base/compiler_specific.h" -#include "base/file_path.h" -#include "base/file_util.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" -#include "base/win/scoped_handle.h" -#include "ipc/ipc_channel.h" -#include "remoting/base/stoppable.h" -#include "remoting/host/win/worker_process_launcher.h" -#include "remoting/host/win/wts_console_observer.h" -#include "remoting/host/worker_process_ipc_delegate.h" - -namespace base { -class SingleThreadTaskRunner; -} // namespace base - -namespace remoting { - -class WtsConsoleMonitor; -class WtsSessionProcessLauncherImpl; - -class WtsSessionProcessLauncher - : public Stoppable, - public WorkerProcessIpcDelegate, - public WtsConsoleObserver { - public: - // Constructs a WtsSessionProcessLauncher object. |stopped_callback| and - // |main_message_loop| are passed to the undelying |Stoppable| implementation. - // All interaction with |monitor| should happen on |main_message_loop|. - // |ipc_message_loop| must be an I/O message loop. - WtsSessionProcessLauncher( - const base::Closure& stopped_callback, - WtsConsoleMonitor* monitor, - scoped_refptr<base::SingleThreadTaskRunner> main_message_loop, - scoped_refptr<base::SingleThreadTaskRunner> ipc_message_loop); - - virtual ~WtsSessionProcessLauncher(); - - // WorkerProcessIpcDelegate implementation. - virtual void OnChannelConnected() OVERRIDE; - virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; - - // WtsConsoleObserver implementation. - virtual void OnSessionAttached(uint32 session_id) OVERRIDE; - virtual void OnSessionDetached() OVERRIDE; - - protected: - // Stoppable implementation. - virtual void DoStop() OVERRIDE; - - private: - // Attempts to launch the host process in the current console session. - // Schedules next launch attempt if creation of the process fails for any - // reason. - void LaunchProcess(); - - // Called when the launcher reports the process to be stopped. - void OnLauncherStopped(); - - // |true| if this object is currently attached to the console session. - bool attached_; - - // Path to the host binary to launch. - FilePath host_binary_; - - // OS version dependent WorkerProcessLauncher::Delegate implementation - // instance for the currently-running child process. - scoped_refptr<WtsSessionProcessLauncherImpl> impl_; - - // WorkerProcessLauncher::Delegate implementation that will be used to launch - // the new child process after a session switch. - scoped_refptr<WtsSessionProcessLauncherImpl> new_impl_; - - // Time of the last launch attempt. - base::Time launch_time_; - - // Current backoff delay. - base::TimeDelta launch_backoff_; - - // Timer used to schedule the next attempt to launch the process. - base::OneShotTimer<WtsSessionProcessLauncher> timer_; - - // The main service message loop. - scoped_refptr<base::SingleThreadTaskRunner> main_message_loop_; - - // Message loop used by the IPC channel. - scoped_refptr<base::SingleThreadTaskRunner> ipc_message_loop_; - - // This pointer is used to unsubscribe from session attach and detach events. - WtsConsoleMonitor* monitor_; - - scoped_ptr<WorkerProcessLauncher> launcher_; - - DISALLOW_COPY_AND_ASSIGN(WtsSessionProcessLauncher); -}; - -} // namespace remoting - -#endif // REMOTING_HOST_WIN_WTS_SESSION_PROCESS_LAUNCHER_H_ diff --git a/remoting/host/worker_process_ipc_delegate.h b/remoting/host/worker_process_ipc_delegate.h index 6a8ff58..4a03e8f 100644 --- a/remoting/host/worker_process_ipc_delegate.h +++ b/remoting/host/worker_process_ipc_delegate.h @@ -25,6 +25,9 @@ class WorkerProcessIpcDelegate { // Processes messages sent by the client. virtual bool OnMessageReceived(const IPC::Message& message) = 0; + + // Notifies that a permanent error was encountered. + virtual void OnPermanentError() = 0; }; } // namespace remoting diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp index 283db3c..48ae65d 100644 --- a/remoting/remoting.gyp +++ b/remoting/remoting.gyp @@ -592,6 +592,7 @@ '../base/base.gyp:base_static', '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', '../ipc/ipc.gyp:ipc', + '../net/net.gyp:net', 'remoting_base', 'remoting_breakpad', 'remoting_version_resources', @@ -619,12 +620,16 @@ 'host/win/launch_process_with_token.h', 'host/win/omaha.cc', 'host/win/omaha.h', + 'host/win/unprivileged_process_delegate.cc', + 'host/win/unprivileged_process_delegate.h', 'host/win/worker_process_launcher.cc', 'host/win/worker_process_launcher.h', 'host/win/wts_console_monitor.h', 'host/win/wts_console_observer.h', - 'host/win/wts_session_process_launcher.cc', - 'host/win/wts_session_process_launcher.h', + 'host/win/wts_console_session_process_driver.cc', + 'host/win/wts_console_session_process_driver.h', + 'host/win/wts_session_process_delegate.cc', + 'host/win/wts_session_process_delegate.h', 'host/worker_process_ipc_delegate.h', ], 'msvs_settings': { @@ -1944,6 +1949,9 @@ 'host/video_frame_capturer_helper_unittest.cc', 'host/video_frame_capturer_mac_unittest.cc', 'host/video_frame_capturer_unittest.cc', + 'host/win/worker_process_launcher.cc', + 'host/win/worker_process_launcher.h', + 'host/win/worker_process_launcher_unittest.cc', 'jingle_glue/chromium_socket_factory_unittest.cc', 'jingle_glue/fake_signal_strategy.cc', 'jingle_glue/fake_signal_strategy.h', @@ -1981,6 +1989,9 @@ ], 'conditions': [ [ 'OS=="win"', { + 'dependencies': [ + '../ipc/ipc.gyp:ipc', + ], 'include_dirs': [ '../breakpad/src', ], |