blob: d3474fd69f6d6aba7d3263185725e9ad3eaa5a9c [file] [log] [blame]
// Copyright 2016 The Chromium OS Authors. All rights reserved.
// 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 <fcntl.h>
#include <linux/usbdevice_fs.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <memory>
#include <string>
#include <utility>
#include <base/bind.h>
#include <base/files/file_descriptor_watcher_posix.h>
#include <base/files/scoped_file.h>
#include <base/logging.h>
#include <base/posix/eintr_wrapper.h>
#include <base/strings/string_number_conversions.h>
#include "permission_broker/udev_scopers.h"
namespace permission_broker {
struct UsbDriverTracker::UsbInterfaces {
std::string path;
std::unique_ptr<base::FileDescriptorWatcher::Controller> controller;
std::vector<uint8_t> ifaces;
base::ScopedFD fd;
};
UsbDriverTracker::UsbDriverTracker() = default;
UsbDriverTracker::~UsbDriverTracker() {
// Re-attach all delegated USB interfaces
for (auto& elem : dev_fds_) {
auto entry = std::move(elem.second);
ReAttachPathToKernel(entry.path, entry.ifaces);
}
}
void UsbDriverTracker::HandleClosedFd(int fd) {
auto iter = dev_fds_.find(fd);
if (iter != dev_fds_.end()) {
auto entry = std::move(iter->second);
// Re-attaching the kernel driver to the USB interface.
ReAttachPathToKernel(entry.path, entry.ifaces);
// We are done with the lifeline_fd.
dev_fds_.erase(iter);
} else {
LOG(WARNING) << "Untracked USB lifeline_fd " << fd;
}
}
bool UsbDriverTracker::ReAttachPathToKernel(
const std::string& path, const std::vector<uint8_t>& ifaces) {
int fd = HANDLE_EINTR(open(path.c_str(), O_RDWR));
if (fd < 0) {
LOG(WARNING) << "Cannot open " << path;
return false;
}
for (uint8_t iface_num : ifaces) {
struct usbdevfs_ioctl dio;
dio.ifno = iface_num;
dio.ioctl_code = USBDEVFS_CONNECT;
dio.data = nullptr;
int res = ioctl(fd, USBDEVFS_IOCTL, &dio);
if (res < 0) {
LOG(WARNING) << "Kernel USB driver connection for " << path
<< " on interface " << iface_num << " failed " << errno;
} else {
LOG(INFO) << "Kernel USB driver attached on " << path << " interface "
<< static_cast<int>(iface_num);
}
}
IGNORE_EINTR(close(fd));
return true;
}
bool UsbDriverTracker::DetachPathFromKernel(int fd,
int lifeline_fd,
const std::string& 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;
std::vector<uint8_t> ifaces;
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;
}
struct usbdevfs_ioctl dio;
dio.ifno = iface_num;
dio.ioctl_code = USBDEVFS_DISCONNECT;
dio.data = nullptr;
int res = ioctl(fd, USBDEVFS_IOCTL, &dio);
if (res < 0) {
LOG(WARNING) << "Kernel USB driver disconnection for " << path
<< " on interface " << iface_num << " failed " << errno;
} else {
detached = true;
ifaces.push_back(iface_num);
LOG(INFO) << "USB driver '" << driver << "' detached on " << path
<< " interface " << iface_num;
}
}
}
if (detached && lifeline_fd != kInvalidLifelineFD) {
WatchLifelineFd(lifeline_fd, std::move(path), std::move(ifaces));
}
return detached;
}
void UsbDriverTracker::WatchLifelineFd(int lifeline_fd,
std::string path,
std::vector<uint8_t> ifaces) {
base::ScopedFD lifeline(HANDLE_EINTR(dup(lifeline_fd)));
int dup_fd = lifeline.get();
auto controller = base::FileDescriptorWatcher::WatchReadable(
dup_fd, base::BindRepeating(&UsbDriverTracker::HandleClosedFd,
weak_ptr_factory_.GetWeakPtr(), dup_fd));
if (!controller) {
LOG(ERROR) << "Unable to watch lifeline_fd " << dup_fd;
return;
}
VLOG(1) << "Watching lifeline_fd " << dup_fd;
dev_fds_.emplace(dup_fd,
UsbInterfaces{std::move(path), std::move(controller),
std::move(ifaces), std::move(lifeline)});
}
} // namespace permission_broker