| // Copyright 2020 The Chromium 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 "services/device/public/cpp/hid/hid_blocklist.h" |
| |
| #include "base/command_line.h" |
| #include "base/no_destructor.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "components/variations/variations_associated_data.h" |
| #include "services/device/public/cpp/hid/hid_switches.h" |
| |
| namespace device { |
| |
| namespace { |
| |
| #define VENDOR_PRODUCT_RULE(vid, pid) \ |
| { \ |
| true, (vid), true, (pid), false, 0, false, 0, false, 0, \ |
| HidBlocklist::ReportType::kReportTypeAny \ |
| } |
| |
| constexpr HidBlocklist::Entry kStaticEntries[] = { |
| // Block all top-level collections with the FIDO usage page. |
| {false, 0, false, 0, true, mojom::kPageFido, false, 0, false, 0, |
| HidBlocklist::ReportType::kReportTypeAny}, |
| |
| // KEY-ID |
| VENDOR_PRODUCT_RULE(0x096e, 0x0850), |
| // Feitian devices |
| VENDOR_PRODUCT_RULE(0x096e, 0x0852), |
| VENDOR_PRODUCT_RULE(0x096e, 0x0853), |
| VENDOR_PRODUCT_RULE(0x096e, 0x0854), |
| VENDOR_PRODUCT_RULE(0x096e, 0x0856), |
| VENDOR_PRODUCT_RULE(0x096e, 0x0858), |
| VENDOR_PRODUCT_RULE(0x096e, 0x085a), |
| VENDOR_PRODUCT_RULE(0x096e, 0x085b), |
| // HyperFIDO |
| VENDOR_PRODUCT_RULE(0x096e, 0x0880), |
| // HID Global BlueTrust Token |
| VENDOR_PRODUCT_RULE(0x09c3, 0x0023), |
| // Yubikey devices |
| VENDOR_PRODUCT_RULE(0x1050, 0x0010), |
| VENDOR_PRODUCT_RULE(0x1050, 0x0018), |
| VENDOR_PRODUCT_RULE(0x1050, 0x0030), |
| VENDOR_PRODUCT_RULE(0x1050, 0x0110), |
| VENDOR_PRODUCT_RULE(0x1050, 0x0111), |
| VENDOR_PRODUCT_RULE(0x1050, 0x0112), |
| VENDOR_PRODUCT_RULE(0x1050, 0x0113), |
| VENDOR_PRODUCT_RULE(0x1050, 0x0114), |
| VENDOR_PRODUCT_RULE(0x1050, 0x0115), |
| VENDOR_PRODUCT_RULE(0x1050, 0x0116), |
| VENDOR_PRODUCT_RULE(0x1050, 0x0120), |
| VENDOR_PRODUCT_RULE(0x1050, 0x0200), |
| VENDOR_PRODUCT_RULE(0x1050, 0x0211), |
| VENDOR_PRODUCT_RULE(0x1050, 0x0401), |
| VENDOR_PRODUCT_RULE(0x1050, 0x0402), |
| VENDOR_PRODUCT_RULE(0x1050, 0x0403), |
| VENDOR_PRODUCT_RULE(0x1050, 0x0404), |
| VENDOR_PRODUCT_RULE(0x1050, 0x0405), |
| VENDOR_PRODUCT_RULE(0x1050, 0x0406), |
| VENDOR_PRODUCT_RULE(0x1050, 0x0407), |
| VENDOR_PRODUCT_RULE(0x1050, 0x0410), |
| // U2F Zero |
| VENDOR_PRODUCT_RULE(0x10c4, 0x8acf), |
| // Mooltipass Mini-BLE |
| VENDOR_PRODUCT_RULE(0x1209, 0x4321), |
| // Mooltipass Arduino sketch |
| VENDOR_PRODUCT_RULE(0x1209, 0x4322), |
| // Titan |
| VENDOR_PRODUCT_RULE(0x18d1, 0x5026), |
| // VASCO |
| VENDOR_PRODUCT_RULE(0x1a44, 0x00bb), |
| // Keydo AES |
| VENDOR_PRODUCT_RULE(0x1e0d, 0xf1ae), |
| // Neowave Keydo |
| VENDOR_PRODUCT_RULE(0x1e0d, 0xf1d0), |
| // Thetis |
| VENDOR_PRODUCT_RULE(0x1ea8, 0xf025), |
| // Nitrokey |
| VENDOR_PRODUCT_RULE(0x20a0, 0x4287), |
| // JaCarta |
| VENDOR_PRODUCT_RULE(0x24dc, 0x0101), |
| // Happlink |
| VENDOR_PRODUCT_RULE(0x2581, 0xf1d0), |
| // Bluink |
| VENDOR_PRODUCT_RULE(0x2abe, 0x1002), |
| // Feitian USB, HyperFIDO |
| VENDOR_PRODUCT_RULE(0x2ccf, 0x0880), |
| }; |
| |
| bool IsValidBlocklistEntry(const HidBlocklist::Entry& entry) { |
| // An entry with a product ID parameter must also specify a vendor ID. |
| if (!entry.has_vendor_id && entry.has_product_id) |
| return false; |
| |
| // An entry with a usage ID parameter must also specify a usage page. |
| if (!entry.has_usage_page && entry.has_usage) |
| return false; |
| |
| return true; |
| } |
| |
| const std::vector<mojom::HidReportDescriptionPtr>& GetReportsForType( |
| HidBlocklist::ReportType report_type, |
| const mojom::HidCollectionInfo& collection) { |
| switch (report_type) { |
| case HidBlocklist::kReportTypeInput: |
| return collection.input_reports; |
| case HidBlocklist::kReportTypeOutput: |
| return collection.output_reports; |
| case HidBlocklist::kReportTypeFeature: |
| return collection.feature_reports; |
| case HidBlocklist::kReportTypeAny: |
| NOTREACHED(); |
| return collection.input_reports; |
| } |
| } |
| |
| // Iterates over |collections| to find reports of type |report_type| that should |
| // be protected according to the blocklist rule |entry|. |vendor_id| and |
| // |product_id| are the vendor and product IDs of the device with these reports. |
| // The report IDs of the protected reports are inserted into |protected_ids|. |
| void CheckBlocklistEntry( |
| const HidBlocklist::Entry& entry, |
| HidBlocklist::ReportType report_type, |
| uint16_t vendor_id, |
| uint16_t product_id, |
| const std::vector<mojom::HidCollectionInfoPtr>& collections, |
| std::set<uint8_t>& protected_ids) { |
| DCHECK_NE(report_type, HidBlocklist::kReportTypeAny); |
| if (entry.report_type != HidBlocklist::kReportTypeAny && |
| entry.report_type != report_type) { |
| return; |
| } |
| |
| if (entry.has_vendor_id) { |
| if (entry.vendor_id != vendor_id) |
| return; |
| |
| if (entry.has_product_id && entry.product_id != product_id) |
| return; |
| } |
| |
| for (const auto& collection : collections) { |
| if (entry.has_usage_page) { |
| if (entry.usage_page != collection->usage->usage_page) |
| continue; |
| |
| if (entry.has_usage && entry.usage != collection->usage->usage) |
| continue; |
| } |
| |
| const auto& reports = GetReportsForType(report_type, *collection); |
| for (const auto& report : reports) { |
| if (!entry.has_report_id || entry.report_id == report->report_id) |
| protected_ids.insert(report->report_id); |
| } |
| } |
| } |
| |
| // Returns true if the passed string is exactly |digits| digits long and only |
| // contains valid hexadecimal characters (no leading 0x). |
| bool IsHexComponent(base::StringPiece string, size_t digits) { |
| if (string.length() != digits) |
| 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; |
| } |
| |
| // Returns true if the passed string is "I" (input report), "O" (output report), |
| // "F" (feature report), or "" (any report type). |
| bool IsReportTypeComponent(base::StringPiece string) { |
| return string.empty() || |
| (string.length() == 1 && |
| (string[0] == 'I' || string[0] == 'O' || string[0] == 'F')); |
| } |
| |
| } // namespace |
| |
| // static |
| HidBlocklist& HidBlocklist::Get() { |
| static base::NoDestructor<HidBlocklist> instance; |
| return *instance; |
| } |
| |
| // static |
| bool HidBlocklist::IsDeviceExcluded(const mojom::HidDeviceInfo& device_info) { |
| // A device should only be excluded if all its reports are protected. |
| for (const auto& collection : device_info.collections) { |
| if (device_info.protected_input_report_ids) { |
| for (const auto& report : collection->input_reports) { |
| if (!base::Contains(*device_info.protected_input_report_ids, |
| report->report_id)) { |
| return false; |
| } |
| } |
| } else if (!collection->input_reports.empty()) { |
| return false; |
| } |
| if (device_info.protected_output_report_ids) { |
| for (const auto& report : collection->output_reports) { |
| if (!base::Contains(*device_info.protected_output_report_ids, |
| report->report_id)) { |
| return false; |
| } |
| } |
| } else if (!collection->output_reports.empty()) { |
| return false; |
| } |
| if (device_info.protected_feature_report_ids) { |
| for (const auto& report : collection->feature_reports) { |
| if (!base::Contains(*device_info.protected_feature_report_ids, |
| report->report_id)) { |
| return false; |
| } |
| } |
| } else if (!collection->feature_reports.empty()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| std::vector<uint8_t> HidBlocklist::GetProtectedReportIds( |
| HidBlocklist::ReportType report_type, |
| uint16_t vendor_id, |
| uint16_t product_id, |
| const std::vector<mojom::HidCollectionInfoPtr>& collections) { |
| DCHECK_NE(report_type, ReportType::kReportTypeAny); |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kDisableHidBlocklist)) { |
| return {}; |
| } |
| |
| std::set<uint8_t> protected_ids; |
| for (const auto& entry : kStaticEntries) { |
| CheckBlocklistEntry(entry, report_type, vendor_id, product_id, collections, |
| protected_ids); |
| } |
| for (const auto& entry : dynamic_entries_) { |
| CheckBlocklistEntry(entry, report_type, vendor_id, product_id, collections, |
| protected_ids); |
| } |
| return std::vector<uint8_t>(protected_ids.begin(), protected_ids.end()); |
| } |
| |
| void HidBlocklist::PopulateWithServerProvidedValues() { |
| std::string blocklist_string = variations::GetVariationParamValue( |
| "WebHIDBlocklist", "blocklist_additions"); |
| DLOG(WARNING) << "HID blocklist additions: " << blocklist_string; |
| for (const auto& blocklist_rule : |
| base::SplitStringPiece(blocklist_string, ",", base::TRIM_WHITESPACE, |
| base::SPLIT_WANT_NONEMPTY)) { |
| std::vector<base::StringPiece> components = base::SplitStringPiece( |
| blocklist_rule, ":", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); |
| if (components.size() != 6) { |
| DLOG(WARNING) << "Wrong number of components in HID blocklist rule: " |
| << blocklist_rule; |
| continue; |
| } |
| |
| // The vendor ID, product ID, usage page, and usage must be specified as |
| // either an empty string or a 16-bit hexadecimal value. |
| if ((!components[0].empty() && !IsHexComponent(components[0], 4)) || |
| (!components[1].empty() && !IsHexComponent(components[1], 4)) || |
| (!components[2].empty() && !IsHexComponent(components[2], 4)) || |
| (!components[3].empty() && !IsHexComponent(components[3], 4))) { |
| DLOG(WARNING) << "Bad component format in HID blocklist rule: " |
| << blocklist_rule; |
| continue; |
| } |
| |
| // The report ID must be specified as either an empty string or an 8-bit |
| // hexadecimal value. |
| if (!components[4].empty() && !IsHexComponent(components[4], 2)) { |
| DLOG(WARNING) << "Bad component format in HID blocklist rule: " |
| << blocklist_rule; |
| continue; |
| } |
| |
| // The report type must be specified as either an empty string or a single |
| // character 'I', 'O', or 'F'. |
| if (!components[5].empty() && !IsReportTypeComponent(components[5])) { |
| DLOG(WARNING) << "Bad component format in HID blocklist rule: " |
| << blocklist_rule; |
| continue; |
| } |
| |
| Entry entry = {}; |
| uint32_t int_value; |
| if (!components[0].empty()) { |
| base::HexStringToUInt(components[0], &int_value); |
| entry.has_vendor_id = true; |
| entry.vendor_id = static_cast<uint16_t>(int_value); |
| } |
| if (!components[1].empty()) { |
| base::HexStringToUInt(components[1], &int_value); |
| entry.has_product_id = true; |
| entry.product_id = static_cast<uint16_t>(int_value); |
| } |
| if (!components[2].empty()) { |
| base::HexStringToUInt(components[2], &int_value); |
| entry.has_usage_page = true; |
| entry.usage_page = static_cast<uint16_t>(int_value); |
| } |
| if (!components[3].empty()) { |
| base::HexStringToUInt(components[3], &int_value); |
| entry.has_usage = true; |
| entry.usage = static_cast<uint16_t>(int_value); |
| } |
| if (!components[4].empty()) { |
| base::HexStringToUInt(components[4], &int_value); |
| entry.has_report_id = true; |
| entry.report_id = static_cast<uint16_t>(int_value); |
| } |
| if (components[5] == "I") |
| entry.report_type = HidBlocklist::kReportTypeInput; |
| else if (components[5] == "O") |
| entry.report_type = HidBlocklist::kReportTypeOutput; |
| else if (components[5] == "F") |
| entry.report_type = HidBlocklist::kReportTypeFeature; |
| |
| if (!IsValidBlocklistEntry(entry)) { |
| DLOG(WARNING) << "Ivalid HID blocklist rule: " << blocklist_rule; |
| continue; |
| } |
| |
| dynamic_entries_.push_back(entry); |
| } |
| } |
| |
| void HidBlocklist::ResetToDefaultValuesForTest() { |
| dynamic_entries_.clear(); |
| PopulateWithServerProvidedValues(); |
| } |
| |
| HidBlocklist::HidBlocklist() { |
| #if DCHECK_IS_ON() |
| for (const auto& entry : kStaticEntries) |
| DCHECK(IsValidBlocklistEntry(entry)); |
| #endif |
| } |
| |
| } // namespace device |