Commit eb1d24d3 authored by reillyg's avatar reillyg Committed by Commit bot

Add infrastructure for a USB device blocklist.

This blocklist allows access to particular models of USB device via the
WebUSB API to be blocked via either a binary push or Finch experiment.

Manual testing of this is difficult. I added a real device to the
blocklist and verified that it was not returned by
navigator.usb.getDevices() and was not listed in the chooser.

BUG=674353

Review-Url: https://codereview.chromium.org/2581543002
Cr-Commit-Position: refs/heads/master@{#438981}
parent af29d418
...@@ -1284,6 +1284,8 @@ split_static_library("browser") { ...@@ -1284,6 +1284,8 @@ split_static_library("browser") {
"undo/bookmark_undo_service_factory.h", "undo/bookmark_undo_service_factory.h",
"update_client/chrome_update_query_params_delegate.cc", "update_client/chrome_update_query_params_delegate.cc",
"update_client/chrome_update_query_params_delegate.h", "update_client/chrome_update_query_params_delegate.h",
"usb/usb_blocklist.cc",
"usb/usb_blocklist.h",
"usb/usb_chooser_context.cc", "usb/usb_chooser_context.cc",
"usb/usb_chooser_context.h", "usb/usb_chooser_context.h",
"usb/usb_chooser_context_factory.cc", "usb/usb_chooser_context_factory.cc",
......
// Copyright 2016 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 "chrome/browser/usb/usb_blocklist.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "components/variations/variations_associated_data.h"
#include "device/usb/usb_device.h"
namespace {
static base::LazyInstance<UsbBlocklist>::Leaky g_singleton =
LAZY_INSTANCE_INITIALIZER;
} // namespace
// The == and < operators are necessary to use Entries in std::set.
bool operator==(const UsbBlocklist::Entry& a, const UsbBlocklist::Entry& b) {
return a.vendor_id == b.vendor_id && a.product_id == b.product_id &&
a.version == b.version;
}
bool operator<(const UsbBlocklist::Entry& a, const UsbBlocklist::Entry& b) {
if (a.vendor_id == b.vendor_id) {
if (a.product_id == b.product_id)
return a.version < b.version;
return a.product_id < b.product_id;
}
return a.vendor_id < b.vendor_id;
}
UsbBlocklist::Entry::Entry(uint16_t vendor_id,
uint16_t product_id,
uint16_t version)
: vendor_id(vendor_id), product_id(product_id), version(version) {}
UsbBlocklist::~UsbBlocklist() {}
// static
UsbBlocklist& UsbBlocklist::Get() {
return g_singleton.Get();
}
void UsbBlocklist::Exclude(const Entry& entry) {
blocklisted_devices_.insert(entry);
}
void UsbBlocklist::Exclude(base::StringPiece blocklist_string) {
for (const auto& entry :
base::SplitStringPiece(blocklist_string, ",", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY)) {
std::vector<base::StringPiece> components = base::SplitStringPiece(
entry, ":", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
if (components.size() != 3 || components[0].size() != 4 ||
components[1].size() != 4 || components[2].size() != 4) {
continue;
}
uint32_t vendor_id;
uint32_t product_id;
uint32_t version;
if (!base::HexStringToUInt(components[0], &vendor_id) ||
!base::HexStringToUInt(components[1], &product_id) ||
!base::HexStringToUInt(components[2], &version)) {
continue;
}
Exclude(Entry(vendor_id, product_id, version));
}
}
bool UsbBlocklist::IsExcluded(const Entry& entry) {
return base::ContainsValue(blocklisted_devices_, entry);
}
bool UsbBlocklist::IsExcluded(scoped_refptr<const device::UsbDevice> device) {
return IsExcluded(Entry(device->vendor_id(), device->product_id(),
device->device_version()));
}
void UsbBlocklist::ResetToDefaultValuesForTest() {
blocklisted_devices_.clear();
PopulateWithDefaultValues();
PopulateWithServerProvidedValues();
}
UsbBlocklist::UsbBlocklist() {
PopulateWithDefaultValues();
PopulateWithServerProvidedValues();
}
void UsbBlocklist::PopulateWithDefaultValues() {
// To add a device to the blocklist add an entry here as well as configuring
// a Finch trial so that the blocklist update is pushed out to existing users
// as quickly as possible, e.g.:
//
// Exclude({ 0x18D0, 0x58F0, 0x1BAD });
}
void UsbBlocklist::PopulateWithServerProvidedValues() {
std::string blocklist_string = variations::GetVariationParamValue(
"WebUSBBlocklist", "blocklist_additions");
Exclude(blocklist_string);
}
// Copyright 2016 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.
#ifndef CHROME_BROWSER_USB_USB_BLOCKLIST_H_
#define CHROME_BROWSER_USB_USB_BLOCKLIST_H_
#include <stdint.h>
#include <set>
#include "base/lazy_instance.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/strings/string_piece.h"
namespace device {
class UsbDevice;
}
class UsbBlocklist final {
public:
// An entry in the blocklist. Represents a specific version of a device that
// should not be accessible. These fields correspond to the idVendor,
// idProduct and bcdDevice fields of the device's USB device descriptor.
struct Entry {
Entry(uint16_t vendor_id, uint16_t product_id, uint16_t version);
uint16_t vendor_id;
uint16_t product_id;
uint16_t version;
};
~UsbBlocklist();
// Returns a singleton instance of the blocklist.
static UsbBlocklist& Get();
// Adds a device to the blocklist to be excluded from access.
void Exclude(const Entry&);
// Adds a device to the blocklist by parsing a blocklist string and calling
// Exclude(entry).
//
// The blocklist string must be a comma-separated list of
// idVendor:idProduct:bcdDevice triples, where each member of the triple is a
// 16-bit integer written as exactly 4 hexadecimal digits. The triples may
// be separated by whitespace. Triple components are colon-separated and must
// not have whitespace around the colon.
//
// Invalid entries in the comma-separated list will be ignored.
//
// Example:
// "1000:001C:0100, 1000:001D:0101, 123:ignored:0"
void Exclude(base::StringPiece blocklist_string);
// Returns if a device is excluded from access.
bool IsExcluded(const Entry&);
bool IsExcluded(scoped_refptr<const device::UsbDevice>);
// Size of the blocklist.
size_t size() { return blocklisted_devices_.size(); }
// Reload the blocklist for testing purposes.
void ResetToDefaultValuesForTest();
private:
// friend LazyInstance to permit access to private constructor.
friend base::DefaultLazyInstanceTraits<UsbBlocklist>;
UsbBlocklist();
void PopulateWithDefaultValues();
// Populates the blocklist with values obtained dynamically from a server,
// able to be updated without shipping new executable versions.
void PopulateWithServerProvidedValues();
// Set of blocklist entries.
std::set<Entry> blocklisted_devices_;
DISALLOW_COPY_AND_ASSIGN(UsbBlocklist);
};
#endif // CHROME_BROWSER_USB_USB_BLOCKLIST_H_
// Copyright 2016 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 "base/metrics/field_trial.h"
#include "chrome/browser/usb/usb_blocklist.h"
#include "components/variations/variations_associated_data.h"
#include "testing/gtest/include/gtest/gtest.h"
class UsbBlocklistTest : public testing::Test {
public:
UsbBlocklistTest()
: field_trial_list_(new base::FieldTrialList(nullptr)),
list_(UsbBlocklist::Get()) {}
void TearDown() override {
// Because UsbBlocklist is a singleton it must be cleared after tests run
// to prevent leakage between tests.
field_trial_list_.reset();
list_.ResetToDefaultValuesForTest();
}
std::unique_ptr<base::FieldTrialList> field_trial_list_;
UsbBlocklist& list_;
};
TEST_F(UsbBlocklistTest, BasicExclusions) {
list_.Exclude({0x18D1, 0x58F0, 0x0100});
EXPECT_TRUE(list_.IsExcluded({0x18D1, 0x58F0, 0x0100}));
EXPECT_FALSE(list_.IsExcluded({0x18D1, 0x58F1, 0x0100}));
EXPECT_FALSE(list_.IsExcluded({0x18D0, 0x58F0, 0x0100}));
EXPECT_FALSE(list_.IsExcluded({0x18D1, 0x58F0, 0x0200}));
}
TEST_F(UsbBlocklistTest, StringsWithNoValidEntries) {
size_t previous_list_size = list_.size();
list_.Exclude("");
list_.Exclude("~!@#$%^&*()-_=+[]{}/*-");
list_.Exclude(":");
list_.Exclude("::");
list_.Exclude(",");
list_.Exclude(",,");
list_.Exclude(",::,");
list_.Exclude("1:2:3");
list_.Exclude("18D1:2:3000");
list_.Exclude("☯");
EXPECT_EQ(previous_list_size, list_.size());
}
TEST_F(UsbBlocklistTest, StringsWithOneValidEntry) {
size_t previous_list_size = list_.size();
list_.Exclude("18D1:58F0:0101");
EXPECT_EQ(++previous_list_size, list_.size());
EXPECT_TRUE(list_.IsExcluded({0x18D1, 0x58F0, 0x0101}));
list_.Exclude(" 18D1:58F0:0200 ");
EXPECT_EQ(++previous_list_size, list_.size());
EXPECT_TRUE(list_.IsExcluded({0x18D1, 0x58F0, 0x0200}));
list_.Exclude(", 18D1:58F0:0201, ");
EXPECT_EQ(++previous_list_size, list_.size());
EXPECT_TRUE(list_.IsExcluded({0x18D1, 0x58F0, 0x0201}));
list_.Exclude("18D1:58F0:0202, 0000:1:0000");
EXPECT_EQ(++previous_list_size, list_.size());
EXPECT_TRUE(list_.IsExcluded({0x18D1, 0x58F0, 0x0202}));
}
TEST_F(UsbBlocklistTest, ServerProvidedBlocklist) {
if (base::FieldTrialList::TrialExists("WebUSBBlocklist")) {
// This code checks to make sure that when a field trial is launched it
// still contains our test data.
LOG(INFO) << "WebUSBBlocklist field trial already configured.";
ASSERT_NE(variations::GetVariationParamValue("WebUSBBlocklist",
"blocklist_additions")
.find("18D1:58F0:1BAD"),
std::string::npos)
<< "ERROR: A WebUSBBlocklist field trial has been configured in\n"
"testing/variations/fieldtrial_testing_config.json and must\n"
"include this test's excluded device ID '18D1:58F0:1BAD' in\n"
"blocklist_additions.\n";
} else {
LOG(INFO) << "Creating WebUSBBlocklist field trial for test.";
// Create a field trial with test parameter.
std::map<std::string, std::string> params;
params["blocklist_additions"] = "18D1:58F0:1BAD";
variations::AssociateVariationParams("WebUSBBlocklist", "TestGroup",
params);
base::FieldTrialList::CreateFieldTrial("WebUSBBlocklist", "TestGroup");
// Refresh the blocklist based on the new field trial.
list_.ResetToDefaultValuesForTest();
}
EXPECT_TRUE(list_.IsExcluded({0x18D1, 0x58F0, 0x1BAD}));
EXPECT_FALSE(list_.IsExcluded({0x18D1, 0x58F0, 0x0100}));
}
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/scoped_tabbed_browser_displayer.h" #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
#include "chrome/browser/usb/usb_blocklist.h"
#include "chrome/browser/usb/usb_chooser_context.h" #include "chrome/browser/usb/usb_chooser_context.h"
#include "chrome/browser/usb/usb_chooser_context_factory.h" #include "chrome/browser/usb/usb_chooser_context_factory.h"
#include "chrome/browser/usb/web_usb_histograms.h" #include "chrome/browser/usb/web_usb_histograms.h"
...@@ -223,6 +224,7 @@ void UsbChooserController::GotUsbDeviceList( ...@@ -223,6 +224,7 @@ void UsbChooserController::GotUsbDeviceList(
bool UsbChooserController::DisplayDevice( bool UsbChooserController::DisplayDevice(
scoped_refptr<device::UsbDevice> device) const { scoped_refptr<device::UsbDevice> device) const {
return device::UsbDeviceFilter::MatchesAny(device, filters_) && return device::UsbDeviceFilter::MatchesAny(device, filters_) &&
!UsbBlocklist::Get().IsExcluded(device) &&
(base::CommandLine::ForCurrentProcess()->HasSwitch( (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableWebUsbSecurity) || switches::kDisableWebUsbSecurity) ||
device::FindInWebUsbAllowedOrigins( device::FindInWebUsbAllowedOrigins(
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "base/command_line.h" #include "base/command_line.h"
#include "base/stl_util.h" #include "base/stl_util.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/browser/usb/usb_blocklist.h"
#include "chrome/browser/usb/usb_chooser_context.h" #include "chrome/browser/usb/usb_chooser_context.h"
#include "chrome/browser/usb/usb_chooser_context_factory.h" #include "chrome/browser/usb/usb_chooser_context_factory.h"
#include "chrome/browser/usb/usb_tab_helper.h" #include "chrome/browser/usb/usb_tab_helper.h"
...@@ -59,6 +60,10 @@ bool WebUSBPermissionProvider::HasDevicePermission( ...@@ -59,6 +60,10 @@ bool WebUSBPermissionProvider::HasDevicePermission(
content::RenderFrameHost* render_frame_host, content::RenderFrameHost* render_frame_host,
scoped_refptr<const device::UsbDevice> device) { scoped_refptr<const device::UsbDevice> device) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (UsbBlocklist::Get().IsExcluded(device))
return false;
WebContents* web_contents = WebContents* web_contents =
WebContents::FromRenderFrameHost(render_frame_host); WebContents::FromRenderFrameHost(render_frame_host);
GURL embedding_origin = GURL embedding_origin =
......
...@@ -3685,6 +3685,7 @@ test("unit_tests") { ...@@ -3685,6 +3685,7 @@ test("unit_tests") {
"../browser/ui/window_sizer/window_sizer_common_unittest.cc", "../browser/ui/window_sizer/window_sizer_common_unittest.cc",
"../browser/ui/window_sizer/window_sizer_unittest.cc", "../browser/ui/window_sizer/window_sizer_unittest.cc",
"../browser/ui/zoom/zoom_controller_unittest.cc", "../browser/ui/zoom/zoom_controller_unittest.cc",
"../browser/usb/usb_blocklist_unittest.cc",
"../browser/usb/usb_chooser_context_unittest.cc", "../browser/usb/usb_chooser_context_unittest.cc",
"../browser/usb/usb_chooser_controller_unittest.cc", "../browser/usb/usb_chooser_controller_unittest.cc",
"../browser/usb/web_usb_detector_unittest.cc", "../browser/usb/web_usb_detector_unittest.cc",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment