Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient policy enforcement in Bluetooth
DescriptionInsufficient policy enforcement in Bluetooth
ComponentBluetooth
Bug ClassLogic Error
Tracker503912196
Fix commita0b55d797bbc (chromium/src) +143/-11
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Files Changed

  • content/browser/bluetooth/web_bluetooth_service_impl.cc
  • content/browser/bluetooth/web_bluetooth_service_impl.h
  • content/browser/bluetooth/web_bluetooth_service_impl_unittest.cc
  • third_party/blink/web_tests/external/wpt/bluetooth/resources/bluetooth-fake-devices.js
From a0b55d797bbc3d7dfd31113f4957df873d50d20b Mon Sep 17 00:00:00 2001
From: Rob Pitkin <[email protected]>
Date: Fri, 15 May 2026 16:40:45 -0700
Subject: [PATCH] bluetooth: Fix missing blocklist check for characteristic in descriptors

Web Bluetooth fails to check the GATT blocklist for a descriptor's
parent characteristic during read and write operations. This allows
websites to bypass security restrictions and interact with descriptors
of protected characteristics.

This CL adds checks in `RemoteDescriptorReadValue` and
`RemoteDescriptorWriteValue` to ensure that the parent characteristic is
also checked against the GATT blocklist before allowing read or write
operations on its descriptors.

Unit tests are added to verify that reads and writes are blocked when
the parent characteristic is blocklisted.

Bug: 503912196
Change-Id: If62e10d5ac66ed414aec1a3a4af346133a584374
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7837659
Commit-Queue: Rob Pitkin <[email protected]>
Reviewed-by: Matt Reynolds <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1631633}
---

diff --git a/content/browser/bluetooth/web_bluetooth_service_impl.cc b/content/browser/bluetooth/web_bluetooth_service_impl.cc
index a075173..deea337 100644
--- a/content/browser/bluetooth/web_bluetooth_service_impl.cc
+++ b/content/browser/bluetooth/web_bluetooth_service_impl.cc
@@ -1383,7 +1383,9 @@
   }
 
   if (BluetoothBlocklist::Get().IsExcludedFromReads(
-          query_result.descriptor->GetUUID())) {
+          query_result.descriptor->GetUUID()) ||
+      BluetoothBlocklist::Get().IsExcludedFromReads(
+          query_result.characteristic->GetUUID())) {
     std::move(callback).Run(blink::mojom::WebBluetoothResult::BLOCKLISTED_READ,
                             /*value=*/{});
     return;
@@ -1423,7 +1425,9 @@
   }
 
   if (BluetoothBlocklist::Get().IsExcludedFromWrites(
-          query_result.descriptor->GetUUID())) {
+          query_result.descriptor->GetUUID()) ||
+      BluetoothBlocklist::Get().IsExcludedFromWrites(
+          query_result.characteristic->GetUUID())) {
     std::move(callback).Run(
         blink::mojom::WebBluetoothResult::BLOCKLISTED_WRITE);
     return;
diff --git a/content/browser/bluetooth/web_bluetooth_service_impl.h b/content/browser/bluetooth/web_bluetooth_service_impl.h
index c414992..73196dd4 100644
--- a/content/browser/bluetooth/web_bluetooth_service_impl.h
+++ b/content/browser/bluetooth/web_bluetooth_service_impl.h
@@ -178,6 +178,12 @@
                            EmulatedAdapterRemovalRestoresOriginalAdapter);
   FRIEND_TEST_ALL_PREFIXES(WebBluetoothServiceImplTest,
                            ServiceDestroyedDuringAdapterAcquisition);
+  FRIEND_TEST_ALL_PREFIXES(
+      WebBluetoothServiceImplTest,
+      RemoteDescriptorReadValue_ParentCharacteristicBlocklisted);
+  FRIEND_TEST_ALL_PREFIXES(
+      WebBluetoothServiceImplTest,
+      RemoteDescriptorWriteValue_ParentCharacteristicBlocklisted);
 
 #if PAIR_BLUETOOTH_ON_DEMAND()
   FRIEND_TEST_ALL_PREFIXES(WebBluetoothServiceImplTest,
diff --git a/content/browser/bluetooth/web_bluetooth_service_impl_unittest.cc b/content/browser/bluetooth/web_bluetooth_service_impl_unittest.cc
index 42523def..4880d79 100644
--- a/content/browser/bluetooth/web_bluetooth_service_impl_unittest.cc
+++ b/content/browser/bluetooth/web_bluetooth_service_impl_unittest.cc
@@ -32,6 +32,7 @@
 #include "device/bluetooth/test/mock_bluetooth_adapter.h"
 #include "device/bluetooth/test/mock_bluetooth_device.h"
 #include "device/bluetooth/test/mock_bluetooth_gatt_characteristic.h"
+#include "device/bluetooth/test/mock_bluetooth_gatt_descriptor.h"
 #include "device/bluetooth/test/mock_bluetooth_gatt_notify_session.h"
 #include "device/bluetooth/test/mock_bluetooth_gatt_service.h"
 #include "mojo/public/cpp/bindings/associated_receiver.h"
@@ -96,6 +97,8 @@
 const device::BluetoothUUID kBatteryServiceUUID(kBatteryServiceUUIDString);
 const device::BluetoothUUID kBatteryLevelCharacteristicUUID(
     "00002a19-0000-1000-8000-00805f9b34fb");
+const device::BluetoothUUID kCharacteristicUserDescriptionDescriptorUUID(
+    "00002901-0000-1000-8000-00805f9b34fb");
 const BluetoothDeviceBundleData battery_device_bundle_data = {
     kBatteryServiceId, kBatteryLevelCharacteristicId, kBatteryServiceUUID,
     kBatteryLevelCharacteristicUUID, kTestCharacteristicProperties};
@@ -559,6 +562,7 @@
     heart_rate_device_bundle_.reset();
     service_ptr_ = nullptr;
     SetBrowserClientForTesting(old_browser_client_);
+    BluetoothBlocklist::Get().ResetToDefaultValuesForTest();
     RenderViewHostImplTestHarness::TearDown();
   }
 
@@ -1442,4 +1446,95 @@
   EXPECT_CALL(*adapter_, StopScan).Times(1);
 }
 
+TEST_F(WebBluetoothServiceImplTest,
+       RemoteDescriptorReadValue_ParentCharacteristicBlocklisted) {
+  RegisterTestCharacteristic();
+
+  FakeBluetoothCharacteristic& test_characteristic =
+      battery_device_bundle().characteristic();
+
+  auto mock_descriptor =
+      std::make_unique<testing::NiceMock<device::MockBluetoothGattDescriptor>>(
+          &test_characteristic, "test_descriptor_id",
+          kCharacteristicUserDescriptionDescriptorUUID,
+          device::BluetoothRemoteGattCharacteristic::PERMISSION_NONE);
+
+  test_characteristic.AddMockDescriptor(std::move(mock_descriptor));
+
+  base::test::TestFuture<
+      WebBluetoothResult,
+      std::optional<
+          std::vector<blink::mojom::WebBluetoothRemoteGATTDescriptorPtr>>>
+      get_descriptors_future;
+  service_ptr_->RemoteCharacteristicGetDescriptors(
+      test_characteristic.GetIdentifier(),
+      WebBluetoothGATTQueryQuantity::SINGLE, std::nullopt,
+      get_descriptors_future.GetCallback());
+
+  EXPECT_EQ(get_descriptors_future.Get<0>(), WebBluetoothResult::SUCCESS);
+
+  const auto& descriptors = get_descriptors_future.Get<1>();
+  ASSERT_TRUE(descriptors.has_value());
+  ASSERT_FALSE(descriptors->empty());
+  std::string descriptor_instance_id = descriptors->at(0)->instance_id;
+
+  BluetoothBlocklist::Get().Add(test_characteristic.GetUUID(),
+                                BluetoothBlocklist::Value::EXCLUDE_READS);
+
+  base::test::TestFuture<WebBluetoothResult, std::vector<uint8_t>> read_future;
+  service_ptr_->RemoteDescriptorReadValue(
+      descriptor_instance_id,
+      base::BindLambdaForTesting(
+          [&read_future](WebBluetoothResult result,
+                         base::span<const uint8_t> value) {
+            read_future.SetValue(
+                result, std::vector<uint8_t>(value.begin(), value.end()));
+          }));
+
+  EXPECT_EQ(read_future.Get<0>(), WebBluetoothResult::BLOCKLISTED_READ);
+}
+
+TEST_F(WebBluetoothServiceImplTest,
+       RemoteDescriptorWriteValue_ParentCharacteristicBlocklisted) {
+  RegisterTestCharacteristic();
+
+  FakeBluetoothCharacteristic& test_characteristic =
+      battery_device_bundle().characteristic();
+
+  auto mock_descriptor =
+      std::make_unique<testing::NiceMock<device::MockBluetoothGattDescriptor>>(
+          &test_characteristic, "test_descriptor_id",
+          kCharacteristicUserDescriptionDescriptorUUID,
+          device::BluetoothRemoteGattCharacteristic::PERMISSION_NONE);
+
+  test_characteristic.AddMockDescriptor(std::move(mock_descriptor));
+
+  base::test::TestFuture<
+      WebBluetoothResult,
+      std::optional<
+          std::vector<blink::mojom::WebBluetoothRemoteGATTDescriptorPtr>>>
+      get_descriptors_future;
+  service_ptr_->RemoteCharacteristicGetDescriptors(
+      test_characteristic.GetIdentifier(),
+      WebBluetoothGATTQueryQuantity::SINGLE, std::nullopt,
+      get_descriptors_future.GetCallback());
+
+  EXPECT_EQ(get_descriptors_future.Get<0>(), WebBluetoothResult::SUCCESS);
+
+  const auto& descriptors = get_descriptors_future.Get<1>();
+  ASSERT_TRUE(descriptors.has_value());
+  ASSERT_FALSE(descriptors->empty());
+  std::string descriptor_instance_id = descriptors->at(0)->instance_id;
+
+  BluetoothBlocklist::Get().Add(test_characteristic.GetUUID(),
+                                BluetoothBlocklist::Value::EXCLUDE_WRITES);
+
+  std::vector<uint8_t> value = {1, 2, 3};
+  base::test::TestFuture<WebBluetoothResult> write_future;
+  service_ptr_->RemoteDescriptorWriteValue(descriptor_instance_id, value,
+                                           write_future.GetCallback());
+
+  EXPECT_EQ(write_future.Get(), WebBluetoothResult::BLOCKLISTED_WRITE);
+}
+
 }  // namespace content
diff --git a/third_party/blink/web_tests/external/wpt/bluetooth/resources/bluetooth-fake-devices.js b/third_party/blink/web_tests/external/wpt/bluetooth/resources/bluetooth-fake-devices.js
index c503bf7..37e126df 100644
--- a/third_party/blink/web_tests/external/wpt/bluetooth/resources/bluetooth-fake-devices.js
+++ b/third_party/blink/web_tests/external/wpt/bluetooth/resources/bluetooth-fake-devices.js
@@ -29,6 +29,7 @@
     'bad1c9a2-9a5b-4015-8b60-1579bbbf2135';
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/bluetooth/web_bluetooth_service_impl_unittest.cc b/content/browser/bluetooth/web_bluetooth_service_impl_unittest.cc
index 42523def..4880d79 100644
--- a/content/browser/bluetooth/web_bluetooth_service_impl_unittest.cc
+++ b/content/browser/bluetooth/web_bluetooth_service_impl_unittest.cc
@@ -32,6 +32,7 @@
 #include "device/bluetooth/test/mock_bluetooth_adapter.h"
 #include "device/bluetooth/test/mock_bluetooth_device.h"
 #include "device/bluetooth/test/mock_bluetooth_gatt_characteristic.h"
+#include "device/bluetooth/test/mock_bluetooth_gatt_descriptor.h"
 #include "device/bluetooth/test/mock_bluetooth_gatt_notify_session.h"
 #include "device/bluetooth/test/mock_bluetooth_gatt_service.h"
 #include "mojo/public/cpp/bindings/associated_receiver.h"
@@ -96,6 +97,8 @@
 const device::BluetoothUUID kBatteryServiceUUID(kBatteryServiceUUIDString);
 const device::BluetoothUUID kBatteryLevelCharacteristicUUID(
     "00002a19-0000-1000-8000-00805f9b34fb");
+const device::BluetoothUUID kCharacteristicUserDescriptionDescriptorUUID(
+    "00002901-0000-1000-8000-00805f9b34fb");
 const BluetoothDeviceBundleData battery_device_bundle_data = {
     kBatteryServiceId, kBatteryLevelCharacteristicId, kBatteryServiceUUID,
     kBatteryLevelCharacteristicUUID, kTestCharacteristicProperties};
@@ -559,6 +562,7 @@
     heart_rate_device_bundle_.reset();
     service_ptr_ = nullptr;
     SetBrowserClientForTesting(old_browser_client_);
+    BluetoothBlocklist::Get().ResetToDefaultValuesForTest();
     RenderViewHostImplTestHarness::TearDown();
   }
 
@@ -1442,4 +1446,95 @@
   EXPECT_CALL(*adapter_, StopScan).Times(1);
 }
 
+TEST_F(WebBluetoothServiceImplTest,
+       RemoteDescriptorReadValue_ParentCharacteristicBlocklisted) {
+  RegisterTestCharacteristic();
+
+  FakeBluetoothCharacteristic& test_characteristic =
+      battery_device_bundle().characteristic();
+
+  auto mock_descriptor =
+      std::make_unique<testing::NiceMock<device::MockBluetoothGattDescriptor>>(
+          &test_characteristic, "test_descriptor_id",
+          kCharacteristicUserDescriptionDescriptorUUID,
+          device::BluetoothRemoteGattCharacteristic::PERMISSION_NONE);
+
+  test_characteristic.AddMockDescriptor(std::move(mock_descriptor));
+
+  base::test::TestFuture<
+      WebBluetoothResult,
+      std::optional<
+          std::vector<blink::mojom::WebBluetoothRemoteGATTDescriptorPtr>>>
+      get_descriptors_future;
+  service_ptr_->RemoteCharacteristicGetDescriptors(
+      test_characteristic.GetIdentifier(),
+      WebBluetoothGATTQueryQuantity::SINGLE, std::nullopt,
+      get_descriptors_future.GetCallback());
+
+  EXPECT_EQ(get_descriptors_future.Get<0>(), WebBluetoothResult::SUCCESS);
+
+  const auto& descriptors = get_descriptors_future.Get<1>();
+  ASSERT_TRUE(descriptors.has_value());
+  ASSERT_FALSE(descriptors->empty());
+  std::string descriptor_instance_id = descriptors->at(0)->instance_id;
+
+  BluetoothBlocklist::Get().Add(test_characteristic.GetUUID(),
+                                BluetoothBlocklist::Value::EXCLUDE_READS);
+
+  base::test::TestFuture<WebBluetoothResult, std::vector<uint8_t>> read_future;
+  service_ptr_->RemoteDescriptorReadValue(
+      descriptor_instance_id,
+      base::BindLambdaForTesting(
+          [&read_future](WebBluetoothResult result,
+                         base::span<const uint8_t> value) {
+            read_future.SetValue(
+                result, std::vector<uint8_t>(value.begin(), value.end()));
+          }));
+
+  EXPECT_EQ(read_future.Get<0>(), WebBluetoothResult::BLOCKLISTED_READ);
+}
+
+TEST_F(WebBluetoothServiceImplTest,
+       RemoteDescriptorWriteValue_ParentCharacteristicBlocklisted) {
+  RegisterTestCharacteristic();
+
+  FakeBluetoothCharacteristic& test_characteristic =
+      battery_device_bundle().characteristic();
+
+  auto mock_descriptor =
+      std::make_unique<testing::NiceMock<device::MockBluetoothGattDescriptor>>(
+          &test_characteristic, "test_descriptor_id",
+          kCharacteristicUserDescriptionDescriptorUUID,
+          device::BluetoothRemoteGattCharacteristic::PERMISSION_NONE);
+
+  test_characteristic.AddMockDescriptor(std::move(mock_descriptor));
+
+  base::test::TestFuture<
+      WebBluetoothResult,
+      std::optional<
+          std::vector<blink::mojom::WebBluetoothRemoteGATTDescriptorPtr>>>
+      get_descriptors_future;
+  service_ptr_->RemoteCharacteristicGetDescriptors(
+      test_characteristic.GetIdentifier(),
+      WebBluetoothGATTQueryQuantity::SINGLE, std::nullopt,
+      get_descriptors_future.GetCallback());
+
+  EXPECT_EQ(get_descriptors_future.Get<0>(), WebBluetoothResult::SUCCESS);
+
+  const auto& descriptors = get_descriptors_future.Get<1>();
+  ASSERT_TRUE(descriptors.has_value());
+  ASSERT_FALSE(descriptors->empty());
+  std::string descriptor_instance_id = descriptors->at(0)->instance_id;
+
+  BluetoothBlocklist::Get().Add(test_characteristic.GetUUID(),
+                                BluetoothBlocklist::Value::EXCLUDE_WRITES);
+
+  std::vector<uint8_t> value = {1, 2, 3};
+  base::test::TestFuture<WebBluetoothResult> write_future;
+  service_ptr_->RemoteDescriptorWriteValue(descriptor_instance_id, value,
+                                           write_future.GetCallback());
+
+  EXPECT_EQ(write_future.Get(), WebBluetoothResult::BLOCKLISTED_WRITE);
+}
+
 }  // namespace content
diff --git a/third_party/blink/web_tests/external/wpt/bluetooth/resources/bluetooth-fake-devices.js b/third_party/blink/web_tests/external/wpt/bluetooth/resources/bluetooth-fake-devices.js
index c503bf7..37e126df 100644
--- a/third_party/blink/web_tests/external/wpt/bluetooth/resources/bluetooth-fake-devices.js
+++ b/third_party/blink/web_tests/external/wpt/bluetooth/resources/bluetooth-fake-devices.js
@@ -29,6 +29,7 @@
     'bad1c9a2-9a5b-4015-8b60-1579bbbf2135';
 var request_disconnection_characteristic_uuid =
     '01d7d88a-7451-419f-aeb8-d65e7b9277af';
+var heart_rate_measurement_uuid = '00002a37-0000-1000-8000-00805f9b34fb';
 
 /* Descriptor UUIDs */
 var blocklist_test_descriptor_uuid = 'bad2ddcf-60db-45cd-bef9-fd72b153cf7c';
@@ -610,12 +611,25 @@
  *         objects.
  */
 async function getBlocklistExcludeReadsDescriptor() {
-  let result = await getBlocklistExcludeWritesCharacteristic();
-  let descriptor = await result.characteristic.getDescriptor(
-      blocklist_exclude_reads_descriptor_uuid);
+  let result = await getBlocklistTestService();
+
+  let fake_heart_rate_characteristic =
+      await result.fake_service.addFakeCharacteristic({
+        uuid: heart_rate_measurement_uuid,
+        properties: ['read', 'write'],
+      });
+
+  let fake_descriptor = await fake_heart_rate_characteristic.addFakeDescriptor(
+      {uuid: blocklist_exclude_reads_descriptor_uuid});
+
+  let characteristic = await result.service.getCharacteristic(heart_rate_measurement_uuid);
+  let descriptor = await characteristic.getDescriptor(blocklist_exclude_reads_descriptor_uuid);
+
   return Object.assign(result, {
+    characteristic,
+    fake_characteristic: fake_heart_rate_characteristic,
     descriptor,
-    fake_descriptor: result.fake_blocklist_exclude_reads_descriptor
+    fake_descriptor
   });
 }
 
@@ -641,12 +655,25 @@
  *         objects.
  */
 async function getBlocklistExcludeWritesDescriptor() {
-  let result = await getBlocklistExcludeWritesCharacteristic();
-  let descriptor = await result.characteristic.getDescriptor(
-      'gatt.client_characteristic_configuration');
+  let result = await getBlocklistTestService();
+
+  let fake_heart_rate_characteristic =
+      await result.fake_service.addFakeCharacteristic({
+        uuid: heart_rate_measurement_uuid,
+        properties: ['read', 'write'],
+      });
+
+  let fake_descriptor = await fake_heart_rate_characteristic.addFakeDescriptor(
+      {uuid: 'gatt.client_characteristic_configuration'});
+
+  let characteristic = await result.service.getCharacteristic(heart_rate_measurement_uuid);
+  let descriptor = await characteristic.getDescriptor('gatt.client_characteristic_configuration');
+
   return Object.assign(result, {
-    descriptor: descriptor,
-    fake_descriptor: result.fake_blocklist_exclude_writes_descriptor,
+    characteristic,
+    fake_characteristic: fake_heart_rate_characteristic,
+    descriptor,
+    fake_descriptor
   });
 }
Loading diff…

Original Bug Report

reported by [email protected]

Missing GATT blocklist check for parent characteristic in Web Bluetooth descriptors

Flapjack, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: The Web Bluetooth implementation fails to check the GATT blocklist for a descriptor’s parent characteristic during read and write operations. This allows websites to bypass security restrictions and interact with descriptors of protected characteristics, violating the Web Bluetooth specification.

Affected files:

  • content/browser/bluetooth/web_bluetooth_service_impl.cc

Estimated timestamp from git blame: 2024-10-23

Summary

The GATT blocklist in Web Bluetooth is designed to restrict access to sensitive services, characteristics, and descriptors. However, the implementation of GATT descriptor read and write operations in WebBluetoothServiceImpl fails to verify the blocklist status of the descriptor’s parent characteristic. This omission allows a malicious site to read or write descriptors associated with a partially blocked characteristic, bypassing the security and privacy protections intended by the GATT blocklist.

Technical Details

In content/browser/bluetooth/web_bluetooth_service_impl.cc, the methods RemoteDescriptorReadValue and RemoteDescriptorWriteValue handle GATT descriptor interactions.

According to the Web Bluetooth specification:

  • For readValue() on a descriptor (§ 13.5): “If characteristic is in the Blocked Characteristic for Read UUIDs list, then return a promise rejected with a ‘SecurityError’ DOMException.”
  • For writeValue() on a descriptor (§ 13.5): “If characteristic is in the Blocked Characteristic for Write UUIDs list, then return a promise rejected with a ‘SecurityError’ DOMException.”

The current implementation only checks whether the descriptor’s own UUID is in the blocklist, ignoring the status of the characteristic that owns it.

Vulnerable Code Snippets

In WebBluetoothServiceImpl::RemoteDescriptorReadValue:

1378:   if (BluetoothBlocklist::Get().IsExcludedFromReads(
1379:           query_result.descriptor->GetUUID())) {
1380:     std::move(callback).Run(blink::mojom::WebBluetoothResult::BLOCKLISTED_READ,
1381:                             /*value=*/{});
1382:     return;
1383:   }

In WebBluetoothServiceImpl::RemoteDescriptorWriteValue:

1418:   if (BluetoothBlocklist::Get().IsExcludedFromWrites(
1419:           query_result.descriptor->GetUUID())) {
1420:     std::move(callback).Run(
1421:         blink::mojom::WebBluetoothResult::BLOCKLISTED_WRITE);
1422:     return;
1423:   }

Because characteristics entirely excluded from the blocklist (marked with Value::EXCLUDE) are filtered during discovery (WebBluetoothServiceImpl::RemoteServiceGetCharacteristics), they are unaffected. However, partially restricted characteristics (e.g., those marked with Value::EXCLUDE_READS or Value::EXCLUDE_WRITES) remain discoverable by the renderer, allowing an attacker to request their descriptors.

Potential Attack Steps

These are potential steps an attacker would follow to trigger the vulnerability:

  1. An attacker hosts a malicious website and tricks a user into pairing a Bluetooth device that exposes a partially blocklisted characteristic (e.g., 2a02 Peripheral Privacy Flag, which is EXCLUDE_WRITES).
  2. The attacker’s JavaScript connects to the GATT server and requests the partially blocklisted characteristic (e.g., service.getCharacteristic('2a02')). This succeeds because it is not fully excluded.
  3. The script retrieves the descriptors associated with this characteristic by calling characteristic.getDescriptors().
  4. The script attempts to read or write to one of these descriptors (e.g., descriptor.writeValue(new Uint8Array([...]))).
  5. The browser verifies the descriptor’s UUID against the blocklist, but fails to check the parent characteristic’s UUID.
  6. The browser allows the write operation, successfully bypassing the GATT blocklist restriction.

Suggested Fix

In WebBluetoothServiceImpl::RemoteDescriptorReadValue and WebBluetoothServiceImpl::RemoteDescriptorWriteValue, add a check for the parent characteristic’s blocklist status before allowing the operation.

For RemoteDescriptorReadValue:

  if (BluetoothBlocklist::Get().IsExcludedFromReads(
          query_result.descriptor->GetUUID()) ||
      BluetoothBlocklist::Get().IsExcludedFromReads(
          query_result.characteristic->GetUUID())) {
    std::move(callback).Run(blink::mojom::WebBluetoothResult::BLOCKLISTED_READ,
                            /*value=*/{});
    return;
  }

For RemoteDescriptorWriteValue:

  if (BluetoothBlocklist::Get().IsExcludedFromWrites(
          query_result.descriptor->GetUUID()) ||
      BluetoothBlocklist::Get().IsExcludedFromWrites(
          query_result.characteristic->GetUUID())) {
    std::move(callback).Run(
        blink::mojom::WebBluetoothResult::BLOCKLISTED_WRITE);
    return;
  }

Evaluated with Chrome root at commit: c0eb5541aebfa4ea08806eaf6e94bcc69f87ab2f


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker