blob: 52a73632b9c4a1fffc0b686c839b88b29daaa360 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/serial/serial_blocklist.h"
#include <algorithm>
#include <string>
#include <string_view>
#include <tuple>
#include "base/metrics/field_trial_params.h"
#include "base/no_destructor.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "services/device/public/cpp/bluetooth/bluetooth_utils.h"
#include "services/device/public/mojom/serial.mojom.h"
namespace {
const char kEntryKeyUsb[] = "usb";
const char kEntryKeyBluetooth[] = "bluetooth";
const char kBluetoothStandardUUID[] = "0000-1000-8000-00805f9b34fb";
// Returns true if the passed string is exactly 4 digits long and only contains
// valid hexadecimal characters (no leading 0x).
bool IsHexComponent(std::string_view string) {
if (string.length() != 4)
return false;
// This is necessary because base::HexStringToUInt allows whitespace and the
// "0x" prefix in its input.
for (char c : string) {
if (c >= '0' && c <= '9')
continue;
if (c >= 'a' && c <= 'f')
continue;
if (c >= 'A' && c <= 'F')
continue;
return false;
}
return true;
}
bool CompareEntry(const SerialBlocklist::Entry& a,
const SerialBlocklist::Entry& b) {
return std::tie(a.usb_vendor_id, a.usb_product_id,
a.bluetooth_service_class_id) <
std::tie(b.usb_vendor_id, b.usb_product_id,
b.bluetooth_service_class_id);
}
// Returns true if an entry in [begin, end) matches |entry|.
template <class Iterator>
bool EntryMatches(Iterator begin,
Iterator end,
const SerialBlocklist::Entry& entry) {
auto it = std::lower_bound(begin, end, entry, CompareEntry);
if (it == end) {
return false;
}
// USB devices
if (it->bluetooth_service_class_id.empty()) {
return it->usb_vendor_id == entry.usb_vendor_id &&
it->usb_product_id == entry.usb_product_id;
}
// Bluetooth devices
return it->bluetooth_service_class_id == entry.bluetooth_service_class_id;
}
} // namespace
BASE_FEATURE(kWebSerialBlocklist,
"WebSerialBlocklist",
base::FEATURE_ENABLED_BY_DEFAULT);
constexpr base::FeatureParam<std::string> kWebSerialBlocklistAdditions{
&kWebSerialBlocklist, "BlocklistAdditions", /*default_value=*/""};
SerialBlocklist::~SerialBlocklist() = default;
// static
SerialBlocklist& SerialBlocklist::Get() {
static base::NoDestructor<SerialBlocklist> blocklist;
return *blocklist;
}
bool SerialBlocklist::IsExcluded(
const device::mojom::SerialPortInfo& port_info) const {
if (!((port_info.has_vendor_id && port_info.has_product_id) ||
port_info.bluetooth_service_class_id.has_value())) {
return false;
}
// We exclude all Bluetooth specified services except Serial Port Profile
// regardless of the blocklist for security.
if (port_info.bluetooth_service_class_id &&
port_info.bluetooth_service_class_id->IsValid() &&
*port_info.bluetooth_service_class_id !=
device::GetSerialPortProfileUUID() &&
port_info.bluetooth_service_class_id->canonical_value().substr(9, 27) ==
kBluetoothStandardUUID) {
return true;
}
Entry entry(port_info.has_vendor_id ? port_info.vendor_id : 0x0,
port_info.has_product_id ? port_info.product_id : 0x0,
port_info.bluetooth_service_class_id
? port_info.bluetooth_service_class_id->canonical_value()
: "");
return EntryMatches(std::begin(static_entries_), std::end(static_entries_),
entry) ||
EntryMatches(dynamic_entries_.begin(), dynamic_entries_.end(), entry);
}
void SerialBlocklist::ResetToDefaultValuesForTesting() {
dynamic_entries_.clear();
PopulateWithServerProvidedValues();
}
SerialBlocklist::SerialBlocklist() {
CHECK(std::is_sorted(std::begin(static_entries_), std::end(static_entries_),
CompareEntry));
PopulateWithServerProvidedValues();
}
void SerialBlocklist::PopulateWithServerProvidedValues() {
std::string blocklist_string = kWebSerialBlocklistAdditions.Get();
for (const auto& entry :
base::SplitStringPiece(blocklist_string, ",", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY)) {
std::vector<std::string_view> components = base::SplitStringPiece(
entry, ":", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
// Entry must at least indicate the type
if (components.empty()) {
continue;
}
if (components[0] == kEntryKeyUsb) {
if (components.size() != 3 || !IsHexComponent(components[1]) ||
!IsHexComponent(components[2])) {
continue;
}
uint32_t vendor_id;
uint32_t product_id;
if (!base::HexStringToUInt(components[1], &vendor_id) ||
!base::HexStringToUInt(components[2], &product_id)) {
continue;
}
dynamic_entries_.emplace_back(vendor_id, product_id,
/*bluetooth_service_class_id=*/"");
} else if (components[0] == kEntryKeyBluetooth) {
if (components.size() != 2) {
continue;
}
std::string uuid_as_string(components[1]);
device::BluetoothUUID uuid(uuid_as_string);
if (!uuid.IsValid()) {
continue;
}
dynamic_entries_.emplace_back(/*usb_vendor_id=*/0, /*usb_product_id=*/0,
uuid.canonical_value());
}
}
std::sort(dynamic_entries_.begin(), dynamic_entries_.end(), CompareEntry);
}