| // 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 <string> |
| #include <utility> |
| |
| #include <base/bind.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; |
| }; |
| |
| 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::ScanClosedFd(int fd) { |
| auto iter = dev_fds_.find(fd); |
| if (iter != dev_fds_.end()) { |
| int res = fcntl(fd, F_GETFL); |
| if (res < 0) { // the browser has close the file descriptor |
| 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 USB interface. |
| dev_fds_.erase(iter); |
| } |
| } else { |
| LOG(WARNING) << "Untracked USB file descriptor " << 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 " << iface_num; |
| } |
| } |
| IGNORE_EINTR(close(fd)); |
| |
| return true; |
| } |
| |
| bool UsbDriverTracker::DetachPathFromKernel(int 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) { |
| auto controller = base::FileDescriptorWatcher::WatchWritable( |
| fd, |
| base::BindRepeating(&UsbDriverTracker::ScanClosedFd, |
| base::Unretained(this), fd)); |
| if (!controller) { |
| LOG(ERROR) << "Unable to watch FD: " << fd; |
| return true; |
| } |
| dev_fds_.emplace( |
| fd, |
| UsbInterfaces{ |
| std::move(path), std::move(controller), std::move(ifaces)}); |
| } |
| |
| return detached; |
| } |
| |
| } // namespace permission_broker |