| // 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/bind.h" |
| #include "base/callback.h" |
| #include "base/check_op.h" |
| #include "base/containers/contains.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/posix/eintr_wrapper.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task/thread_pool.h" |
| #include "base/threading/thread_task_runner_handle.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::ThreadTaskRunnerHandle::Get())); |
| } |
| |
| 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) { |
| 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) { |
| 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))); |
| } |
| |
| 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))); |
| } |
| |
| void FakePermissionBrokerClient::AddTcpDenyRule(uint16_t port, |
| const std::string& interface) { |
| tcp_deny_rule_set_.insert(std::make_pair(port, interface)); |
| } |
| |
| void FakePermissionBrokerClient::AddUdpDenyRule(uint16_t port, |
| const std::string& interface) { |
| udp_deny_rule_set_.insert(std::make_pair(port, interface)); |
| } |
| |
| 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); |
| } |
| |
| 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 |