| // Copyright (c) 2012 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/deny_claimed_usb_device_rule.h" |
| |
| #include <libudev.h> |
| #include <linux/usb/ch9.h> |
| |
| #include <iterator> |
| #include <memory> |
| #include <string> |
| |
| #include "base/logging.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "permission_broker/udev_scopers.h" |
| |
| #include <base/check.h> |
| |
| using policy::DevicePolicy; |
| |
| namespace { |
| |
| const uint32_t kAdbClass = 0xff; |
| const uint32_t kAdbSubclass = 0x42; |
| const uint32_t kAdbProtocol = 0x1; |
| |
| enum class RemovableAttr { |
| kUnknown, |
| kFixed, |
| kRemovable, |
| }; |
| |
| RemovableAttr ParseRemovableSysattr(const std::string& removable) { |
| if (removable == "fixed") { |
| return RemovableAttr::kFixed; |
| } else if (removable == "removable") { |
| return RemovableAttr::kRemovable; |
| } else { |
| if (removable != "unknown") { |
| LOG(WARNING) << "Unexpected value for removable sysattr: '" << removable |
| << "'"; |
| } |
| return RemovableAttr::kUnknown; |
| } |
| } |
| |
| RemovableAttr GetRemovableSysattr(udev_device* device) { |
| const char* removable = udev_device_get_sysattr_value(device, "removable"); |
| if (!removable) { |
| return RemovableAttr::kUnknown; |
| } |
| return ParseRemovableSysattr(removable); |
| } |
| |
| bool GetUIntSysattr(udev_device* device, const char* key, uint32_t* val) { |
| CHECK(val); |
| |
| const char* str_val = udev_device_get_sysattr_value(device, key); |
| return str_val && base::HexStringToUInt(str_val, val); |
| } |
| |
| // Check if a USB vendor:product ID pair is in the provided list. |
| // Entries in the list with |product_id| of 0 match any product with the |
| // corresponding |vendor_id|. |
| template <typename Iterator> |
| bool UsbDeviceListContainsId(Iterator first, |
| Iterator last, |
| uint16_t vendor_id, |
| uint16_t product_id) { |
| while (first != last) { |
| if (first->vendor_id == vendor_id && |
| (!first->product_id || first->product_id == product_id)) |
| return true; |
| ++first; |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| namespace permission_broker { |
| |
| DenyClaimedUsbDeviceRule::DenyClaimedUsbDeviceRule() |
| : UsbSubsystemUdevRule("DenyClaimedUsbDeviceRule"), policy_loaded_(false) {} |
| |
| DenyClaimedUsbDeviceRule::~DenyClaimedUsbDeviceRule() = default; |
| |
| bool DenyClaimedUsbDeviceRule::LoadPolicy() { |
| usb_allow_list_.clear(); |
| |
| auto policy_provider = std::make_unique<policy::PolicyProvider>(); |
| policy_provider->Reload(); |
| |
| // No available policies. |
| if (!policy_provider->device_policy_is_loaded()) |
| return false; |
| |
| const policy::DevicePolicy* policy = &policy_provider->GetDevicePolicy(); |
| return policy->GetUsbDetachableWhitelist(&usb_allow_list_); |
| } |
| |
| bool DenyClaimedUsbDeviceRule::IsDeviceDetachableByPolicy(udev_device* device) { |
| // Retrieve the device policy for detachable USB devices if needed. |
| if (!policy_loaded_) |
| policy_loaded_ = LoadPolicy(); |
| if (!policy_loaded_) |
| return false; |
| |
| // Check whether this USB device is allowed. |
| uint32_t vendor_id, product_id; |
| if (!GetUIntSysattr(device, "idVendor", &vendor_id) || |
| !GetUIntSysattr(device, "idProduct", &product_id)) |
| return false; |
| |
| return UsbDeviceListContainsId(usb_allow_list_.begin(), usb_allow_list_.end(), |
| vendor_id, product_id); |
| } |
| |
| bool DenyClaimedUsbDeviceRule::IsInterfaceAdb(udev_device* device) { |
| uint32_t intf_class, intf_subclass, intf_protocol; |
| if (!GetUIntSysattr(device, "bInterfaceClass", &intf_class) || |
| !GetUIntSysattr(device, "bInterfaceSubClass", &intf_subclass) || |
| !GetUIntSysattr(device, "bInterfaceProtocol", &intf_protocol)) |
| return false; |
| |
| return intf_class == kAdbClass && intf_subclass == kAdbSubclass && |
| intf_protocol == kAdbProtocol; |
| } |
| |
| bool IsInterfaceUsbSerial(udev_device* iface) { |
| // Search all children of the interface in the 'usb-serial' subsystem |
| // this includes all the USB-serial converter and most micro-controllers |
| // USB bulk endpoints presenting a serial-like interface but not CDC-ACM |
| // devices (e.g. modems or boards pretending to be one) |
| udev* udev = udev_device_get_udev(iface); |
| ScopedUdevEnumeratePtr enum_serial(udev_enumerate_new(udev)); |
| udev_enumerate_add_match_subsystem(enum_serial.get(), "usb-serial"); |
| udev_enumerate_add_match_parent(enum_serial.get(), iface); |
| udev_enumerate_scan_devices(enum_serial.get()); |
| |
| struct udev_list_entry* entry = nullptr; |
| udev_list_entry_foreach(entry, |
| udev_enumerate_get_list_entry(enum_serial.get())) { |
| // a usb-serial driver is connected to this interface |
| LOG(INFO) << "Found usb-serial interface."; |
| return true; |
| } |
| return false; |
| } |
| |
| bool IsInterfaceStorage(udev_device* iface) { |
| uint32_t interface_class; |
| if (!GetUIntSysattr(iface, "bInterfaceClass", &interface_class)) |
| return false; |
| // This matches USB drives, SD adapters, and so on. |
| return interface_class == USB_CLASS_MASS_STORAGE; |
| } |
| |
| bool IsInterfaceSafeToDetach(udev_device* iface) { |
| // Normally the permission_broker prevents users from interfering with the |
| // system usage of a USB device. |
| |
| // But in particular cases, a USB interface is deemed 'safe to detach' from |
| // its kernel driver if the purpose of the driver is only exposing it to apps. |
| // e.g. below the usb serial interfaces are only used by the chrome.serial |
| // and WebSerial external API rather than in any intrinsic system use. |
| |
| // Storage devices are a special case that we allow to be shared to Guest VMs. |
| // Chrome provides extra protections to avoid exposing these devices to |
| // non-Guest VM components. |
| |
| return IsInterfaceUsbSerial(iface) || IsInterfaceStorage(iface); |
| } |
| |
| bool IsDeviceAllowedSerial(udev_device* device) { |
| // The Arduino vendor IDs are derived from https://raw.githubusercontent.com |
| // /arduino/ArduinoCore-avr/master/boards.txt |
| // /arduino/ArduinoCore-sam/master/boards.txt |
| // /arduino/ArduinoCore-samd/master/boards.txt |
| // using |
| // grep -o -E "vid\..*=(0x.*)" *boards.txt | sed "s/vid\..=//g" | sort -f | \ |
| // uniq -i |
| const DevicePolicy::UsbDeviceId kAllowedIds[] = { |
| {0x2341, 0}, // Arduino |
| {0x1b4f, 0}, // Sparkfun |
| {0x239a, 0}, // Adafruit |
| {0x2a03, 0}, // doghunter.org |
| {0x10c4, 0}, // Silicon Labs |
| |
| {0x2c99, 0}, // Prusa Research |
| |
| {0x2e8a, 0}, // Raspberry Pi |
| |
| {0x18d1, 0x5002}, // Google Servo V2 |
| {0x18d1, 0x5003}, // Google Servo V2 |
| {0x18d1, 0x500a}, // Google twinkie |
| {0x18d1, 0x500b}, // Google Plankton |
| {0x18d1, 0x500c}, // Google Plankton |
| {0x18d1, 0x5014}, // Google Cr50 |
| {0x18d1, 0x501a}, // Google Servo micro |
| {0x18d1, 0x501b}, // Google Servo V4 |
| {0x18d1, 0x501f}, // Google Suzyq |
| {0x18d1, 0x5020}, // Google Sweetberry |
| {0x18d1, 0x5027}, // Google Tigertail |
| {0x18d1, 0x5036}, // Google Chocodile |
| |
| {0x1d50, 0x6140}, // QuickLogic QuickFeather evaluation board bootloader |
| {0x1d50, 0x6130}, // TinyFPGA BX Bootloader old openmoko VID:PID |
| {0x1209, 0x2100}, // TinyFPGA BX Bootloader new pid.codes VID:PID |
| {0x1209, 0x5bf0}, // Arty FPGA board |
| }; |
| uint32_t vendor_id, product_id; |
| if (!GetUIntSysattr(device, "idVendor", &vendor_id) || |
| !GetUIntSysattr(device, "idProduct", &product_id)) |
| return false; |
| |
| return UsbDeviceListContainsId(std::begin(kAllowedIds), std::end(kAllowedIds), |
| vendor_id, product_id); |
| } |
| |
| Rule::Result DenyClaimedUsbDeviceRule::ProcessUsbDevice(udev_device* device) { |
| const char* device_syspath = udev_device_get_syspath(device); |
| if (!device_syspath) { |
| return DENY; |
| } |
| |
| udev* udev = udev_device_get_udev(device); |
| ScopedUdevEnumeratePtr enumerate(udev_enumerate_new(udev)); |
| udev_enumerate_add_match_subsystem(enumerate.get(), "usb"); |
| udev_enumerate_scan_devices(enumerate.get()); |
| |
| bool found_claimed_interface = false; |
| bool found_unclaimed_interface = false; |
| bool found_adb_interface = false; |
| bool found_only_safe_interfaces = true; |
| struct udev_list_entry* entry = nullptr; |
| 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, entry_path)); |
| |
| // Find out if this entry's direct parent is the device in question. |
| struct udev_device* parent = udev_device_get_parent(child.get()); |
| if (!parent) { |
| continue; |
| } |
| const char* parent_syspath = udev_device_get_syspath(parent); |
| if (!parent_syspath || strcmp(device_syspath, parent_syspath) != 0) { |
| continue; |
| } |
| |
| const char* child_type = udev_device_get_devtype(child.get()); |
| if (!child_type || strcmp(child_type, "usb_interface") != 0) { |
| // If this is not a usb_interface node then something is wrong, fail safe. |
| LOG(WARNING) << "Found a child '" << entry_path |
| << "' with unexpected type: " |
| << (child_type ? child_type : "(null)"); |
| return DENY; |
| } |
| |
| const char* driver = udev_device_get_driver(child.get()); |
| if (driver) { |
| LOG(INFO) << "Found claimed interface with driver: " << driver; |
| found_claimed_interface = true; |
| found_only_safe_interfaces = |
| found_only_safe_interfaces && IsInterfaceSafeToDetach(child.get()); |
| } else { |
| found_unclaimed_interface = true; |
| } |
| |
| if (IsInterfaceAdb(child.get())) { |
| LOG(INFO) << "Found ADB interface."; |
| found_adb_interface = true; |
| } |
| } |
| |
| if (found_claimed_interface) { |
| // Don't allow detaching the driver from fixed (internal) USB devices. |
| if (GetRemovableSysattr(device) == RemovableAttr::kFixed) { |
| LOG(INFO) << "Denying fixed USB device with driver."; |
| return DENY; |
| } |
| |
| if (found_only_safe_interfaces) |
| LOG(INFO) << "Found only detachable interface(s), safe to claim."; |
| |
| if (IsDeviceDetachableByPolicy(device) || IsDeviceAllowedSerial(device) || |
| found_adb_interface || found_only_safe_interfaces) |
| return ALLOW_WITH_DETACH; |
| else |
| return found_unclaimed_interface ? ALLOW_WITH_LOCKDOWN : DENY; |
| } else { |
| return IGNORE; |
| } |
| } |
| |
| } // namespace permission_broker |