CVE-2026-11633
Overview
Files Changed
device/bluetooth/bluetooth_l2cap_channel_mac.mmdevice/bluetooth/bluetooth_rfcomm_channel_mac.mm
Patch
From 3da0d899cbbe299ec3ba36bb842b0b6215915b63 Mon Sep 17 00:00:00 2001
From: Alvin Ji <alvinji@chromium.org>
Date: Fri, 29 May 2026 15:45:54 -0700
Subject: [PATCH] bluetooth: Fix potential UAF in Bluetooth Classic channel delegates on macOS
Clears the native channel's delegate and extends the delegate's lifetime
across one run-loop turn during channel destruction.
On macOS, the classic Bluetooth channel delegates only self-retain on
successful open. On connection failure or timeout, destroying the C++
wrapper deallocates the delegate while the OS still holds an unsafe
reference, causing a UAF during subsequent async callbacks.
Bug: 516963272
Change-Id: Ice878214b875039e6eff06b0b4101a7c4ea433f6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7884206
Commit-Queue: Alvin Ji <alvinji@chromium.org>
Reviewed-by: Matt Reynolds <mattreynolds@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1638776}
---
diff --git a/device/bluetooth/bluetooth_l2cap_channel_mac.mm b/device/bluetooth/bluetooth_l2cap_channel_mac.mm
index f1c5b39..1792d66 100644
--- a/device/bluetooth/bluetooth_l2cap_channel_mac.mm
+++ b/device/bluetooth/bluetooth_l2cap_channel_mac.mm
@@ -120,7 +120,16 @@
// delegate's reference to this object so the delegate will not notify us
// for events that occur after our destruction.
[delegate_ resetOwner];
+ [channel_ setDelegate:nil];
[channel_ closeChannel];
+ // `delegate_`'s self-retain (`_strongSelf`) is only armed after a successful
+ // open. If we are destroyed during a pending or failed open, keep the
+ // delegate alive across one main-run-loop turn so any already-enqueued
+ // IOBluetooth callbacks hit a live receiver. See FB13705522.
+ BluetoothL2capChannelDelegate* __strong delegate = delegate_;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ (void)delegate;
+ });
}
// static
diff --git a/device/bluetooth/bluetooth_rfcomm_channel_mac.mm b/device/bluetooth/bluetooth_rfcomm_channel_mac.mm
index 635a30f4..a241175 100644
--- a/device/bluetooth/bluetooth_rfcomm_channel_mac.mm
+++ b/device/bluetooth/bluetooth_rfcomm_channel_mac.mm
@@ -120,7 +120,16 @@
// delegate's reference to this object so the delegate will not notify us
// for events that occur after our destruction.
[delegate_ resetOwner];
+ [channel_ setDelegate:nil];
[channel_ closeChannel];
+ // `delegate_`'s self-retain (`_strongSelf`) is only armed after a successful
+ // open. If we are destroyed during a pending or failed open, keep the
+ // delegate alive across one main-run-loop turn so any already-enqueued
+ // IOBluetooth callbacks hit a live receiver. See FB13705522.
+ BluetoothRfcommChannelDelegate* __strong delegate = delegate_;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ (void)delegate;
+ });
}
// static
Original Bug Report
Potential Use-After-Free in BluetoothRfcommChannelDelegate and BluetoothL2capChannelDelegate on macOS
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 classic implementation within Chromium. If a connection times out or fails during initialization, the channel delegate is deallocated while the system’s IOBluetooth framework still holds an unsafe_unretained reference to it. This can lead to a browser-process crash or potential arbitrary code execution when the framework attempts to dispatch delayed asynchronous callbacks to the freed delegate.
Affected files:
device/bluetooth/bluetooth_rfcomm_channel_mac.mmdevice/bluetooth/bluetooth_l2cap_channel_mac.mm
Estimated timestamp from git blame: 2024-04-03
Description
An analysis of the macOS-specific Bluetooth classic channel implementation in Chromium reveals a potential Use-After-Free (UAF) vulnerability affecting both BluetoothRfcommChannelDelegate and BluetoothL2capChannelDelegate.
To address a macOS-specific bug (Apple Feedback report FB13705522), the Objective-C delegates attempt to keep themselves alive across asynchronous notifications using a self-retain pattern (_strongSelf = self). However, this self-retain is established only upon a successful channel opening status (error == kIOReturnSuccess):
// device/bluetooth/bluetooth_rfcomm_channel_mac.mm
- (void)rfcommChannelOpenComplete:(IOBluetoothRFCOMMChannel*)rfcommChannel
status:(IOReturn)error {
CHECK(_rfcommChannel);
if (error == kIOReturnSuccess) {
// Keep the delegate alive until rfcommChannelClosed.
_strongSelf = self;
}
if (_channel) {
_channel->OnChannelOpenComplete(rfcommChannel, error);
}
}
Before a successful open completion callback is received, the only strong reference to the delegate is maintained by the C++ wrapper via its __strong delegate_ member variable. The IOBluetooth framework stores its reference to the delegate as an assign (i.e., unsafe_unretained) property.
If the connection times out (e.g., via the 10-second channel opening timer in BluetoothSocketMac::OnChannelOpeningTimeout) or fails with an error:
ReleaseChannel()is called, destroying the C++ channel wrapper.- The C++ destructor (
~BluetoothRfcommChannelMacor~BluetoothL2capChannelMac) is executed. It resets the delegate’s back-pointer and closes the channel, but does not clear the delegate registration from the native channel:
BluetoothRfcommChannelMac::~BluetoothRfcommChannelMac() {
[delegate_ resetOwner];
[channel_ closeChannel]; // Does NOT call [channel_ setDelegate:nil]
}
- Because
_strongSelfwas never set (the connection never succeeded), releasing the C++ wrapper’sdelegate_reference reduces the delegate’s ARC retain count to zero, causing it to be immediately deallocated. - Meanwhile, the
IOBluetoothframework retains itsunsafe_unretainedpointer to the deallocated delegate. When subsequent asynchronous callbacks or close notifications are dispatched to the delegate, it results in a Use-After-Free (UAF) condition duringobjc_msgSendon the main thread of the browser process.
An identical potential vulnerability pattern exists in the L2CAP implementation within device/bluetooth/bluetooth_l2cap_channel_mac.mm.
Suggested Attack Steps
An attacker could theoretically trigger this issue by doing the following (note: our analysis is static and we have not executed a physical proof of concept):
- The user is induced to connect to a paired, malicious classic Bluetooth device controlled by the attacker (e.g., via a Web Serial or Web Bluetooth prompt).
- The browser process initiates the connection, invoking
OpenAsync, which registers the delegate with the macOSIOBluetoothframework. - The attacker’s device purposefully stalls the handshake process, forcing the browser’s connection timer to expire, or actively rejects the connection with an error status.
- The browser process handles the failure/timeout by resetting the channel, deallocating the delegate.
- The system framework asynchronously processes the aborted connection state and dispatches delayed delegate selectors (like
rfcommChannelClosed:orrfcommChannelOpenComplete:status:) to the freed delegate’s memory address, resulting in a UAF.
Proposed Remediation
To safely prevent the framework from calling back into the deallocated delegate, the native channel’s delegate reference must be explicitly removed in the C++ destructor before the delegate is released:
BluetoothRfcommChannelMac::~BluetoothRfcommChannelMac() {
[delegate_ resetOwner];
[channel_ setDelegate:nil]; // Explicitly clear the delegate
[channel_ closeChannel];
}
Apply the equivalent correction to BluetoothL2capChannelMac::~BluetoothL2capChannelMac().
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.