chromium/remoting/host/desktop_session_win.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "remoting/host/desktop_session_win.h"

#include <objbase.h>

#include <sddl.h>
#include <wrl/client.h>

#include <algorithm>
#include <limits>
#include <memory>
#include <string>
#include <utility>

#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/i18n/time_formatting.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_checker.h"
#include "base/timer/timer.h"
#include "base/uuid.h"
#include "base/win/registry.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_handle.h"
#include "remoting/base/auto_thread_task_runner.h"
#include "remoting/host/base/screen_resolution.h"
#include "remoting/host/base/switches.h"
#include "remoting/host/daemon_process.h"
#include "remoting/host/desktop_session.h"
#include "remoting/host/host_main.h"
#include "remoting/host/ipc_constants.h"
#include "remoting/host/sas_injector.h"
// MIDL-generated declarations and definitions.
#include "remoting/host/win/chromoting_lib.h"
#include "remoting/host/win/host_service.h"
#include "remoting/host/win/trust_util.h"
#include "remoting/host/win/worker_process_launcher.h"
#include "remoting/host/win/wts_session_process_delegate.h"
#include "remoting/host/win/wts_terminal_monitor.h"
#include "remoting/host/win/wts_terminal_observer.h"
#include "remoting/host/worker_process_ipc_delegate.h"

using base::win::ScopedHandle;

namespace remoting {

namespace {

// The security descriptor of the daemon IPC endpoint. It gives full access
// to SYSTEM and denies access by anyone else.
const wchar_t kDaemonIpcSecurityDescriptor[] =
    SDDL_OWNER L":" SDDL_LOCAL_SYSTEM
    SDDL_GROUP L":" SDDL_LOCAL_SYSTEM
    SDDL_DACL L":("
        SDDL_ACCESS_ALLOWED L";;" SDDL_GENERIC_ALL L";;;" SDDL_LOCAL_SYSTEM
    L")";

// The command line parameters that should be copied from the service's command
// line to the desktop process.
const char* const kCopiedSwitchNames[] = {switches::kV, switches::kVModule};

// The default screen dimensions for an RDP session.
const int kDefaultRdpScreenWidth = 1280;
const int kDefaultRdpScreenHeight = 768;

// The minimum effective screen dimensions supported by Windows are 800x600.
const int kMinRdpScreenWidth = 800;
const int kMinRdpScreenHeight = 600;

// Windows supports dimensions up to 8192x8192.
const int kMaxRdpScreenWidth = 8192;
const int kMaxRdpScreenHeight = 8192;

// Default dots per inch used by RDP is 96 DPI.
const int kDefaultRdpDpi = 96;

// The session attach notification should arrive within 30 seconds.
const int kSessionAttachTimeoutSeconds = 30;

// The default port number used for establishing an RDP session.
const int kDefaultRdpPort = 3389;

// Used for validating the required RDP registry values.
const int kRdpConnectionsDisabled = 1;
const int kNetworkLevelAuthEnabled = 1;
const int kSecurityLayerTlsRequired = 2;

// The values used to establish RDP connections are stored in the registry.
const wchar_t kRdpSettingsKeyName[] =
    L"SYSTEM\\CurrentControlSet\\Control\\Terminal Server";
const wchar_t kRdpTcpSettingsKeyName[] =
    L"SYSTEM\\CurrentControlSet\\"
    L"Control\\Terminal Server\\WinStations\\RDP-Tcp";
const wchar_t kRdpPortValueName[] = L"PortNumber";
const wchar_t kDenyTsConnectionsValueName[] = L"fDenyTSConnections";
const wchar_t kNetworkLevelAuthValueName[] = L"UserAuthentication";
const wchar_t kSecurityLayerValueName[] = L"SecurityLayer";

webrtc::DesktopSize GetBoundedRdpDesktopSize(int width, int height) {
  return webrtc::DesktopSize(
      std::clamp(width, kMinRdpScreenWidth, kMaxRdpScreenWidth),
      std::clamp(height, kMinRdpScreenHeight, kMaxRdpScreenHeight));
}

// DesktopSession implementation which attaches to the host's physical console.
// Receives IPC messages from the desktop process, running in the console
// session, via |WorkerProcessIpcDelegate|, and monitors console session
// attach/detach events via |WtsConsoleObserver|.
class ConsoleSession : public DesktopSessionWin {
 public:
  // Same as DesktopSessionWin().
  ConsoleSession(scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
                 scoped_refptr<AutoThreadTaskRunner> io_task_runner,
                 DaemonProcess* daemon_process,
                 int id,
                 WtsTerminalMonitor* monitor);

  ConsoleSession(const ConsoleSession&) = delete;
  ConsoleSession& operator=(const ConsoleSession&) = delete;

  ~ConsoleSession() override;

 protected:
  // DesktopSession overrides.
  void SetScreenResolution(const ScreenResolution& resolution) override;

  // DesktopSessionWin overrides.
  void InjectSas() override;

 private:
  std::unique_ptr<SasInjector> sas_injector_;
};

// DesktopSession implementation which attaches to virtual RDP console.
// Receives IPC messages from the desktop process, running in the console
// session, via |WorkerProcessIpcDelegate|, and monitors console session
// attach/detach events via |WtsConsoleObserver|.
class RdpSession : public DesktopSessionWin {
 public:
  // Same as DesktopSessionWin().
  RdpSession(scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
             scoped_refptr<AutoThreadTaskRunner> io_task_runner,
             DaemonProcess* daemon_process,
             int id,
             WtsTerminalMonitor* monitor);

  RdpSession(const RdpSession&) = delete;
  RdpSession& operator=(const RdpSession&) = delete;

  ~RdpSession() override;

  // Performs the part of initialization that can fail.
  bool Initialize(const ScreenResolution& resolution);

  // Mirrors IRdpDesktopSessionEventHandler.
  void OnRdpConnected();
  void OnRdpClosed();

 protected:
  // DesktopSession overrides.
  void SetScreenResolution(const ScreenResolution& resolution) override;

  // DesktopSessionWin overrides.
  void InjectSas() override;

 private:
  // An implementation of IRdpDesktopSessionEventHandler interface that forwards
  // notifications to the owning desktop session.
  class EventHandler : public IRdpDesktopSessionEventHandler {
   public:
    explicit EventHandler(base::WeakPtr<RdpSession> desktop_session);

    EventHandler(const EventHandler&) = delete;
    EventHandler& operator=(const EventHandler&) = delete;

    virtual ~EventHandler();

    // IUnknown interface.
    STDMETHOD_(ULONG, AddRef)() override;
    STDMETHOD_(ULONG, Release)() override;
    STDMETHOD(QueryInterface)(REFIID riid, void** ppv) override;

    // IRdpDesktopSessionEventHandler interface.
    STDMETHOD(OnRdpConnected)() override;
    STDMETHOD(OnRdpClosed)() override;

   private:
    ULONG ref_count_;

    // Points to the desktop session object receiving OnRdpXxx() notifications.
    base::WeakPtr<RdpSession> desktop_session_;

    // This class must be used on a single thread.
    base::ThreadChecker thread_checker_;
  };

  // Examines the system settings required to establish an RDP session.
  // This method returns false if the values are retrieved and any of them would
  // prevent us from creating an RDP connection.
  bool VerifyRdpSettings();

  // Retrieves a DWORD value from the registry.  Returns true on success.
  bool RetrieveDwordRegistryValue(const wchar_t* key_name,
                                  const wchar_t* value_name,
                                  DWORD* value);

  // Used to create an RDP desktop session.
  Microsoft::WRL::ComPtr<IRdpDesktopSession> rdp_desktop_session_;

  // Used to match |rdp_desktop_session_| with the session it is attached to.
  std::string terminal_id_;

  base::WeakPtrFactory<RdpSession> weak_factory_{this};
};

ConsoleSession::ConsoleSession(
    scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
    scoped_refptr<AutoThreadTaskRunner> io_task_runner,
    DaemonProcess* daemon_process,
    int id,
    WtsTerminalMonitor* monitor)
    : DesktopSessionWin(caller_task_runner,
                        io_task_runner,
                        daemon_process,
                        id,
                        monitor) {
  StartMonitoring(WtsTerminalMonitor::kConsole);
}

ConsoleSession::~ConsoleSession() {}

void ConsoleSession::SetScreenResolution(const ScreenResolution& resolution) {
  // Do nothing. The screen resolution of the console session is controlled by
  // the DesktopSessionAgent instance running in that session.
  DCHECK(caller_task_runner()->BelongsToCurrentThread());
}

void ConsoleSession::InjectSas() {
  DCHECK(caller_task_runner()->BelongsToCurrentThread());

  if (!sas_injector_) {
    sas_injector_ = SasInjector::Create();
  }
  if (!sas_injector_->InjectSas()) {
    LOG(ERROR) << "Failed to inject Secure Attention Sequence.";
  }
}

RdpSession::RdpSession(scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
                       scoped_refptr<AutoThreadTaskRunner> io_task_runner,
                       DaemonProcess* daemon_process,
                       int id,
                       WtsTerminalMonitor* monitor)
    : DesktopSessionWin(caller_task_runner,
                        io_task_runner,
                        daemon_process,
                        id,
                        monitor) {}

RdpSession::~RdpSession() {}

bool RdpSession::Initialize(const ScreenResolution& resolution) {
  DCHECK(caller_task_runner()->BelongsToCurrentThread());

  if (!VerifyRdpSettings()) {
    LOG(ERROR) << "Could not create an RDP session due to invalid settings.";
    return false;
  }

  // Create the RDP wrapper object.
  HRESULT result =
      ::CoCreateInstance(__uuidof(RdpDesktopSession), nullptr, CLSCTX_ALL,
                         IID_PPV_ARGS(&rdp_desktop_session_));
  if (FAILED(result)) {
    LOG(ERROR) << "Failed to create RdpSession object, 0x" << std::hex << result
               << std::dec << ".";
    return false;
  }

  ScreenResolution local_resolution = resolution;

  // If the screen resolution is not specified, use the default screen
  // resolution.
  if (local_resolution.IsEmpty()) {
    local_resolution = ScreenResolution(
        webrtc::DesktopSize(kDefaultRdpScreenWidth, kDefaultRdpScreenHeight),
        webrtc::DesktopVector(kDefaultRdpDpi, kDefaultRdpDpi));
  }

  // Get the screen dimensions using the default DPI for the RDP client window.
  webrtc::DesktopSize host_size = local_resolution.ScaleDimensionsToDpi(
      webrtc::DesktopVector(kDefaultRdpDpi, kDefaultRdpDpi));

  // Make sure that the host resolution is within the limits supported by RDP.
  host_size = GetBoundedRdpDesktopSize(host_size.width(), host_size.height());

  // Read the port number used by RDP.
  DWORD server_port = kDefaultRdpPort;
  if (RetrieveDwordRegistryValue(kRdpTcpSettingsKeyName, kRdpPortValueName,
                                 &server_port) &&
      server_port > 65535) {
    LOG(ERROR) << "Invalid RDP port specified: " << server_port;
    return false;
  }

  // Create an RDP session.
  Microsoft::WRL::ComPtr<IRdpDesktopSessionEventHandler> event_handler(
      new EventHandler(weak_factory_.GetWeakPtr()));
  terminal_id_ = base::Uuid::GenerateRandomV4().AsLowercaseString();
  base::win::ScopedBstr terminal_id(base::UTF8ToWide(terminal_id_));
  result = rdp_desktop_session_->Connect(
      host_size.width(), host_size.height(), kDefaultRdpDpi, kDefaultRdpDpi,
      terminal_id.Get(), server_port, event_handler.Get());
  if (FAILED(result)) {
    LOG(ERROR) << "RdpSession::Create() failed, 0x" << std::hex << result
               << std::dec << ".";
    return false;
  }

  return true;
}

void RdpSession::OnRdpConnected() {
  DCHECK(caller_task_runner()->BelongsToCurrentThread());

  StopMonitoring();
  StartMonitoring(terminal_id_);
}

void RdpSession::OnRdpClosed() {
  DCHECK(caller_task_runner()->BelongsToCurrentThread());

  TerminateSession();
}

void RdpSession::SetScreenResolution(const ScreenResolution& resolution) {
  DCHECK(caller_task_runner()->BelongsToCurrentThread());
  DCHECK(!resolution.IsEmpty());

  webrtc::DesktopSize bounded_size = GetBoundedRdpDesktopSize(
      resolution.dimensions().width(), resolution.dimensions().height());

  rdp_desktop_session_->ChangeResolution(
      bounded_size.width(), bounded_size.height(), resolution.dpi().x(),
      resolution.dpi().y());
}

void RdpSession::InjectSas() {
  DCHECK(caller_task_runner()->BelongsToCurrentThread());

  rdp_desktop_session_->InjectSas();
}

bool RdpSession::VerifyRdpSettings() {
  // Verify RDP connections are enabled.
  DWORD deny_ts_connections_flag = 0;
  if (RetrieveDwordRegistryValue(kRdpSettingsKeyName,
                                 kDenyTsConnectionsValueName,
                                 &deny_ts_connections_flag) &&
      deny_ts_connections_flag == kRdpConnectionsDisabled) {
    LOG(ERROR) << "RDP Connections must be enabled.";
    return false;
  }

  // Verify Network Level Authentication is disabled.
  DWORD network_level_auth_flag = 0;
  if (RetrieveDwordRegistryValue(kRdpTcpSettingsKeyName,
                                 kNetworkLevelAuthValueName,
                                 &network_level_auth_flag) &&
      network_level_auth_flag == kNetworkLevelAuthEnabled) {
    LOG(ERROR) << "Network Level Authentication for RDP must be disabled.";
    return false;
  }

  // Verify Security Layer is not set to TLS.  It can be either of the other two
  // values, but forcing TLS will prevent us from establishing a connection.
  DWORD security_layer_flag = 0;
  if (RetrieveDwordRegistryValue(kRdpTcpSettingsKeyName,
                                 kSecurityLayerValueName,
                                 &security_layer_flag) &&
      security_layer_flag == kSecurityLayerTlsRequired) {
    LOG(ERROR) << "RDP SecurityLayer must not be set to TLS.";
    return false;
  }

  return true;
}

bool RdpSession::RetrieveDwordRegistryValue(const wchar_t* key_name,
                                            const wchar_t* value_name,
                                            DWORD* value) {
  DCHECK(key_name);
  DCHECK(value_name);
  DCHECK(value);

  base::win::RegKey key(HKEY_LOCAL_MACHINE, key_name, KEY_READ);
  if (!key.Valid()) {
    LOG(WARNING) << "Failed to open key: " << key_name;
    return false;
  }

  if (key.ReadValueDW(value_name, value) != ERROR_SUCCESS) {
    LOG(WARNING) << "Failed to read registry value: " << value_name;
    return false;
  }

  return true;
}

RdpSession::EventHandler::EventHandler(
    base::WeakPtr<RdpSession> desktop_session)
    : ref_count_(0), desktop_session_(desktop_session) {}

RdpSession::EventHandler::~EventHandler() {
  DCHECK(thread_checker_.CalledOnValidThread());

  if (desktop_session_) {
    desktop_session_->OnRdpClosed();
  }
}

ULONG STDMETHODCALLTYPE RdpSession::EventHandler::AddRef() {
  DCHECK(thread_checker_.CalledOnValidThread());

  return ++ref_count_;
}

ULONG STDMETHODCALLTYPE RdpSession::EventHandler::Release() {
  DCHECK(thread_checker_.CalledOnValidThread());

  if (--ref_count_ == 0) {
    delete this;
    return 0;
  }

  return ref_count_;
}

STDMETHODIMP
RdpSession::EventHandler::QueryInterface(REFIID riid, void** ppv) {
  DCHECK(thread_checker_.CalledOnValidThread());

  if (riid == IID_IUnknown || riid == IID_IRdpDesktopSessionEventHandler) {
    *ppv = static_cast<IRdpDesktopSessionEventHandler*>(this);
    AddRef();
    return S_OK;
  }

  *ppv = nullptr;
  return E_NOINTERFACE;
}

STDMETHODIMP RdpSession::EventHandler::OnRdpConnected() {
  DCHECK(thread_checker_.CalledOnValidThread());

  if (desktop_session_) {
    desktop_session_->OnRdpConnected();
  }

  return S_OK;
}

STDMETHODIMP RdpSession::EventHandler::OnRdpClosed() {
  DCHECK(thread_checker_.CalledOnValidThread());

  if (!desktop_session_) {
    return S_OK;
  }

  base::WeakPtr<RdpSession> desktop_session = desktop_session_;
  desktop_session_.reset();
  desktop_session->OnRdpClosed();
  return S_OK;
}

}  // namespace

// static
std::unique_ptr<DesktopSession> DesktopSessionWin::CreateForConsole(
    scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
    scoped_refptr<AutoThreadTaskRunner> io_task_runner,
    DaemonProcess* daemon_process,
    int id,
    const ScreenResolution& resolution) {
  return std::make_unique<ConsoleSession>(caller_task_runner, io_task_runner,
                                          daemon_process, id,
                                          HostService::GetInstance());
}

// static
std::unique_ptr<DesktopSession> DesktopSessionWin::CreateForVirtualTerminal(
    scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
    scoped_refptr<AutoThreadTaskRunner> io_task_runner,
    DaemonProcess* daemon_process,
    int id,
    const ScreenResolution& resolution) {
  std::unique_ptr<RdpSession> session(
      new RdpSession(caller_task_runner, io_task_runner, daemon_process, id,
                     HostService::GetInstance()));
  if (!session->Initialize(resolution)) {
    return nullptr;
  }

  return std::move(session);
}

DesktopSessionWin::DesktopSessionWin(
    scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
    scoped_refptr<AutoThreadTaskRunner> io_task_runner,
    DaemonProcess* daemon_process,
    int id,
    WtsTerminalMonitor* monitor)
    : DesktopSession(daemon_process, id),
      caller_task_runner_(caller_task_runner),
      io_task_runner_(io_task_runner),
      monitor_(monitor),
      monitoring_notifications_(false) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  ReportElapsedTime("created");
}

DesktopSessionWin::~DesktopSessionWin() {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  StopMonitoring();
}

void DesktopSessionWin::OnSessionAttachTimeout() {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  LOG(ERROR) << "Session attach notification didn't arrived within "
             << kSessionAttachTimeoutSeconds << " seconds.";
  TerminateSession();
}

void DesktopSessionWin::StartMonitoring(const std::string& terminal_id) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  DCHECK(!monitoring_notifications_);
  DCHECK(!session_attach_timer_.IsRunning());

  ReportElapsedTime("started monitoring");

  session_attach_timer_.Start(FROM_HERE,
                              base::Seconds(kSessionAttachTimeoutSeconds), this,
                              &DesktopSessionWin::OnSessionAttachTimeout);

  monitoring_notifications_ = true;
  monitor_->AddWtsTerminalObserver(terminal_id, this);
}

void DesktopSessionWin::StopMonitoring() {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  if (monitoring_notifications_) {
    ReportElapsedTime("stopped monitoring");

    monitoring_notifications_ = false;
    monitor_->RemoveWtsTerminalObserver(this);
  }

  session_attach_timer_.Stop();
  OnSessionDetached();
}

void DesktopSessionWin::TerminateSession() {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  StopMonitoring();

  // This call will delete |this| so it should be at the very end of the method.
  daemon_process()->CloseDesktopSession(id());
}

void DesktopSessionWin::OnChannelConnected(int32_t peer_pid) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  ReportElapsedTime("channel connected");

  VLOG(1) << "IPC: daemon <- desktop (" << peer_pid << ")";
}

void DesktopSessionWin::OnPermanentError(int exit_code) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  TerminateSession();
}

void DesktopSessionWin::OnWorkerProcessStopped() {}

void DesktopSessionWin::OnAssociatedInterfaceRequest(
    const std::string& interface_name,
    mojo::ScopedInterfaceEndpointHandle handle) {
  if (interface_name == mojom::DesktopSessionRequestHandler::Name_) {
    if (desktop_session_request_handler_.is_bound()) {
      LOG(ERROR) << "Receiver already bound for associated interface: "
                 << mojom::DesktopSessionRequestHandler::Name_;
      CrashDesktopProcess(FROM_HERE);
    }

    mojo::PendingAssociatedReceiver<mojom::DesktopSessionRequestHandler>
        pending_receiver(std::move(handle));
    desktop_session_request_handler_.Bind(std::move(pending_receiver));

    // Reset the receiver on disconnect so |desktop_session_request_handler_|
    // can be re-bound if |launcher_| spawns a new desktop process.
    desktop_session_request_handler_.reset_on_disconnect();
  } else {
    LOG(ERROR) << "Unknown associated interface requested: " << interface_name
               << ", crashing the desktop process";
    CrashDesktopProcess(FROM_HERE);
  }
}

void DesktopSessionWin::OnSessionAttached(uint32_t session_id) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());
  DCHECK(!launcher_);
  DCHECK(monitoring_notifications_);

  ReportElapsedTime("attached");

  // Get the name of the executable to run. `kDesktopBinaryName` specifies
  // uiAccess="true" in its manifest.  Prefer kDesktopBinaryName but fall back
  // to kHostBinaryName if there is a problem loading it.
  base::FilePath desktop_binary;
  bool result = GetInstalledBinaryPath(kDesktopBinaryName, &desktop_binary);

  if (!result || !IsBinaryTrusted(desktop_binary)) {
    result = GetInstalledBinaryPath(kHostBinaryName, &desktop_binary);
  }

  if (!result) {
    TerminateSession();
    return;
  }

  session_attach_timer_.Stop();

  std::unique_ptr<base::CommandLine> target(
      new base::CommandLine(desktop_binary));
  target->AppendSwitchASCII(kProcessTypeSwitchName, kProcessTypeDesktop);
  // Copy the command line switches enabling verbose logging.
  target->CopySwitchesFrom(*base::CommandLine::ForCurrentProcess(),
                           kCopiedSwitchNames);

  // Create a delegate capable of launching a process in a different session.
  // Launch elevated to enable injection of Alt+Tab and Ctrl+Alt+Del.
  std::unique_ptr<WtsSessionProcessDelegate> delegate(
      new WtsSessionProcessDelegate(
          io_task_runner_, std::move(target), /*launch_elevated=*/true,
          base::WideToUTF8(kDaemonIpcSecurityDescriptor)));
  if (!delegate->Initialize(session_id)) {
    TerminateSession();
    return;
  }

  // Create a launcher for the desktop process, using the per-session delegate.
  launcher_ =
      std::make_unique<WorkerProcessLauncher>(std::move(delegate), this);
  session_id_ = session_id;
}

void DesktopSessionWin::OnSessionDetached() {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  launcher_.reset();
  desktop_session_request_handler_.reset();
  session_id_ = UINT32_MAX;

  if (monitoring_notifications_) {
    ReportElapsedTime("detached");

    session_attach_timer_.Start(
        FROM_HERE, base::Seconds(kSessionAttachTimeoutSeconds), this,
        &DesktopSessionWin::OnSessionAttachTimeout);
  }
}

void DesktopSessionWin::ConnectDesktopChannel(
    mojo::ScopedMessagePipeHandle desktop_pipe) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  if (!daemon_process()->OnDesktopSessionAgentAttached(
          id(), session_id_, std::move(desktop_pipe))) {
    CrashDesktopProcess(FROM_HERE);
  }
}

void DesktopSessionWin::InjectSecureAttentionSequence() {
  InjectSas();
}

void DesktopSessionWin::CrashNetworkProcess() {
  daemon_process()->CrashNetworkProcess(FROM_HERE);
}

void DesktopSessionWin::CrashDesktopProcess(const base::Location& location) {
  DCHECK(caller_task_runner_->BelongsToCurrentThread());

  launcher_->Crash(location);
}

void DesktopSessionWin::ReportElapsedTime(const std::string& event) {
  base::Time now = base::Time::Now();

  std::string passed;
  if (!last_timestamp_.is_null()) {
    passed = base::StringPrintf(", %.2fs passed",
                                (now - last_timestamp_).InSecondsF());
  }

  VLOG(1) << base::StringPrintf(
      "session(%d): %s at %s%s", id(), event.c_str(),
      base::UnlocalizedTimeFormatWithPattern(now, "HH:mm:ss.SSS").c_str(),
      passed.c_str());

  last_timestamp_ = now;
}

}  // namespace remoting