blob: 4fa7c6d14bc452f641793094c11180a48e5568d6 [file] [log] [blame]
// 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[] = {
{0x0d28, 0x0204}, // BBC micro:bit
{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