blob: 8e03de1eba28ca794f8f21381d444e3f6a5527ee [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_hidraw_device_rule.h"
#include <bits/wordsize.h>
#include <libudev.h>
#include <linux/input.h>
#include <algorithm>
#include <limits>
#include <string>
#include <vector>
#include "base/containers/adapters.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "permission_broker/udev_scopers.h"
namespace permission_broker {
namespace {
const std::vector<std::string> kGenericSubsystems = {
"bluetooth", "hid", "hidraw", "rfkill", "usb", "usbmisc"};
const char kLogitechUnifyingReceiverDriver[] = "logitech-djreceiver";
const char kThingmDriver[] = "thingm";
const base::StringPiece kJoydevPrefix = "/dev/input/js";
const size_t kAllowedAbsCapabilities[] = {
ABS_X, ABS_Y, ABS_Z, ABS_RX, ABS_RY,
ABS_RZ, ABS_THROTTLE, ABS_RUDDER, ABS_WHEEL, ABS_GAS,
ABS_BRAKE, ABS_HAT0X, ABS_HAT0Y, ABS_HAT1X, ABS_HAT1Y,
ABS_HAT2X, ABS_HAT2Y, ABS_HAT3X, ABS_HAT3Y, ABS_MISC,
};
bool ParseInputCapabilities(const char* input, std::vector<uint64_t>* output) {
// The kernel expresses capabilities as a bitfield, broken into long-sized
// chunks encoded in hexadecimal.
std::vector<std::string> chunks = base::SplitString(
input, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
output->clear();
output->reserve(chunks.size());
// The most-significant chunk of the bitmask is stored first, iterate over
// the chunks in reverse so that the result is easier to work with.
for (const std::string& chunk : base::Reversed(chunks)) {
uint64_t value = 0;
if (!base::HexStringToUInt64(chunk, &value)) {
LOG(ERROR) << "Failed to parse: " << chunk;
return false;
}
// NOLINTNEXTLINE(runtime/int)
if (value > std::numeric_limits<unsigned long>::max()) {
LOG(ERROR) << "Chunk value too large for platform: " << value;
return false;
}
output->push_back(value);
}
if (__WORDSIZE == 32) {
// Compact the vector of 32-bit values into a vector of 64-bit values.
for (size_t i = 0; i < chunks.size(); i += 2) {
(*output)[i / 2] = (*output)[i];
if (i + 1 < chunks.size())
(*output)[i / 2] |= (uint64_t)((*output)[i + 1]) << 32;
}
output->resize((chunks.size() + 1) / 2);
}
return true;
}
bool IsCapabilityBitSet(const std::vector<uint64_t>& bitfield, size_t bit) {
size_t offset = bit / (sizeof(uint64_t) * 8);
if (offset >= bitfield.size())
return false;
return bitfield[offset] & (1ULL << (bit - offset * sizeof(bitfield[0]) * 8));
}
bool AnyCapabilityBitsSet(const std::vector<uint64_t>& bitfield) {
for (auto value : bitfield) {
if (value != 0)
return true;
}
return false;
}
void UnsetCapabilityBit(std::vector<uint64_t>* bitfield, size_t bit) {
size_t offset = bit / (sizeof(uint64_t) * 8);
if (offset >= bitfield->size())
return;
(*bitfield)[offset] &= ~(1ULL << (bit - offset * sizeof(uint64_t) * 8));
}
constexpr bool IsCfmDevice() {
return USE_CFM_ENABLED_DEVICE;
}
// Joydev devices expose a devnode string with the format:
// /dev/input/js#
// Where # is the numeric index of the joydev device.
bool IsJoydevDeviceNode(base::StringPiece devnode) {
// Match the devnode prefix.
if (!base::StartsWith(devnode, kJoydevPrefix, base::CompareCase::SENSITIVE))
return false;
// Match if the suffix parses as a non-negative integer.
devnode.remove_prefix(kJoydevPrefix.length());
int joydev_index = 0;
if (!base::StringToInt(devnode, &joydev_index))
return false;
return joydev_index >= 0;
}
} // namespace
DenyClaimedHidrawDeviceRule::DenyClaimedHidrawDeviceRule()
: HidrawSubsystemUdevRule("DenyClaimedHidrawDeviceRule") {}
Rule::Result DenyClaimedHidrawDeviceRule::ProcessHidrawDevice(
struct udev_device* device) {
struct udev_device* hid_parent =
udev_device_get_parent_with_subsystem_devtype(device, "hid", nullptr);
if (!hid_parent) {
// A hidraw device without a HID parent, we don't know what this can be.
return DENY;
}
const char* hid_parent_driver = udev_device_get_driver(hid_parent);
if (hid_parent_driver) {
// Add an exception to the rule for Logitech Unifying receiver. This hidraw
// device is a parent of devices that have input subsystem. Yet the traffic
// to those children is not available on the hidraw node of the receiver,
// so it is safe to allow it.
if (strcmp(hid_parent_driver, kLogitechUnifyingReceiverDriver) == 0) {
LOG(INFO) << "Found Logitech Unifying receiver. Skipping rule.";
return IGNORE;
}
// An led subsystem driver is provided for this device but for historical
// reasons we want to continue to allow raw HID access as well.
if (strcmp(hid_parent_driver, kThingmDriver) == 0) {
LOG(INFO) << "Found ThingM blink(1). Skipping rule.";
return IGNORE;
}
}
std::string hid_parent_path(udev_device_get_syspath(hid_parent));
std::string usb_interface_path;
struct udev_device* usb_interface =
udev_device_get_parent_with_subsystem_devtype(device, "usb",
"usb_interface");
if (usb_interface)
usb_interface_path = udev_device_get_syspath(usb_interface);
// Count the number of children of the same HID parent as us.
int hid_siblings = 0;
bool should_sibling_subsystem_exclude_access = false;
// Scan all children of the USB interface for subsystems other than generic
// USB or HID, and all the children of the same HID parent device.
// The presence of such subsystems is an indication that the device is in
// use by another driver.
//
// Because udev lacks the ability to filter an enumeration by arbitrary
// ancestor properties (e.g. "enumerate all nodes with a usb_interface
// ancestor") we have to scan the entire set of devices to find potential
// matches.
struct udev* udev = udev_device_get_udev(device);
ScopedUdevEnumeratePtr enumerate(udev_enumerate_new(udev));
udev_enumerate_scan_devices(enumerate.get());
struct udev_list_entry* entry;
udev_list_entry_foreach(entry,
udev_enumerate_get_list_entry(enumerate.get())) {
const char* syspath = udev_list_entry_get_name(entry);
ScopedUdevDevicePtr child(udev_device_new_from_syspath(udev, syspath));
struct udev_device* child_usb_interface =
udev_device_get_parent_with_subsystem_devtype(child.get(), "usb",
"usb_interface");
struct udev_device* child_hid_parent =
udev_device_get_parent_with_subsystem_devtype(child.get(), "hid",
nullptr);
if (!child_usb_interface && !child_hid_parent) {
continue;
}
// Some gamepads expose functionality that is only accessible through the
// hidraw node. To allow hidraw access to such devices, skip the sibling
// subsystem and capabilities checks if one of the siblings is a joydev
// device.
const char* devnode = udev_device_get_devnode(child.get());
if (devnode && IsJoydevDeviceNode(devnode))
return IGNORE;
// This device shares a USB interface with the hidraw device in question.
// Check its subsystem to see if it should block hidraw access.
if (!should_sibling_subsystem_exclude_access && usb_interface &&
child_usb_interface &&
usb_interface_path == udev_device_get_syspath(child_usb_interface)) {
should_sibling_subsystem_exclude_access =
ShouldSiblingSubsystemExcludeHidAccess(child.get());
}
// This device shares the same HID device as parent, count it.
if (child_hid_parent &&
hid_parent_path == udev_device_get_syspath(child_hid_parent)) {
hid_siblings++;
}
}
// If the underlying device presents other interfaces, deny access to the
// hidraw node as it may allow access to private data transmitted over these
// interfaces.
if (should_sibling_subsystem_exclude_access)
return DENY;
// If the underlying HID device presents no other interface than hidraw,
// we can use it.
// USB devices have already been filtered directly in the loop above.
if (!usb_interface && hid_siblings != 1)
return DENY;
return IGNORE;
}
bool DenyClaimedHidrawDeviceRule::ShouldSiblingSubsystemExcludeHidAccess(
struct udev_device* sibling) {
const char* subsystem = udev_device_get_subsystem(sibling);
if (!subsystem) {
return false;
}
// Generic subsystems (such as "hid" or "usb") should never exclude access.
if (std::find(kGenericSubsystems.begin(), kGenericSubsystems.end(),
subsystem) != kGenericSubsystems.end()) {
return false;
}
// Don't block hidraw access due to leds subsystem.
if (IsCfmDevice() && strcmp(subsystem, "leds") == 0) {
return false;
}
if (strcmp(subsystem, "input") == 0 &&
!ShouldInputCapabilitiesExcludeHidAccess(
udev_device_get_sysattr_value(sibling, "capabilities/abs"),
udev_device_get_sysattr_value(sibling, "capabilities/rel"),
udev_device_get_sysattr_value(sibling, "capabilities/key"))) {
return false;
}
return true;
}
bool DenyClaimedHidrawDeviceRule::ShouldInputCapabilitiesExcludeHidAccess(
const char* abs_capabilities,
const char* rel_capabilities,
const char* key_capabilities) {
bool has_absolute_mouse_axes = false;
bool has_absolute_mouse_keys = false;
std::vector<uint64_t> capabilities;
if (abs_capabilities) {
if (!ParseInputCapabilities(abs_capabilities, &capabilities)) {
// Parse error? Fail safe.
return true;
}
// If the device has ABS_X and ABS_Y and no other absolute axes, it may be
// an absolute pointing device.
if (IsCapabilityBitSet(capabilities, ABS_X) &&
IsCapabilityBitSet(capabilities, ABS_Y)) {
UnsetCapabilityBit(&capabilities, ABS_X);
UnsetCapabilityBit(&capabilities, ABS_Y);
if (!AnyCapabilityBitsSet(capabilities))
has_absolute_mouse_axes = true;
}
// Remove allowed capabilities. Any other absolute pointer capabilities
// exclude access.
for (size_t i = 0; i < base::size(kAllowedAbsCapabilities); ++i)
UnsetCapabilityBit(&capabilities, kAllowedAbsCapabilities[i]);
if (AnyCapabilityBitsSet(capabilities))
return true;
}
if (rel_capabilities) {
if (!ParseInputCapabilities(rel_capabilities, &capabilities)) {
// Parse error? Fail safe.
return true;
}
// Any relative pointer capabilities exclude access.
if (AnyCapabilityBitsSet(capabilities))
return true;
}
if (key_capabilities) {
if (!ParseInputCapabilities(key_capabilities, &capabilities)) {
// Parse error? Fail safe.
return true;
}
// If the device has BTN_LEFT, BTN_RIGHT, BTN_MIDDLE and no other keys,
// it may be an absolute pointing device.
if (IsCapabilityBitSet(capabilities, BTN_LEFT) &&
IsCapabilityBitSet(capabilities, BTN_RIGHT) &&
IsCapabilityBitSet(capabilities, BTN_MIDDLE)) {
UnsetCapabilityBit(&capabilities, BTN_LEFT);
UnsetCapabilityBit(&capabilities, BTN_RIGHT);
UnsetCapabilityBit(&capabilities, BTN_MIDDLE);
if (!AnyCapabilityBitsSet(capabilities))
has_absolute_mouse_keys = true;
}
// Any key code <= KEY_KPDOT (83) excludes access.
for (int key = 0; key <= KEY_KPDOT; key++) {
if (IsCapabilityBitSet(capabilities, key)) {
return true;
}
}
// Braille dots are outside the "normal keyboard keys" range.
for (int key = KEY_BRL_DOT1; key <= KEY_BRL_DOT10; key++) {
if (IsCapabilityBitSet(capabilities, key)) {
return true;
}
}
}
// Exclude absolute pointing devices that match joydev's capabilities check.
if (has_absolute_mouse_axes && has_absolute_mouse_keys)
return true;
return false;
}
} // namespace permission_broker