CVE-2026-17737
Overview
Files Changed
device/bluetooth/bluetooth_socket_android.ccdevice/bluetooth/bluetooth_socket_android.h
Patch
From e9dafc9b0a2ccb75075a93eeaaac1e45c4a82050 Mon Sep 17 00:00:00 2001
From: Takashi Sakamoto <tasak@google.com>
Date: Thu, 02 Apr 2026 01:54:33 -0700
Subject: [PATCH] Make receiving_thread_ stop and destroy on UI thread.
Bug: 498000415
Change-Id: I42a7bffac1e7c2f5b58f95fab4399aa69430c9e5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7718965
Reviewed-by: Keishi Hattori <keishi@chromium.org>
Reviewed-by: Colin Blundell <blundell@chromium.org>
Commit-Queue: Takashi Sakamoto <tasak@google.com>
Cr-Commit-Position: refs/heads/main@{#1609029}
---
diff --git a/device/bluetooth/bluetooth_socket_android.cc b/device/bluetooth/bluetooth_socket_android.cc
index b1742b1..1bbb74cd 100644
--- a/device/bluetooth/bluetooth_socket_android.cc
+++ b/device/bluetooth/bluetooth_socket_android.cc
@@ -112,20 +112,26 @@
}
void BluetoothSocketAndroid::Disconnect(base::OnceClosure success_callback) {
- socket_thread_->task_runner()->PostTask(
- FROM_HERE, base::BindOnce(&BluetoothSocketAndroid::DoDisconnect, this,
- std::move(success_callback)));
+ socket_thread_->task_runner()->PostTaskAndReply(
+ FROM_HERE,
+ base::BindOnce(&BluetoothSocketAndroid::DoDisconnect,
+ base::Unretained(this)),
+ base::BindOnce(&BluetoothSocketAndroid::PostDisconnect,
+ base::Unretained(this), std::move(success_callback)));
}
-void BluetoothSocketAndroid::DoDisconnect(base::OnceClosure success_callback) {
+void BluetoothSocketAndroid::DoDisconnect() {
CHECK(socket_thread_->task_runner()->RunsTasksInCurrentSequence());
Java_ChromeBluetoothSocket_close(AttachCurrentThread(), j_socket_);
+}
+void BluetoothSocketAndroid::PostDisconnect(
+ base::OnceClosure success_callback) {
+ // Stop and destroy `receiving_thread_` on UI thread, not on Socket Thread.
receiving_thread_->Stop();
receiving_thread_.reset();
-
- ui_task_runner_->PostTask(FROM_HERE, std::move(success_callback));
+ std::move(success_callback).Run();
}
void BluetoothSocketAndroid::Receive(
diff --git a/device/bluetooth/bluetooth_socket_android.h b/device/bluetooth/bluetooth_socket_android.h
index cbb9857..b0f888e 100644
--- a/device/bluetooth/bluetooth_socket_android.h
+++ b/device/bluetooth/bluetooth_socket_android.h
@@ -64,7 +64,8 @@
void DoConnect(base::OnceClosure success_callback,
ErrorCompletionCallback error_callback);
- void DoDisconnect(base::OnceClosure success_callback);
+ void DoDisconnect();
+ void PostDisconnect(base::OnceClosure success_callback);
void DoReceive(size_t buffer_size,
ReceiveCompletionCallback success_callback,
ReceiveErrorCompletionCallback error_callback);
Original Bug Report
Use-After-Free in BluetoothSocketAndroid via Race Condition
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A race condition in BluetoothSocketAndroid between the UI thread and the socket thread can lead to a Use-After-Free (UAF). A compromised renderer with Web Serial permissions could potentially trigger this by concurrently initiating close and read operations. This may allow an attacker to achieve arbitrary code execution in the highly privileged browser process.
Affected files:
device/bluetooth/bluetooth_socket_android.ccdevice/bluetooth/bluetooth_socket_android.h
Estimated timestamp from git blame: 2025-03-21
Summary
There is a potential Use-After-Free (UAF) vulnerability in the Android implementation of BluetoothSocketAndroid within the browser process. The issue stems from an unsynchronized cross-thread access to the receiving_thread_ member (a std::unique_ptr<base::Thread>). A data race between the UI thread reading this member and a background thread destroying it can lead to a dangling pointer dereference and potentially Remote Code Execution (RCE).
Technical Details
BluetoothSocketAndroid manages a dedicated base::Thread (receiving_thread_) for blocking JNI read operations.
- When
Disconnect()is called, it postsDoDisconnect()to thesocket_thread_. DoDisconnect()stops and destroys the thread:// device/bluetooth/bluetooth_socket_android.cc:125 receiving_thread_->Stop(); receiving_thread_.reset(); // Destroys the base::Thread object- Concurrently,
Receive()is called on the UI thread (e.g., viaBluetoothSerialPortImpl::ReadMore()).Receive()performs an unsynchronized read ofreceiving_thread_:// device/bluetooth/bluetooth_socket_android.cc:135 if (!receiving_thread_) { // ... error handling } receiving_thread_->task_runner()->PostTask(...);
A race condition occurs if the UI thread evaluates the !receiving_thread_ check as false, but is then preempted. The socket_thread_ can then execute receiving_thread_.reset(), freeing the memory. When the UI thread resumes, it dereferences the now-freed pointer (cached in a register) to call task_runner().
Inside base::Thread::task_runner(), it calls delegate_ ? delegate_->GetDefaultTaskRunner() : nullptr. Since the base::Thread object has been freed, if an attacker reclaims this heap memory, they can control the delegate_ pointer. Dereferencing delegate_ to call the virtual method GetDefaultTaskRunner() allows the attacker to hijack the instruction pointer.
Potential Steps to Trigger
Note: These are suggested steps; our tooling has not yet executed a working proof of concept to verify the exploit end-to-end.
- Compromise a Renderer: The attacker gains execution in a renderer process.
- Obtain Permissions: The attacker’s origin must have Web Serial API permissions for a paired Bluetooth device (e.g., previously granted by the user via a chooser dialog).
- Initiate Race: The compromised renderer opens the serial port and concurrently sends a
Close()Mojo message immediately followed by aStartReading()Mojo message to themojom::SerialPortinterface. - Heap Grooming: The attacker leverages other Mojo IPC calls to spray the browser process heap, aiming to reallocate the memory freed by
receiving_thread_.reset()with controlled data. - Execution: The
StartReading()call reachesBluetoothSocketAndroid::Receive()on the UI thread, hits the race condition, and dereferences the attacker-controlled vtable via thedelegate_pointer, leading to arbitrary code execution in the browser process.
Proposed Fix
Synchronize access to receiving_thread_. This can be achieved by protecting receiving_thread_ with a base::Lock, or by ensuring that all operations involving receiving_thread_ (including the null check and task posting in Receive) are trampolined to the socket_thread_ so they execute in the same sequence as DoConnect and DoDisconnect.
Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0
Results from 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.