Chrome · Bluetooth
CVE-2026-17986
Logic Error in Bluetooth
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/bluetooth/web_bluetooth_service_impl_unittest.cc |
modified | |
TEST_Fcontent/browser/bluetooth/web_bluetooth_service_impl_unittest.cc |
modified |
Files Changed
content/browser/bluetooth/web_bluetooth_service_impl.cccontent/browser/bluetooth/web_bluetooth_service_impl_unittest.cc
Patch
From 1c56c2b5dc9a01bdd62896a2bfd5e3394a7d1acc Mon Sep 17 00:00:00 2001
From: Alvin Ji <alvinji@chromium.org>
Date: Tue, 16 Jun 2026 17:33:59 -0700
Subject: [PATCH] bluetooth: Verify bluetooth is allowed in ForgetDevice
This CL adds the missing GetBluetoothAllowed() check to
WebBluetoothServiceImpl::ForgetDevice. This prevents compromised
renderers from bypassing Permissions-Policy or MayUseBluetooth checks by
calling this entry point.
Change-Id: I02c89437b7e5f7ef9fa7c3cf2a6a72bbeb6906a1
Bug: 519981896
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7904063
Reviewed-by: Matt Reynolds <mattreynolds@chromium.org>
Commit-Queue: Alvin Ji <alvinji@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1648006}
---
diff --git a/content/browser/bluetooth/web_bluetooth_service_impl.cc b/content/browser/bluetooth/web_bluetooth_service_impl.cc
index ccc16bb8..87d0e0f 100644
--- a/content/browser/bluetooth/web_bluetooth_service_impl.cc
+++ b/content/browser/bluetooth/web_bluetooth_service_impl.cc
@@ -855,6 +855,10 @@
void WebBluetoothServiceImpl::ForgetDevice(
const blink::WebBluetoothDeviceId& device_id,
ForgetDeviceCallback callback) {
+ if (GetBluetoothAllowed() != blink::mojom::WebBluetoothResult::SUCCESS) {
+ std::move(callback).Run();
+ return;
+ }
CHECK(back_forward_cache_feature_handle_.IsValid());
if (!base::FeatureList::IsEnabled(
features::kWebBluetoothNewPermissionsBackend)) {
diff --git a/content/browser/bluetooth/web_bluetooth_service_impl_unittest.cc b/content/browser/bluetooth/web_bluetooth_service_impl_unittest.cc
index 4880d79..be04cdd 100644
--- a/content/browser/bluetooth/web_bluetooth_service_impl_unittest.cc
+++ b/content/browser/bluetooth/web_bluetooth_service_impl_unittest.cc
@@ -372,6 +372,23 @@
has_device_permission_ = value;
}
+ bool MayUseBluetooth(RenderFrameHost* frame) override {
+ return may_use_bluetooth_;
+ }
+
+ void set_may_use_bluetooth(bool value) { may_use_bluetooth_ = value; }
+
+ AllowWebBluetoothResult AllowWebBluetooth(
+ content::BrowserContext* browser_context,
+ const url::Origin& requesting_origin,
+ const url::Origin& embedding_origin) override {
+ return allow_web_bluetooth_;
+ }
+
+ void set_allow_web_bluetooth(AllowWebBluetoothResult value) {
+ allow_web_bluetooth_ = value;
+ }
+
void RunBluetoothScanningPromptEventCallback(
BluetoothScanningPrompt::Event event) {
if (!prompt_) {
@@ -383,6 +400,9 @@
private:
bool has_device_permission_ = false;
+ bool may_use_bluetooth_ = true;
+ AllowWebBluetoothResult allow_web_bluetooth_ =
+ AllowWebBluetoothResult::kAllow;
raw_ptr<FakeBluetoothScanningPrompt, AcrossTasksDanglingUntriaged> prompt_ =
nullptr;
};
@@ -694,6 +714,17 @@
&service_ptr_.ExtractAsDangling()->render_frame_host());
}
+ void ForgetDevice(
+ const blink::WebBluetoothDeviceId& device_id,
+ blink::mojom::WebBluetoothService::ForgetDeviceCallback callback) {
+ service_ptr_->ForgetDevice(device_id, std::move(callback));
+ }
+
+ std::string GetAllowedDeviceAddress(
+ const blink::WebBluetoothDeviceId& device_id) {
+ return service_ptr_->allowed_devices().GetDeviceAddress(device_id);
+ }
+
scoped_refptr<FakeBluetoothAdapter> adapter_;
raw_ptr<WebBluetoothServiceImpl> service_ptr_ = nullptr;
mojo::Remote<blink::mojom::WebBluetoothService> service_;
@@ -1537,4 +1568,20 @@
EXPECT_EQ(write_future.Get(), WebBluetoothResult::BLOCKLISTED_WRITE);
}
+TEST_F(WebBluetoothServiceImplTest, ForgetDevice_NotAllowed) {
+ blink::WebBluetoothDeviceId device_id =
+ AddTestDevice(battery_device_bundle());
+ EXPECT_FALSE(GetAllowedDeviceAddress(device_id).empty());
+
+ browser_client_.bluetooth_delegate()->set_may_use_bluetooth(false);
+
+ base::test::TestFuture<void> future;
+ ForgetDevice(device_id, future.GetCallback());
+ EXPECT_TRUE(future.Wait());
+
+ // The device should STILL be in allowed devices because ForgetDevice should
+ // return early.
+ EXPECT_FALSE(GetAllowedDeviceAddress(device_id).empty());
+}
+
} // namespace content
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 4880d79..be04cdd 100644
--- a/content/browser/bluetooth/web_bluetooth_service_impl_unittest.cc
+++ b/content/browser/bluetooth/web_bluetooth_service_impl_unittest.cc
@@ -372,6 +372,23 @@
has_device_permission_ = value;
}
+ bool MayUseBluetooth(RenderFrameHost* frame) override {
+ return may_use_bluetooth_;
+ }
+
+ void set_may_use_bluetooth(bool value) { may_use_bluetooth_ = value; }
+
+ AllowWebBluetoothResult AllowWebBluetooth(
+ content::BrowserContext* browser_context,
+ const url::Origin& requesting_origin,
+ const url::Origin& embedding_origin) override {
+ return allow_web_bluetooth_;
+ }
+
+ void set_allow_web_bluetooth(AllowWebBluetoothResult value) {
+ allow_web_bluetooth_ = value;
+ }
+
void RunBluetoothScanningPromptEventCallback(
BluetoothScanningPrompt::Event event) {
if (!prompt_) {
@@ -383,6 +400,9 @@
private:
bool has_device_permission_ = false;
+ bool may_use_bluetooth_ = true;
+ AllowWebBluetoothResult allow_web_bluetooth_ =
+ AllowWebBluetoothResult::kAllow;
raw_ptr<FakeBluetoothScanningPrompt, AcrossTasksDanglingUntriaged> prompt_ =
nullptr;
};
@@ -694,6 +714,17 @@
&service_ptr_.ExtractAsDangling()->render_frame_host());
}
+ void ForgetDevice(
+ const blink::WebBluetoothDeviceId& device_id,
+ blink::mojom::WebBluetoothService::ForgetDeviceCallback callback) {
+ service_ptr_->ForgetDevice(device_id, std::move(callback));
+ }
+
+ std::string GetAllowedDeviceAddress(
+ const blink::WebBluetoothDeviceId& device_id) {
+ return service_ptr_->allowed_devices().GetDeviceAddress(device_id);
+ }
+
scoped_refptr<FakeBluetoothAdapter> adapter_;
raw_ptr<WebBluetoothServiceImpl> service_ptr_ = nullptr;
mojo::Remote<blink::mojom::WebBluetoothService> service_;
@@ -1537,4 +1568,20 @@
EXPECT_EQ(write_future.Get(), WebBluetoothResult::BLOCKLISTED_WRITE);
}
+TEST_F(WebBluetoothServiceImplTest, ForgetDevice_NotAllowed) {
+ blink::WebBluetoothDeviceId device_id =
+ AddTestDevice(battery_device_bundle());
+ EXPECT_FALSE(GetAllowedDeviceAddress(device_id).empty());
+
+ browser_client_.bluetooth_delegate()->set_may_use_bluetooth(false);
+
+ base::test::TestFuture<void> future;
+ ForgetDevice(device_id, future.GetCallback());
+ EXPECT_TRUE(future.Wait());
+
+ // The device should STILL be in allowed devices because ForgetDevice should
+ // return early.
+ EXPECT_FALSE(GetAllowedDeviceAddress(device_id).empty());
+}
+
} // namespace content
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page