chromium/chromeos/dbus/permission_broker/fake_permission_broker_client.cc

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

#include "chromeos/dbus/permission_broker/fake_permission_broker_client.h"

#include <fcntl.h>
#include <linux/usbdevice_fs.h>
#include <stdint.h>
#include <sys/ioctl.h>

#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/unguessable_token.h"

namespace chromeos {

namespace {

constexpr char kOpenFailedError[] = "open_failed";
constexpr char kDupFailedError[] = "dup_failed";
constexpr char kWatchLifelineFdFailedError[] = "watch_lifeline_fd_failed";

FakePermissionBrokerClient* g_instance = nullptr;

// So that real devices can be accessed by tests and "Chromium OS on Linux" this
// function implements a simplified version of the method implemented by the
// permission broker by opening the path specified and returning the resulting
// file descriptor.
void OpenPath(const std::string& path,
              PermissionBrokerClient::OpenPathCallback callback,
              PermissionBrokerClient::ErrorCallback error_callback,
              scoped_refptr<base::TaskRunner> task_runner) {
  base::ScopedFD fd(HANDLE_EINTR(open(path.c_str(), O_RDWR)));
  if (!fd.is_valid()) {
    int error_code = logging::GetLastSystemErrorCode();
    task_runner->PostTask(
        FROM_HERE,
        base::BindOnce(
            std::move(error_callback), kOpenFailedError,
            base::StringPrintf(
                "Failed to open '%s': %s", path.c_str(),
                logging::SystemErrorCodeToString(error_code).c_str())));
    return;
  }

  task_runner->PostTask(FROM_HERE,
                        base::BindOnce(std::move(callback), std::move(fd)));
}

bool DisconnectInterface(const std::string& path, uint8_t iface_num) {
  base::ScopedFD fd(HANDLE_EINTR(open(path.c_str(), O_RDWR)));
  if (!fd.is_valid()) {
    PLOG(ERROR) << "Failed to open path " << path;
    return false;
  }

  struct usbdevfs_ioctl dio = {};
  dio.ifno = iface_num;
  dio.ioctl_code = USBDEVFS_DISCONNECT;
  dio.data = nullptr;
  int rc = HANDLE_EINTR(ioctl(fd.get(), USBDEVFS_IOCTL, &dio));
  if (rc < 0) {
    PLOG(ERROR) << "Failed to disconnect interface "
                << static_cast<int>(iface_num) << " on path " << path;
    return false;
  }
  return true;
}

bool ConnectInterface(const std::string& path, uint8_t iface_num) {
  base::ScopedFD fd(HANDLE_EINTR(open(path.c_str(), O_RDWR)));
  if (!fd.is_valid()) {
    PLOG(ERROR) << "Failed to open path " << path;
    return false;
  }

  struct usbdevfs_ioctl dio = {};
  dio.ifno = iface_num;
  dio.ioctl_code = USBDEVFS_CONNECT;
  dio.data = nullptr;
  int rc = HANDLE_EINTR(ioctl(fd.get(), USBDEVFS_IOCTL, &dio));
  if (rc < 0) {
    PLOG(ERROR) << "Failed to connect interface " << static_cast<int>(iface_num)
                << " on path " << path;
    return false;
  }
  return true;
}

}  // namespace

FakePermissionBrokerClient::FakePermissionBrokerClient() {
  DCHECK(!g_instance);
  g_instance = this;
}

FakePermissionBrokerClient::~FakePermissionBrokerClient() {
  DCHECK_EQ(this, g_instance);
  g_instance = nullptr;
}

// static
FakePermissionBrokerClient* FakePermissionBrokerClient::Get() {
  DCHECK(g_instance);
  return g_instance;
}

void FakePermissionBrokerClient::CheckPathAccess(const std::string& path,
                                                 ResultCallback callback) {
  std::move(callback).Run(true);
}

void FakePermissionBrokerClient::OpenPath(const std::string& path,
                                          OpenPathCallback callback,
                                          ErrorCallback error_callback) {
  base::ThreadPool::PostTask(
      FROM_HERE,
      {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(&chromeos::OpenPath, path, std::move(callback),
                     std::move(error_callback),
                     base::SingleThreadTaskRunner::GetCurrentDefault()));
}

void FakePermissionBrokerClient::ClaimDevicePath(
    const std::string& path,
    uint32_t allowed_interfaces_mask,
    int lifeline_fd,
    OpenPathCallback callback,
    ErrorCallback error_callback) {
  OpenPath(path, std::move(callback), std::move(error_callback));
}

void FakePermissionBrokerClient::OpenPathAndRegisterClient(
    const std::string& path,
    uint32_t allowed_interfaces_mask,
    int lifeline_fd,
    OpenPathAndRegisterClientCallback callback,
    ErrorCallback error_callback) {
  std::string client_id;
  do {
    client_id = base::UnguessableToken::Create().ToString();
  } while (base::Contains(clients_, client_id));

  base::ScopedFD dup_lifeline_fd(HANDLE_EINTR(dup(lifeline_fd)));
  if (!dup_lifeline_fd.is_valid()) {
    int error_code = logging::GetLastSystemErrorCode();
    std::move(error_callback)
        .Run(kDupFailedError,
             base::StringPrintf(
                 "Failed to dup lifeline fd %d: %s", lifeline_fd,
                 logging::SystemErrorCodeToString(error_code).c_str()));
    return;
  }

  auto controller = base::FileDescriptorWatcher::WatchReadable(
      dup_lifeline_fd.get(),
      base::BindRepeating(&FakePermissionBrokerClient::HandleClosedClient,
                          weak_factory_.GetWeakPtr(), client_id));
  if (!controller) {
    std::move(error_callback)
        .Run(kWatchLifelineFdFailedError,
             base::StringPrintf("Failed to watch dup lifeline fd %d",
                                dup_lifeline_fd.get()));
    return;
  }
  clients_.emplace(client_id, UsbInterfaces(path, std::move(dup_lifeline_fd),
                                            std::move(controller)));

  // No concern of OpenPath failure causing orphan client record here, as the
  // inserted client's record will still be removed when requester does error
  // handling and closes the lifeline_fd.
  OpenPath(path, base::BindOnce(std::move(callback), client_id),
           std::move(error_callback));
}

void FakePermissionBrokerClient::DetachInterface(const std::string& client_id,
                                                 uint8_t iface_num,
                                                 ResultCallback callback) {
  auto client_it = clients_.find(client_id);
  if (client_it == clients_.end()) {
    LOG(ERROR) << "Unknown client_id: " << client_id;
    std::move(callback).Run(false);
    return;
  }

  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE,
      {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(&chromeos::DisconnectInterface, client_it->second.path,
                     iface_num),
      std::move(callback));
}

void FakePermissionBrokerClient::ReattachInterface(const std::string& client_id,
                                                   uint8_t iface_num,
                                                   ResultCallback callback) {
  auto client_it = clients_.find(client_id);
  if (client_it == clients_.end()) {
    LOG(ERROR) << "Unknown client_id: " << client_id;
    std::move(callback).Run(false);
    return;
  }

  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE,
      {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(&chromeos::ConnectInterface, client_it->second.path,
                     iface_num),
      std::move(callback));
}

void FakePermissionBrokerClient::RequestTcpPortAccess(
    uint16_t port,
    const std::string& interface,
    int lifeline_fd,
    ResultCallback callback) {
  if (tcp_deny_all_) {
    std::move(callback).Run(false);
    return;
  }
  std::move(callback).Run(
      RequestPortImpl(port, interface, tcp_deny_rule_set_, &tcp_hole_set_));
}

void FakePermissionBrokerClient::RequestUdpPortAccess(
    uint16_t port,
    const std::string& interface,
    int lifeline_fd,
    ResultCallback callback) {
  if (udp_deny_all_) {
    std::move(callback).Run(false);
    return;
  }
  std::move(callback).Run(
      RequestPortImpl(port, interface, udp_deny_rule_set_, &udp_hole_set_));
}

void FakePermissionBrokerClient::ReleaseTcpPort(uint16_t port,
                                                const std::string& interface,
                                                ResultCallback callback) {
  std::move(callback).Run(tcp_hole_set_.erase(std::make_pair(port, interface)));
  if (delegate_) {
    delegate_->OnTcpPortReleased(port, interface);
  }
}

void FakePermissionBrokerClient::ReleaseUdpPort(uint16_t port,
                                                const std::string& interface,
                                                ResultCallback callback) {
  std::move(callback).Run(udp_hole_set_.erase(std::make_pair(port, interface)));
  if (delegate_) {
    delegate_->OnUdpPortReleased(port, interface);
  }
}

void FakePermissionBrokerClient::AddTcpDenyRule(uint16_t port,
                                                const std::string& interface) {
  tcp_deny_rule_set_.insert(std::make_pair(port, interface));
}

void FakePermissionBrokerClient::SetTcpDenyAll() {
  tcp_deny_all_ = true;
}

void FakePermissionBrokerClient::AddUdpDenyRule(uint16_t port,
                                                const std::string& interface) {
  udp_deny_rule_set_.insert(std::make_pair(port, interface));
}

void FakePermissionBrokerClient::SetUdpDenyAll() {
  udp_deny_all_ = true;
}

bool FakePermissionBrokerClient::HasTcpHole(uint16_t port,
                                            const std::string& interface) {
  auto rule = std::make_pair(port, interface);
  return tcp_hole_set_.find(rule) != tcp_hole_set_.end();
}

bool FakePermissionBrokerClient::HasUdpHole(uint16_t port,
                                            const std::string& interface) {
  auto rule = std::make_pair(port, interface);
  return udp_hole_set_.find(rule) != udp_hole_set_.end();
}

bool FakePermissionBrokerClient::HasTcpPortForward(
    uint16_t port,
    const std::string& interface) {
  auto rule = std::make_pair(port, interface);
  return tcp_forwarding_set_.find(rule) != tcp_forwarding_set_.end();
}

bool FakePermissionBrokerClient::HasUdpPortForward(
    uint16_t port,
    const std::string& interface) {
  auto rule = std::make_pair(port, interface);
  return udp_forwarding_set_.find(rule) != udp_forwarding_set_.end();
}

void FakePermissionBrokerClient::RequestTcpPortForward(
    uint16_t in_port,
    const std::string& in_interface,
    const std::string& dst_ip,
    uint16_t dst_port,
    int lifeline_fd,
    ResultCallback callback) {
  // TODO(matterchen): Increase logic for adding duplicate ports.
  auto rule = std::make_pair(in_port, in_interface);
  tcp_forwarding_set_.insert(rule);
  std::move(callback).Run(true);
}

void FakePermissionBrokerClient::RequestUdpPortForward(
    uint16_t in_port,
    const std::string& in_interface,
    const std::string& dst_ip,
    uint16_t dst_port,
    int lifeline_fd,
    ResultCallback callback) {
  auto rule = std::make_pair(in_port, in_interface);
  udp_forwarding_set_.insert(rule);
  std::move(callback).Run(true);
}

void FakePermissionBrokerClient::ReleaseTcpPortForward(
    uint16_t in_port,
    const std::string& in_interface,
    ResultCallback callback) {
  auto rule = std::make_pair(in_port, in_interface);
  tcp_forwarding_set_.erase(rule);
  std::move(callback).Run(true);
}

void FakePermissionBrokerClient::ReleaseUdpPortForward(
    uint16_t in_port,
    const std::string& in_interface,
    ResultCallback callback) {
  auto rule = std::make_pair(in_port, in_interface);
  udp_forwarding_set_.erase(rule);
  std::move(callback).Run(true);
}

void FakePermissionBrokerClient::AttachDelegate(Delegate* delegate) {
  delegate_ = delegate;
}

bool FakePermissionBrokerClient::RequestPortImpl(uint16_t port,
                                                 const std::string& interface,
                                                 const RuleSet& deny_rule_set,
                                                 RuleSet* hole_set) {
  auto rule = std::make_pair(port, interface);

  // If there is already a hole, returns true.
  if (hole_set->find(rule) != hole_set->end())
    return true;

  // If it is denied to make a hole, returns false.
  if (deny_rule_set.find(rule) != deny_rule_set.end())
    return false;

  hole_set->insert(rule);
  return true;
}

FakePermissionBrokerClient::UsbInterfaces::UsbInterfaces(
    const std::string& path,
    base::ScopedFD lifeline_fd,
    std::unique_ptr<base::FileDescriptorWatcher::Controller> controller)
    : path(std::move(path)),
      lifeline_fd(std::move(lifeline_fd)),
      controller(std::move(controller)) {}

FakePermissionBrokerClient::UsbInterfaces::~UsbInterfaces() = default;

FakePermissionBrokerClient::UsbInterfaces::UsbInterfaces(UsbInterfaces&&) =
    default;
FakePermissionBrokerClient::UsbInterfaces&
FakePermissionBrokerClient::UsbInterfaces::operator=(UsbInterfaces&&) = default;

}  // namespace chromeos