blob: 35f2fc88b5216d744ad5a0f2ed42e2d36df95707 [file] [log] [blame]
// Copyright 2016 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "permission_broker/usb_driver_tracker.h"
#include <errno.h>
#include <fcntl.h>
#include <linux/usbdevice_fs.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string>
#include <utility>
#include <base/bind.h>
#include <base/containers/contains.h>
#include <base/containers/cxx20_erase_vector.h>
#include <base/files/file_descriptor_watcher_posix.h>
#include <base/logging.h>
#include <base/posix/eintr_wrapper.h>
#include <base/strings/string_number_conversions.h>
#include <base/unguessable_token.h>
#include "permission_broker/udev_scopers.h"
namespace permission_broker {
UsbDriverTracker::UsbDriverTracker() = default;
UsbDriverTracker::~UsbDriverTracker() {
CleanUpTracking();
}
void UsbDriverTracker::HandleClosedFd(std::string client_id) {
auto iter = dev_fds_.find(client_id);
if (iter != dev_fds_.end()) {
auto& entry = iter->second;
// Reattaching the kernel driver to the USB interface.
while (!entry.interfaces.empty()) {
uint8_t iface_num = *entry.interfaces.begin();
// This might remove elements in entry.interfaces.
if (!ReattachInterface(client_id, iface_num)) {
LOG(ERROR) << "Failed to reattach interface "
<< static_cast<int>(iface_num) << " for client "
<< client_id;
// Remove the interface from the tracking record even if reattaching
// fails (ex: ioctl() failure) to avoid orphan tracking record as this
// client is being closed.
ClearDetachedInterfaceRecord(client_id, entry.path, iface_num);
}
}
// We are done with the client_id.
dev_fds_.erase(iter);
} else {
LOG(WARNING) << "Untracked USB client " << client_id;
}
}
bool UsbDriverTracker::DetachPathFromKernel(int fd,
const std::string* client_id,
const base::FilePath& path) {
// Use the USB device node major/minor to find the udev entry.
struct stat st;
if (fstat(fd, &st) || !S_ISCHR(st.st_mode)) {
LOG(WARNING) << "Cannot stat " << path << " device id";
return false;
}
ScopedUdevPtr udev(udev_new());
ScopedUdevDevicePtr device(
udev_device_new_from_devnum(udev.get(), 'c', st.st_rdev));
if (!device.get()) {
return false;
}
ScopedUdevEnumeratePtr enumerate(udev_enumerate_new(udev.get()));
udev_enumerate_add_match_parent(enumerate.get(), device.get());
udev_enumerate_scan_devices(enumerate.get());
// Try to find our USB interface nodes, by iterating through all devices
// and extracting our children devices.
bool detached = false;
struct udev_list_entry* entry;
udev_list_entry_foreach(entry,
udev_enumerate_get_list_entry(enumerate.get())) {
const char* entry_path = udev_list_entry_get_name(entry);
ScopedUdevDevicePtr child(
udev_device_new_from_syspath(udev.get(), entry_path));
const char* child_type = udev_device_get_devtype(child.get());
if (!child_type || strcmp(child_type, "usb_interface") != 0) {
continue;
}
const char* driver = udev_device_get_driver(child.get());
if (driver) {
// A kernel driver is using this interface, try to detach it.
const char* iface =
udev_device_get_sysattr_value(child.get(), "bInterfaceNumber");
unsigned iface_num;
if (!iface || !base::StringToUint(iface, &iface_num)) {
detached = false;
continue;
}
detached = true;
if (client_id) {
if (!DetachInterface(*client_id, iface_num)) {
LOG(ERROR) << "Fail to detach interface "
<< static_cast<int>(iface_num) << " for client "
<< client_id;
detached = false;
}
} else {
// This is the case in Permission Broker OpenPath() which doesn't use
// any client tracking.
if (!DisconnectInterface(fd, iface_num)) {
LOG(ERROR) << "Failed to detach interface "
<< static_cast<int>(iface_num) << " with fd " << fd;
detached = false;
}
}
}
}
return detached;
}
std::unique_ptr<base::FileDescriptorWatcher::Controller>
UsbDriverTracker::WatchLifelineFd(const std::string& client_id,
int lifeline_fd) {
return base::FileDescriptorWatcher::WatchReadable(
lifeline_fd,
base::BindRepeating(&UsbDriverTracker::HandleClosedFd,
weak_ptr_factory_.GetWeakPtr(), client_id));
}
std::optional<std::string> UsbDriverTracker::RegisterClient(
int lifeline_fd, const base::FilePath& path) {
// |dup_lifeline_fd| is the duplicated file descriptor of the client's
// lifeline pipe read end. The ownership needs to be transferred to the
// internal tracking structure to keep readable callback registered.
base::ScopedFD fd(HANDLE_EINTR(open(path.value().c_str(), O_RDWR)));
if (!fd.is_valid()) {
PLOG(ERROR) << "Failed to open path " << path;
return std::nullopt;
}
base::ScopedFD dup_lifeline_fd(HANDLE_EINTR(dup(lifeline_fd)));
if (!dup_lifeline_fd.is_valid()) {
PLOG(ERROR) << "Failed to dup lifeline_fd " << lifeline_fd;
return std::nullopt;
}
std::string client_id;
do {
client_id = base::UnguessableToken::Create().ToString();
} while (base::Contains(dev_fds_, client_id));
auto controller = WatchLifelineFd(client_id, dup_lifeline_fd.get());
if (!controller) {
LOG(ERROR) << "Unable to watch lifeline_fd " << dup_lifeline_fd.get()
<< " for client " << client_id;
return std::nullopt;
}
dev_fds_.emplace(client_id,
UsbInterfaces{.path = path,
.controller = std::move(controller),
.interfaces = {},
.fd = std::move(fd),
.lifeline_fd = std::move(dup_lifeline_fd)});
return client_id;
}
bool UsbDriverTracker::DisconnectInterface(int fd, uint8_t iface_num) {
struct usbdevfs_ioctl dio;
dio.ifno = iface_num;
dio.ioctl_code = USBDEVFS_DISCONNECT;
dio.data = nullptr;
int rc = ioctl(fd, USBDEVFS_IOCTL, &dio);
// ENODATA is a benign error code which is when the interface isn't
// associated with any driver.
if (rc < 0 && errno != ENODATA) {
PLOG(ERROR) << "Failed to disconnect interface "
<< static_cast<int>(iface_num) << " with fd " << fd;
return false;
}
return true;
}
bool UsbDriverTracker::ConnectInterface(int fd, uint8_t iface_num) {
struct usbdevfs_ioctl dio;
dio.ifno = iface_num;
dio.ioctl_code = USBDEVFS_CONNECT;
dio.data = nullptr;
int rc = ioctl(fd, USBDEVFS_IOCTL, &dio);
if (rc < 0) {
PLOG(ERROR) << "Failed to connect interface " << static_cast<int>(iface_num)
<< " with fd " << fd;
return false;
}
return true;
}
void UsbDriverTracker::RecordInterfaceDetached(const std::string& client_id,
const base::FilePath& path,
uint8_t iface_num) {
auto client_it = dev_fds_.find(client_id);
if (client_it == dev_fds_.end()) {
LOG(DFATAL) << "Can't find client " << client_id
<< " in the tracking record";
return;
}
if (base::Contains(client_it->second.interfaces, iface_num)) {
LOG(DFATAL) << "Detached interface " << static_cast<int>(iface_num)
<< " on path " << path
<< " has already been recorded by client " << client_id;
return;
}
client_it->second.interfaces.push_back(iface_num);
dev_ifaces_[path][iface_num] = client_id;
}
void UsbDriverTracker::ClearDetachedInterfaceRecord(
const std::string& client_id,
const base::FilePath& path,
uint8_t iface_num) {
auto client_it = dev_fds_.find(client_id);
auto path_it = dev_ifaces_.find(path);
if (client_it == dev_fds_.end()) {
LOG(DFATAL) << "Can't find client " << client_id
<< " in the tracking record";
return;
}
if (path_it == dev_ifaces_.end()) {
LOG(DFATAL) << "Can't find path " << path << " in the tracking record";
return;
}
auto num_erased = base::Erase(client_it->second.interfaces, iface_num);
if (num_erased != 1) {
LOG(DFATAL) << "Unexpected number of erased records " << num_erased
<< " for interface " << static_cast<int>(iface_num)
<< " on path " << path << " for client " << client_id;
}
path_it->second.erase(iface_num);
if (path_it->second.empty()) {
dev_ifaces_.erase(path_it);
}
}
bool UsbDriverTracker::IsClientIdTracked(const std::string& client_id) {
return base::Contains(dev_fds_, client_id);
}
void UsbDriverTracker::CleanUpTracking() {
// Reattach all delegated USB interfaces.
while (!dev_fds_.empty()) {
// This might remove the element.
HandleClosedFd(dev_fds_.begin()->first);
}
}
bool UsbDriverTracker::DetachInterface(const std::string& client_id,
uint8_t iface_num) {
if (!IsClientIdTracked(client_id)) {
LOG(WARNING) << "DetachInterface: Untracked client " << client_id;
return false;
}
const auto& path = dev_fds_[client_id].path;
const auto& fd = dev_fds_[client_id].fd;
auto path_it = dev_ifaces_.find(path);
if (path_it != dev_ifaces_.end()) {
auto iface_it = path_it->second.find(iface_num);
if (iface_it != path_it->second.end()) {
if (iface_it->second != client_id) {
LOG(WARNING) << "The interface " << static_cast<int>(iface_num)
<< " at path " << path << " can't be detached by client "
<< client_id << " as it has been detached by other client "
<< iface_it->second;
return false;
}
// No-op if the interface has been detached by the requested client.
return true;
}
}
if (!DisconnectInterface(fd.get(), iface_num)) {
LOG(ERROR) << "Kernel USB driver disconnection for " << path
<< " on interface " << static_cast<int>(iface_num)
<< " by client " << client_id << " failed";
return false;
}
RecordInterfaceDetached(client_id, path, iface_num);
return true;
}
bool UsbDriverTracker::ReattachInterface(const std::string& client_id,
uint8_t iface_num) {
if (!IsClientIdTracked(client_id)) {
LOG(WARNING) << "ReattachInterface: Untracked client " << client_id;
return false;
}
const auto& path = dev_fds_[client_id].path;
const auto& fd = dev_fds_[client_id].fd;
auto path_it = dev_ifaces_.find(path);
if (path_it == dev_ifaces_.end()) {
// No-op if the path hasn't been detached by any clients.
return true;
}
auto iface_it = path_it->second.find(iface_num);
if (iface_it == path_it->second.end()) {
// No-op if the interface hasn't been detached by any clients.
return true;
}
if (iface_it->second != client_id) {
LOG(WARNING) << "The interface " << static_cast<int>(iface_num)
<< " at path " << path << " can't be attached by client "
<< client_id << " as it was detached by other client "
<< iface_it->second;
return false;
}
if (!ConnectInterface(fd.get(), iface_num)) {
LOG(ERROR) << "Kernel USB driver connection for " << path
<< " on interface " << static_cast<int>(iface_num)
<< " by client " << client_id << " failed";
return false;
}
ClearDetachedInterfaceRecord(client_id, path, iface_num);
return true;
}
} // namespace permission_broker