CVE-2026-13785
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifdevice/bluetooth/bluetooth_socket_mac.mm |
modified |
Files Changed
device/bluetooth/bluetooth_socket_mac.mm
Patch
From 429a22c5a9961a9897b31134dcf050c7af51d61a Mon Sep 17 00:00:00 2001 From: Alvin Ji <[email protected]> Date: Fri, 29 May 2026 19:49:52 -0700 Subject: [PATCH] bluetooth: prevent use-after-free in SDPQueryListener during timeout Keep SDPQueryListener alive using a strong self-reference until the macOS IOBluetooth callback is delivered. On a watchdog timeout, the socket drops its strong reference to the listener. Because IOBluetooth stores its target unretained (FB13705522), this leads to an immediate deallocation and a subsequent Use-After-Free (UAF) when the late callback fires. Retaining self until the callback completes ensures the listener survives. BUG=517021684 Change-Id: If7cfbd483ccf3339d6e06348ca9936b350fab9fa Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7882486 Reviewed-by: Matt Reynolds <[email protected]> Commit-Queue: Alvin Ji <[email protected]> Cr-Commit-Position: refs/heads/main@{#1638924} --- diff --git a/device/bluetooth/bluetooth_socket_mac.mm b/device/bluetooth/bluetooth_socket_mac.mm index 2ab7154..315f1ddf 100644 --- a/device/bluetooth/bluetooth_socket_mac.mm +++ b/device/bluetooth/bluetooth_socket_mac.mm @@ -53,6 +53,12 @@ // The device being queried. IOBluetoothDevice* __weak _device; + + // While the SDP query is outstanding, the listener holds a strong reference + // to itself so that it outlives any late -sdpQueryComplete:status: dispatch + // from IOBluetooth, which stores the performSDPQuery: target unretained. + // This is a workaround for a macOS bug, see Apple Feedback report FB13705522. + SDPQueryListener* __strong _strongSelf; } - (instancetype)initWithSocket:(scoped_refptr<device::BluetoothSocketMac>)socket @@ -77,6 +83,8 @@ _device = device; _success_callback = std::move(success_callback); _error_callback = std::move(error_callback); + // Retain self until IOBluetooth delivers -sdpQueryComplete:status:. + _strongSelf = self; } return self; @@ -92,6 +100,10 @@ - (void)sdpQueryComplete:(IOBluetoothDevice*)device status:(IOReturn)status { DCHECK_EQ(device, _device); + // IOBluetooth has called back; drop the self-retain. Keep |self| alive for + // the remainder of this method via a local strong reference. + NS_VALID_UNTIL_END_OF_SCOPE SDPQueryListener* strongSelf = _strongSelf; + _strongSelf = nil; if (!_error_callback) { // This can happen when the target is called after SDP query timeout. return;
Original Bug Report
Potential Use-After-Free in BluetoothSocketMac on macOS during SDP query timeout
Project Fortify, 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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A potential Use-After-Free (UAF) vulnerability exists in the macOS Bluetooth socket implementation when an SDP query times out. If the timeout fires, the strong reference to the SDPQueryListener delegate is released, immediately deallocating it while the macOS IOBluetooth framework still holds an unsafe_unretained reference to it. When the late callback or internal query completion eventually fires, the OS dispatches a selector to the deallocated object, potentially causing a browser process crash or arbitrary code execution.
Affected files:
device/bluetooth/bluetooth_socket_mac.mm
Estimated timestamp from git blame: 2024-06-13
Problem Description
In device/bluetooth/bluetooth_socket_mac.mm, SDPQueryListener is registered as the target of -[IOBluetoothDevice performSDPQuery:], a legacy macOS system API. Because the macOS IOBluetooth framework dates back to early versions of macOS, it stores its target/delegate using an unsafe_unretained (raw) pointer to avoid strong reference cycles.
Chromium’s only strong reference keeping the SDPQueryListener alive is the sdp_query_listener_ instance variable in BluetoothSocketMac (declared as SDPQueryListener* __strong sdp_query_listener_; in the header).
When an SDP query takes longer than the local 10-second watchdog timer, BluetoothSocketMac::OnSDPQueryTimeout() is executed. It clears the callback and resets the listener pointer:
void BluetoothSocketMac::OnSDPQueryTimeout() {
DCHECK(thread_checker_.CalledOnValidThread());
if (!sdp_query_listener_) {
return;
}
auto error_callback = [sdp_query_listener_ takeErrorCallback];
if (error_callback) {
std::move(error_callback).Run(kSDPQueryTimeout);
}
sdp_query_listener_ = nil; // Sole strong reference is dropped, deallocating the listener
}
Resetting sdp_query_listener_ to nil immediately deallocates the SDPQueryListener instance. However, the OS-level SDP query is still running asynchronously in the background. When the query eventually completes (or times out at the system level), the OS Bluetooth framework attempts to invoke -sdpQueryComplete:status: on the deallocated pointer, causing a Use-After-Free inside objc_msgSend.
Since the Device/Bluetooth Service runs inside the unsandboxed Browser process on macOS due to TCC system entitlement constraints, this issue could potentially be leveraged to escape the browser sandbox and execute arbitrary code with browser privileges.
Potential Steps to Trigger the Vulnerability
Note: Our analysis is static and based on code review; we do not currently have an active runtime test environment to execute a proof of concept.
- A user selects a paired, malicious Bluetooth Classic device via a Web Bluetooth/Web Serial chooser prompt.
- The renderer requests a connection, initiating
BluetoothSocketMac::Connectand dispatching[device performSDPQuery:sdp_query_listener_]with a 10-second local watchdog timer. - The malicious Bluetooth device intentionally delays responding to the SDP query for more than 10 seconds.
- The 10-second timer expires, and
BluetoothSocketMac::OnSDPQueryTimeoutdrops the reference, immediately deallocating theSDPQueryListenerobject under ARC. - The attacker attempts to reclaim the freed memory layout by spraying the heap (e.g., via Mojo or other browser allocations) to establish a fake
isapointer and method cache. - The malicious Bluetooth device finally sends the SDP response, prompting the OS to call
-sdpQueryComplete:status:on the dangling target pointer, triggering a control flow hijack.
Suggested Remediation
Since macOS does not provide a reliable API to cancel a pending SDP query once dispatched to IOBluetoothDevice, the SDPQueryListener must not be deallocated until the OS callback is received.
To safely resolve this:
- When a timeout occurs, clear the internal references to prevent processing the callback, but do not immediately deallocate the listener.
- Have the
SDPQueryListenerhold a strong self-reference (__strong id selfKeepAlive = self;) when it is orphaned/detached from the C++ socket. - Release this self-reference at the very end of
sdpQueryComplete:status:, ensuring that the object is safely kept alive until the OS is done interacting with it.
Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
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.