chromium/remoting/host/file_transfer/file_chooser_win.cc

// Copyright 2019 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/file_transfer/file_chooser.h"

#include <windows.h>

#include <wtsapi32.h>

#include <cstdlib>
#include <utility>
#include <vector>

#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/process/launch.h"
#include "base/task/sequenced_task_runner.h"
#include "base/win/object_watcher.h"
#include "base/win/scoped_handle.h"
#include "mojo/public/cpp/bindings/message.h"
#include "remoting/host/base/host_exit_codes.h"
#include "remoting/host/base/switches.h"
#include "remoting/host/file_transfer/file_chooser_common_win.h"
#include "remoting/host/mojom/desktop_session.mojom.h"

namespace remoting {

namespace {

using base::win::ScopedHandle;
using protocol::FileTransferResult;
using protocol::MakeFileTransferError;

FileTransferResult<ScopedHandle> GetCurrentUserToken(base::Location from_here) {
  HANDLE user_token = nullptr;
  if (!WTSQueryUserToken(WTS_CURRENT_SESSION, &user_token)) {
    PLOG(ERROR) << "Failed to get current user token";
    return MakeFileTransferError(
        from_here,
        GetLastError() == ERROR_NO_TOKEN
            ? protocol::FileTransfer_Error_Type_NOT_LOGGED_IN
            : protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR,
        GetLastError());
  }
  return ScopedHandle(user_token);
}

FileTransferResult<std::pair<ScopedHandle, ScopedHandle>> MakePipe(
    base::Location from_here) {
  HANDLE pipe_read = nullptr;
  HANDLE pipe_write = nullptr;

  // Create the pipe for the child process's STDOUT.
  // LaunchProcess will take care of making the write end inheritable.
  if (!CreatePipe(&pipe_read, &pipe_write, nullptr,
                  kFileChooserPipeBufferSize)) {
    PLOG(ERROR) << "Failed to create pipe";
    return MakeFileTransferError(
        from_here, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR,
        GetLastError());
  }

  ScopedHandle scoped_pipe_read(pipe_read);
  ScopedHandle scoped_pipe_write(pipe_write);

  // If the pipe buffer ends up smaller than expected, we want to fail rather
  // hang, so set the pipe to be non-blocking. (Note that despite the name,
  // SetNamedPipeHandleState is documented to be usable with anonymous pipes as
  // well.
  DWORD mode = PIPE_READMODE_BYTE | PIPE_NOWAIT;
  if (!SetNamedPipeHandleState(pipe_write, &mode, nullptr, nullptr)) {
    PLOG(ERROR) << "Failed to set pipe to non-blocking mode";
    return MakeFileTransferError(
        from_here, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR,
        GetLastError());
  }

  return std::make_pair(std::move(scoped_pipe_read),
                        std::move(scoped_pipe_write));
}

FileTransferResult<base::FilePath> GetExePath(base::Location from_here) {
  // The remoting_desktop.exe binary (where this code runs) has extra manifest
  // flags (uiAccess and requireAdministrator) that are undesirable for the
  // file-chooser child process, so remoting_host.exe is used instead.
  base::FilePath path;
  if (!base::PathService::Get(base::DIR_EXE, &path)) {
    LOG(ERROR) << "Failed to get executable path.";
    return MakeFileTransferError(
        from_here, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR);
  }
  return path.AppendASCII("remoting_host.exe");
}

class FileChooserWindows : public FileChooser,
                           public base::win::ObjectWatcher::Delegate {
 public:
  FileChooserWindows(scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
                     ResultCallback callback);

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

  ~FileChooserWindows() override;

  // FileChooser implementation.
  void Show() override;

  // ObjectWatcher::Delegate implementation.
  void OnObjectSignaled(HANDLE object) override;

 private:
  FileTransferResult<absl::monostate> LaunchChooserProcess();

  ResultCallback callback_;
  base::Process process_;
  base::win::ObjectWatcher object_watcher_;
  ScopedHandle pipe_read_;
};

FileChooserWindows::FileChooserWindows(
    scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
    ResultCallback callback)
    : callback_(std::move(callback)) {}

void FileChooserWindows::Show() {
  FileTransferResult<absl::monostate> result = LaunchChooserProcess();

  if (!result) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback_), std::move(result.error())));
  }
}

void FileChooserWindows::OnObjectSignaled(HANDLE object) {
  int exit_code;
  // Shouldn't block since process handle is already signaled.
  if (!process_.WaitForExit(&exit_code)) {
    // Currently, WaitForExit returns immediately if GetExitCodeProcess fails,
    // so GetLastError should still be relevant.
    PLOG(ERROR) << "Failed to check exit status";
    process_.Close();
    std::move(callback_).Run(MakeFileTransferError(
        FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
    return;
  }
  if (exit_code != ERROR_SUCCESS) {
    LOG(ERROR) << "Error running dialog process:" << exit_code;
    process_.Close();
    std::move(callback_).Run(MakeFileTransferError(
        FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
    return;
  }
  process_.Close();

  std::vector<uint8_t> response_bytes(kFileChooserPipeBufferSize);
  DWORD bytes_read;
  if (!PeekNamedPipe(pipe_read_.Get(), response_bytes.data(),
                     response_bytes.size(), &bytes_read, nullptr, nullptr)) {
    PLOG(ERROR) << "Failed to read response from pipe";
    std::move(callback_).Run(MakeFileTransferError(
        FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR,
        GetLastError()));
    return;
  }

  mojo::Message serialized_message(
      base::span<uint8_t>(response_bytes.begin(), bytes_read),
      base::span<mojo::ScopedHandle>());

  FileChooser::Result result;
  if (!mojom::FileChooserResult::DeserializeFromMessage(
          std::move(serialized_message), &result)) {
    LOG(ERROR) << "Failed to deserialize response.";
    std::move(callback_).Run(MakeFileTransferError(
        FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
    return;
  }

  std::move(callback_).Run(std::move(result));
}

FileTransferResult<absl::monostate> FileChooserWindows::LaunchChooserProcess() {
  base::LaunchOptions launch_options;

  FileTransferResult<ScopedHandle> current_user =
      GetCurrentUserToken(FROM_HERE);
  if (!current_user) {
    return current_user.error();
  }
  launch_options.as_user = current_user->Get();

  FileTransferResult<std::pair<ScopedHandle, ScopedHandle>> pipe_pair =
      MakePipe(FROM_HERE);
  if (!pipe_pair) {
    return pipe_pair.error();
  }
  pipe_read_ = std::move(pipe_pair->first);
  launch_options.handles_to_inherit.push_back(pipe_pair->second.Get());
  launch_options.stdin_handle = INVALID_HANDLE_VALUE;
  launch_options.stdout_handle = pipe_pair->second.Get();
  launch_options.stderr_handle = INVALID_HANDLE_VALUE;

  FileTransferResult<base::FilePath> exe_path = GetExePath(FROM_HERE);
  if (!exe_path) {
    return exe_path.error();
  }
  base::CommandLine command_line(*exe_path);
  command_line.AppendSwitchASCII(kProcessTypeSwitchName,
                                 kProcessTypeFileChooser);

  process_ = base::LaunchProcess(command_line, launch_options);
  if (!process_.IsValid()) {
    LOG(ERROR) << "Failed to launch process.";
    return MakeFileTransferError(
        FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR);
  }

  if (!object_watcher_.StartWatchingOnce(process_.Handle(), this)) {
    LOG(ERROR) << "Failed to wait for file chooser";
    process_.Terminate(0, false);
    process_.Close();
    return MakeFileTransferError(
        FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR);
  }

  return kSuccessTag;
}

FileChooserWindows::~FileChooserWindows() {
  if (process_.IsValid()) {
    process_.Terminate(0, false);
  }
}

}  // namespace

std::unique_ptr<FileChooser> FileChooser::Create(
    scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
    ResultCallback callback) {
  return std::make_unique<FileChooserWindows>(std::move(ui_task_runner),
                                              std::move(callback));
}

}  // namespace remoting