| Index: remoting/host/wts_session_process_launcher_win.cc
|
| ===================================================================
|
| --- remoting/host/wts_session_process_launcher_win.cc (revision 149037)
|
| +++ remoting/host/wts_session_process_launcher_win.cc (working copy)
|
| @@ -1,466 +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/wts_session_process_launcher_win.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/logging.h"
|
| -#include "base/message_loop_proxy.h"
|
| -#include "base/process_util.h"
|
| -#include "base/rand_util.h"
|
| -#include "base/stringprintf.h"
|
| -#include "base/win/scoped_handle.h"
|
| -#include "ipc/ipc_channel_proxy.h"
|
| -#include "ipc/ipc_message.h"
|
| -#include "ipc/ipc_message_macros.h"
|
| -#include "remoting/host/constants.h"
|
| -#include "remoting/host/chromoting_messages.h"
|
| -#include "remoting/host/launch_process_in_session_win.h"
|
| -#include "remoting/host/sas_injector.h"
|
| -#include "remoting/host/wts_console_monitor_win.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;
|
| -
|
| -// Match the pipe name prefix used by Chrome IPC channels.
|
| -const wchar_t kChromePipeNamePrefix[] = L"\\\\.\\pipe\\chrome.";
|
| -
|
| -// The IPC channel name is passed to the host in the command line.
|
| -const char kChromotingIpcSwitchName[] = "chromoting-ipc";
|
| -
|
| -// The command line parameters that should be copied from the service's command
|
| -// line to the host process.
|
| -const char* kCopiedSwitchNames[] = {
|
| - "auth-config", "host-config", switches::kV, switches::kVModule };
|
| -
|
| -// The security descriptor of the Chromoting IPC channel. It gives full access
|
| -// to LocalSystem and denies access by anyone else.
|
| -const wchar_t kChromotingChannelSecurityDescriptor[] =
|
| - L"O:SYG:SYD:(A;;GA;;;SY)";
|
| -
|
| -// Takes the process token and makes a copy of it. The returned handle will have
|
| -// |desired_access| rights.
|
| -bool CopyProcessToken(DWORD desired_access,
|
| - ScopedHandle* token_out) {
|
| -
|
| - HANDLE handle;
|
| - if (!OpenProcessToken(GetCurrentProcess(),
|
| - TOKEN_DUPLICATE | desired_access,
|
| - &handle)) {
|
| - LOG_GETLASTERROR(ERROR) << "Failed to open process token";
|
| - return false;
|
| - }
|
| -
|
| - ScopedHandle process_token(handle);
|
| -
|
| - if (!DuplicateTokenEx(process_token,
|
| - desired_access,
|
| - NULL,
|
| - SecurityImpersonation,
|
| - TokenPrimary,
|
| - &handle)) {
|
| - LOG_GETLASTERROR(ERROR) << "Failed to duplicate the process token";
|
| - return false;
|
| - }
|
| -
|
| - token_out->Set(handle);
|
| - return true;
|
| -}
|
| -
|
| -// Creates a copy of the current process with SE_TCB_NAME privilege enabled.
|
| -bool CreatePrivilegedToken(ScopedHandle* token_out) {
|
| - ScopedHandle privileged_token;
|
| - DWORD desired_access = TOKEN_ADJUST_PRIVILEGES | TOKEN_IMPERSONATE |
|
| - TOKEN_DUPLICATE | TOKEN_QUERY;
|
| - if (!CopyProcessToken(desired_access, &privileged_token)) {
|
| - return false;
|
| - }
|
| -
|
| - // Get the LUID for the SE_TCB_NAME privilege.
|
| - TOKEN_PRIVILEGES state;
|
| - state.PrivilegeCount = 1;
|
| - state.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
| - if (!LookupPrivilegeValue(NULL, SE_TCB_NAME, &state.Privileges[0].Luid)) {
|
| - LOG_GETLASTERROR(ERROR) <<
|
| - "Failed to lookup the LUID for the SE_TCB_NAME privilege";
|
| - return false;
|
| - }
|
| -
|
| - // Enable the SE_TCB_NAME privilege.
|
| - if (!AdjustTokenPrivileges(privileged_token, FALSE, &state, 0, NULL, 0)) {
|
| - LOG_GETLASTERROR(ERROR) <<
|
| - "Failed to enable SE_TCB_NAME privilege in a token";
|
| - return false;
|
| - }
|
| -
|
| - token_out->Set(privileged_token.Take());
|
| - return true;
|
| -}
|
| -
|
| -// Creates a copy of the current process token for the given |session_id| so
|
| -// it can be used to launch a process in that session.
|
| -bool CreateSessionToken(uint32 session_id,
|
| - ScopedHandle* token_out) {
|
| -
|
| - ScopedHandle session_token;
|
| - DWORD desired_access = TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID |
|
| - TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY;
|
| - if (!CopyProcessToken(desired_access, &session_token)) {
|
| - return false;
|
| - }
|
| -
|
| - // Change the session ID of the token.
|
| - DWORD new_session_id = session_id;
|
| - if (!SetTokenInformation(session_token,
|
| - TokenSessionId,
|
| - &new_session_id,
|
| - sizeof(new_session_id))) {
|
| - LOG_GETLASTERROR(ERROR) <<
|
| - "Failed to change session ID of a token";
|
| - return false;
|
| - }
|
| -
|
| - token_out->Set(session_token.Take());
|
| - return true;
|
| -}
|
| -
|
| -// Generates random channel ID.
|
| -// N.B. Stolen from src/content/common/child_process_host_impl.cc
|
| -std::wstring GenerateRandomChannelId(void* instance) {
|
| - return base::StringPrintf(L"%d.%p.%d",
|
| - base::GetCurrentProcId(), instance,
|
| - base::RandInt(0, std::numeric_limits<int>::max()));
|
| -}
|
| -
|
| -// Creates the server end of the Chromoting IPC channel.
|
| -// N.B. This code is based on IPC::Channel's implementation.
|
| -bool CreatePipeForIpcChannel(void* instance,
|
| - std::wstring* channel_name_out,
|
| - ScopedHandle* pipe_out) {
|
| - // Create security descriptor for the channel.
|
| - SECURITY_ATTRIBUTES security_attributes;
|
| - security_attributes.nLength = sizeof(security_attributes);
|
| - security_attributes.bInheritHandle = FALSE;
|
| -
|
| - ULONG security_descriptor_length = 0;
|
| - if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
|
| - kChromotingChannelSecurityDescriptor,
|
| - SDDL_REVISION_1,
|
| - reinterpret_cast<PSECURITY_DESCRIPTOR*>(
|
| - &security_attributes.lpSecurityDescriptor),
|
| - &security_descriptor_length)) {
|
| - LOG_GETLASTERROR(ERROR) <<
|
| - "Failed to create a security descriptor for the Chromoting IPC channel";
|
| - return false;
|
| - }
|
| -
|
| - // Generate a random channel name.
|
| - std::wstring channel_name(GenerateRandomChannelId(instance));
|
| -
|
| - // Convert it to the pipe name.
|
| - std::wstring pipe_name(kChromePipeNamePrefix);
|
| - pipe_name.append(channel_name);
|
| -
|
| - // Create the server end of the pipe. This code should match the code in
|
| - // IPC::Channel with exception of passing a non-default security descriptor.
|
| - HANDLE pipe = CreateNamedPipeW(pipe_name.c_str(),
|
| - PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED |
|
| - FILE_FLAG_FIRST_PIPE_INSTANCE,
|
| - PIPE_TYPE_BYTE | PIPE_READMODE_BYTE,
|
| - 1,
|
| - IPC::Channel::kReadBufferSize,
|
| - IPC::Channel::kReadBufferSize,
|
| - 5000,
|
| - &security_attributes);
|
| - if (pipe == INVALID_HANDLE_VALUE) {
|
| - LOG_GETLASTERROR(ERROR) <<
|
| - "Failed to create the server end of the Chromoting IPC channel";
|
| - LocalFree(security_attributes.lpSecurityDescriptor);
|
| - return false;
|
| - }
|
| -
|
| - LocalFree(security_attributes.lpSecurityDescriptor);
|
| -
|
| - *channel_name_out = channel_name;
|
| - pipe_out->Set(pipe);
|
| - return true;
|
| -}
|
| -
|
| -} // namespace
|
| -
|
| -namespace remoting {
|
| -
|
| -// Session id that does not represent any session.
|
| -const uint32 kInvalidSessionId = 0xffffffff;
|
| -
|
| -WtsSessionProcessLauncher::WtsSessionProcessLauncher(
|
| - WtsConsoleMonitor* monitor,
|
| - const FilePath& host_binary,
|
| - scoped_refptr<base::MessageLoopProxy> main_message_loop,
|
| - scoped_refptr<base::MessageLoopProxy> ipc_message_loop)
|
| - : host_binary_(host_binary),
|
| - main_message_loop_(main_message_loop),
|
| - ipc_message_loop_(ipc_message_loop),
|
| - monitor_(monitor),
|
| - state_(StateDetached) {
|
| - monitor_->AddWtsConsoleObserver(this);
|
| -}
|
| -
|
| -WtsSessionProcessLauncher::~WtsSessionProcessLauncher() {
|
| - DCHECK(state_ == StateDetached);
|
| - DCHECK(!timer_.IsRunning());
|
| - DCHECK(process_.handle() == NULL);
|
| - DCHECK(process_watcher_.GetWatchedObject() == NULL);
|
| - DCHECK(chromoting_channel_.get() == NULL);
|
| - if (monitor_ != NULL) {
|
| - monitor_->RemoveWtsConsoleObserver(this);
|
| - }
|
| -}
|
| -
|
| -void WtsSessionProcessLauncher::LaunchProcess() {
|
| - DCHECK(main_message_loop_->BelongsToCurrentThread());
|
| - DCHECK(state_ == StateStarting);
|
| - DCHECK(!timer_.IsRunning());
|
| - DCHECK(process_.handle() == NULL);
|
| - DCHECK(process_watcher_.GetWatchedObject() == NULL);
|
| - DCHECK(chromoting_channel_.get() == NULL);
|
| -
|
| - launch_time_ = base::Time::Now();
|
| -
|
| - std::wstring channel_name;
|
| - ScopedHandle pipe;
|
| - if (CreatePipeForIpcChannel(this, &channel_name, &pipe)) {
|
| - // Wrap the pipe into an IPC channel.
|
| - chromoting_channel_.reset(new IPC::ChannelProxy(
|
| - IPC::ChannelHandle(pipe.Get()),
|
| - IPC::Channel::MODE_SERVER,
|
| - this,
|
| - ipc_message_loop_));
|
| -
|
| - // 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(kChromotingIpcSwitchName, 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.
|
| - if (LaunchProcessInSession(host_binary_,
|
| - command_line.GetCommandLineString(),
|
| - session_token_,
|
| - &process_)) {
|
| - if (process_watcher_.StartWatching(process_.handle(), this)) {
|
| - state_ = StateAttached;
|
| - return;
|
| - } else {
|
| - LOG(ERROR) << "Failed to arm the process watcher.";
|
| - process_.Terminate(0);
|
| - process_.Close();
|
| - }
|
| - }
|
| -
|
| - chromoting_channel_.reset();
|
| - }
|
| -
|
| - // Something went wrong. Try to launch the host again later. The attempts rate
|
| - // is limited by exponential backoff.
|
| - launch_backoff_ = std::max(launch_backoff_ * 2,
|
| - TimeDelta::FromSeconds(kMinLaunchDelaySeconds));
|
| - launch_backoff_ = std::min(launch_backoff_,
|
| - TimeDelta::FromSeconds(kMaxLaunchDelaySeconds));
|
| - timer_.Start(FROM_HERE, launch_backoff_,
|
| - this, &WtsSessionProcessLauncher::LaunchProcess);
|
| -}
|
| -
|
| -void WtsSessionProcessLauncher::OnObjectSignaled(HANDLE object) {
|
| - if (!main_message_loop_->BelongsToCurrentThread()) {
|
| - main_message_loop_->PostTask(
|
| - FROM_HERE, base::Bind(&WtsSessionProcessLauncher::OnObjectSignaled,
|
| - base::Unretained(this), object));
|
| - return;
|
| - }
|
| -
|
| - // It is possible that OnObjectSignaled() task will be queued by another
|
| - // thread right before |process_watcher_| was stopped. It such a case it is
|
| - // safe to ignore this notification.
|
| - if (state_ != StateAttached) {
|
| - return;
|
| - }
|
| -
|
| - DCHECK(!timer_.IsRunning());
|
| - DCHECK(process_.handle() != NULL);
|
| - DCHECK(process_watcher_.GetWatchedObject() == NULL);
|
| - DCHECK(chromoting_channel_.get() != NULL);
|
| -
|
| - // Stop trying to restart the host if its process exited due to
|
| - // misconfiguration.
|
| - int exit_code;
|
| - bool stop_trying =
|
| - base::WaitForExitCodeWithTimeout(
|
| - process_.handle(), &exit_code, base::TimeDelta()) &&
|
| - kMinPermanentErrorExitCode <= exit_code &&
|
| - exit_code <= kMaxPermanentErrorExitCode;
|
| -
|
| - // The host process has been terminated for some reason. The handle can now be
|
| - // closed.
|
| - process_.Close();
|
| - chromoting_channel_.reset();
|
| - state_ = StateStarting;
|
| -
|
| - if (stop_trying) {
|
| - OnSessionDetached();
|
| -
|
| - // N.B. The service will stop once the last observer is removed from
|
| - // the list.
|
| - monitor_->RemoveWtsConsoleObserver(this);
|
| - monitor_ = NULL;
|
| - 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 restart the host.
|
| - timer_.Start(FROM_HERE, launch_backoff_,
|
| - this, &WtsSessionProcessLauncher::LaunchProcess);
|
| -}
|
| -
|
| -bool WtsSessionProcessLauncher::OnMessageReceived(const IPC::Message& message) {
|
| - bool handled = true;
|
| - IPC_BEGIN_MESSAGE_MAP(WtsSessionProcessLauncher, message)
|
| - IPC_MESSAGE_HANDLER(ChromotingHostMsg_SendSasToConsole,
|
| - OnSendSasToConsole)
|
| - IPC_MESSAGE_UNHANDLED(handled = false)
|
| - IPC_END_MESSAGE_MAP()
|
| - return handled;
|
| -}
|
| -
|
| -void WtsSessionProcessLauncher::OnSendSasToConsole() {
|
| - if (!main_message_loop_->BelongsToCurrentThread()) {
|
| - main_message_loop_->PostTask(
|
| - FROM_HERE, base::Bind(&WtsSessionProcessLauncher::OnSendSasToConsole,
|
| - base::Unretained(this)));
|
| - return;
|
| - }
|
| -
|
| - if (state_ == StateAttached) {
|
| - if (sas_injector_.get() == NULL) {
|
| - sas_injector_ = SasInjector::Create();
|
| - }
|
| -
|
| - if (sas_injector_.get() != NULL) {
|
| - sas_injector_->InjectSas();
|
| - }
|
| - }
|
| -}
|
| -
|
| -void WtsSessionProcessLauncher::OnSessionAttached(uint32 session_id) {
|
| - DCHECK(main_message_loop_->BelongsToCurrentThread());
|
| - DCHECK(state_ == StateDetached);
|
| - DCHECK(!timer_.IsRunning());
|
| - DCHECK(process_.handle() == NULL);
|
| - DCHECK(process_watcher_.GetWatchedObject() == NULL);
|
| - DCHECK(chromoting_channel_.get() == NULL);
|
| -
|
| - // Temporarily enable the SE_TCB_NAME privilege. The privileged token is
|
| - // created as needed and kept for later reuse.
|
| - if (privileged_token_.Get() == NULL) {
|
| - if (!CreatePrivilegedToken(&privileged_token_)) {
|
| - return;
|
| - }
|
| - }
|
| -
|
| - if (!ImpersonateLoggedOnUser(privileged_token_)) {
|
| - LOG_GETLASTERROR(ERROR) <<
|
| - "Failed to impersonate the privileged token";
|
| - return;
|
| - }
|
| -
|
| - // While the SE_TCB_NAME privilege is enabled, create a session token for
|
| - // the launched process.
|
| - bool result = CreateSessionToken(session_id, &session_token_);
|
| -
|
| - // Revert to the default token. The default token is sufficient to call
|
| - // CreateProcessAsUser() successfully.
|
| - CHECK(RevertToSelf());
|
| -
|
| - if (!result)
|
| - return;
|
| -
|
| - // Now try to launch the host.
|
| - state_ = StateStarting;
|
| - LaunchProcess();
|
| -}
|
| -
|
| -void WtsSessionProcessLauncher::OnSessionDetached() {
|
| - DCHECK(main_message_loop_->BelongsToCurrentThread());
|
| - DCHECK(state_ == StateDetached ||
|
| - state_ == StateStarting ||
|
| - state_ == StateAttached);
|
| -
|
| - switch (state_) {
|
| - case StateDetached:
|
| - DCHECK(!timer_.IsRunning());
|
| - DCHECK(process_.handle() == NULL);
|
| - DCHECK(process_watcher_.GetWatchedObject() == NULL);
|
| - DCHECK(chromoting_channel_.get() == NULL);
|
| - break;
|
| -
|
| - case StateStarting:
|
| - DCHECK(process_.handle() == NULL);
|
| - DCHECK(process_watcher_.GetWatchedObject() == NULL);
|
| - DCHECK(chromoting_channel_.get() == NULL);
|
| -
|
| - timer_.Stop();
|
| - launch_backoff_ = base::TimeDelta();
|
| - state_ = StateDetached;
|
| - break;
|
| -
|
| - case StateAttached:
|
| - DCHECK(!timer_.IsRunning());
|
| - DCHECK(process_.handle() != NULL);
|
| - DCHECK(process_watcher_.GetWatchedObject() != NULL);
|
| - DCHECK(chromoting_channel_.get() != NULL);
|
| -
|
| - process_watcher_.StopWatching();
|
| - process_.Terminate(0);
|
| - process_.Close();
|
| - chromoting_channel_.reset();
|
| - state_ = StateDetached;
|
| - break;
|
| - }
|
| -
|
| - session_token_.Close();
|
| -}
|
| -
|
| -} // namespace remoting
|
|
|