Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Bluetooth
DescriptionUse after free in Bluetooth
ComponentBluetooth
Bug ClassUAF
Tracker518235412
Fix commit407929b2c76b (chromium/src) +14/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-08

Files Changed

  • device/bluetooth/bluetooth_l2cap_channel_mac.mm
  • device/bluetooth/bluetooth_rfcomm_channel_mac.mm
From 407929b2c76b20b7d2e291ffd5cdea7c2c383380 Mon Sep 17 00:00:00 2001
From: Alvin Ji <[email protected]>
Date: Tue, 02 Jun 2026 13:03:21 -0700
Subject: [PATCH] Fix UAF in BluetoothRfcommChannelMac and BluetoothL2capChannelMac delegates on macOS

When a Bluetooth channel is closed, the C++ wrapper class synchronously
releases its strong reference to its Objective-C delegate class while
the delegate's own closed-callback method is still active on the call
stack. This results in the delegate being immediately deallocated,
causing UAF reads and writes upon method return.

This CL fixes the issue by holding a strong local reference to the
delegate (self) at the beginning of rfcommChannelClosed: and l2capChannelClosed: to ensure it survives the callback execution.

Bug: 518235412
Change-Id: Ie29e7a96cb36f3673a56d2a8d3834409c5007efb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7892775
Reviewed-by: Matt Reynolds <[email protected]>
Commit-Queue: Alvin Ji <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1640409}
---

diff --git a/device/bluetooth/bluetooth_l2cap_channel_mac.mm b/device/bluetooth/bluetooth_l2cap_channel_mac.mm
index 1792d66..58436795 100644
--- a/device/bluetooth/bluetooth_l2cap_channel_mac.mm
+++ b/device/bluetooth/bluetooth_l2cap_channel_mac.mm
@@ -78,6 +78,13 @@
 - (void)l2capChannelClosed:(IOBluetoothL2CAPChannel*)l2capChannel {
   [_l2capChannel setDelegate:nil];
 
+  // Maintain a strong local reference to ensure the delegate survives the
+  // callback. This is necessary for incoming connections or failed outgoing
+  // connections where `_strongSelf` is never armed, leaving `delegate_` in
+  // C++ as the only strong reference keeping this object alive.
+  [[maybe_unused]] NS_VALID_UNTIL_END_OF_SCOPE BluetoothL2capChannelDelegate*
+      keepAlive = self;
+
   // If `_channel` still exists, notify it that the channel was closed so it
   // can release its strong references to `l2capChannel` and the channel
   // delegate (this object). In the typical case we expect `_channel` has
diff --git a/device/bluetooth/bluetooth_rfcomm_channel_mac.mm b/device/bluetooth/bluetooth_rfcomm_channel_mac.mm
index a241175..2354bad 100644
--- a/device/bluetooth/bluetooth_rfcomm_channel_mac.mm
+++ b/device/bluetooth/bluetooth_rfcomm_channel_mac.mm
@@ -78,6 +78,13 @@
 - (void)rfcommChannelClosed:(IOBluetoothRFCOMMChannel*)rfcommChannel {
   [_rfcommChannel setDelegate:nil];
 
+  // Maintain a strong local reference to ensure the delegate survives the
+  // callback. This is necessary for incoming connections or failed outgoing
+  // connections where `_strongSelf` is never armed, leaving `delegate_` in
+  // C++ as the only strong reference keeping this object alive.
+  [[maybe_unused]] NS_VALID_UNTIL_END_OF_SCOPE BluetoothRfcommChannelDelegate*
+      keepAlive = self;
+
   // If `_channel` still exists, notify it that the channel was closed so it
   // can release its strong references to `rfcommChannel` and the channel
   // delegate (this object). In the typical case we expect `_channel` has
Loading diff…

Original Bug Report

reported by [email protected]

Potential UAF in BluetoothRfcommChannelMac and BluetoothL2capChannelMac delegates 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 the browser process. When an incoming or failed-open connection is closed, the C++ wrapper synchronously drops its strong reference to its Objective-C delegate while executing inside that delegate’s own callback. This causes immediate deallocation of the delegate object followed by UAF writes and a potential controlled indirect call when execution resumes.

Affected files:

  • device/bluetooth/bluetooth_rfcomm_channel_mac.mm
  • device/bluetooth/bluetooth_l2cap_channel_mac.mm
  • device/bluetooth/bluetooth_rfcomm_channel_mac.h
  • device/bluetooth/bluetooth_l2cap_channel_mac.h

Estimated timestamp from git blame: 2024-04-03

Summary

A potential Use-After-Free (UAF) memory safety vulnerability has been identified in Chromium’s macOS Bluetooth classic implementation (RFCOMM and L2CAP channels). When a channel is closed, the C++ wrapper class synchronously releases its strong reference to its Objective-C delegate class while the delegate’s own closed-callback method is still active on the call stack. This results in the delegate being immediately deallocated, causing UAF reads and writes upon method return.

Affected Files

  • device/bluetooth/bluetooth_rfcomm_channel_mac.mm
  • device/bluetooth/bluetooth_l2cap_channel_mac.mm
  • device/bluetooth/bluetooth_rfcomm_channel_mac.h
  • device/bluetooth/bluetooth_l2cap_channel_mac.h

Root Cause Analysis

To keep the channel delegate alive while the native macOS IOBluetooth framework (which holds it as an unsafe_unretained pointer) executes callbacks, a _strongSelf self-retain is armed in the delegate. However, this self-retain is only armed in rfcommChannelOpenComplete: / l2capChannelOpenComplete: upon a successful open (error == kIOReturnSuccess).

There are two main scenarios where _strongSelf remains nil for an active channel delegate:

  1. Incoming/Accepted Connections: For incoming connections, the channel is already open. When the server-side socket accepts the connection, it instantiates the delegate directly without triggering rfcommChannelOpenComplete:, leaving _strongSelf as nil.
  2. Failed/Aborted Outgoing Connections: If the channel fails to open or is aborted during the connection phase, the completion callback is either not triggered or is triggered with an error status, meaning _strongSelf is never armed.

Under Clang ARC rules, the implicit self parameter of an Objective-C method is not retained by the implementation, meaning the delegate is only kept alive by the C++ channel’s delegate_ ivar. When the channel is closed, the OS invokes -rfcommChannelClosed: / -l2capChannelClosed: on the delegate:

- (void)rfcommChannelClosed:(IOBluetoothRFCOMMChannel*)rfcommChannel {
  [_rfcommChannel setDelegate:nil];
  if (_channel) {
    _channel->OnChannelClosed(rfcommChannel);   // Re-enters C++
  }
  _rfcommChannel = nil;   // Use-After-Free: write to freed self
  _strongSelf = nil;      // Use-After-Free: second write
}

Upon re-entering C++ via OnChannelClosed:

void BluetoothRfcommChannelMac::OnChannelClosed(IOBluetoothRFCOMMChannel*) {
  channel_ = nil;
  [delegate_ resetOwner];
  delegate_ = nil;                 // Drops the only strong reference to delegate
  socket()->OnChannelClosed();     // Releases the C++ channel wrapper
}

Setting delegate_ = nil; synchronously drops the delegate’s retain count to zero, calling -dealloc while -rfcommChannelClosed: is still on the stack. Although the C++ destructor tries to employ a dispatch_async block keep-alive to safeguard the delegate, this protection is entirely bypassed because delegate_ was already cleared and reads as nil inside the destructor.

Upon returning to -rfcommChannelClosed:, the lines _rfcommChannel = nil; and _strongSelf = nil; lower to ARC ivar-release operations. This leads to a UAF read of self->_rfcommChannel (which can result in a controlled objc_msgSend if the memory has been reclaimed and sprayed) followed by two UAF writes of nil to deallocated memory.

Potential Trigger Flow

Note: This is a suggested/potential trigger flow analyzed from source code; our tooling agent does not yet have the ability to run code or verify via an active exploit.

  1. A web page utilizes Web Serial or a compromised renderer interacts with the Bluetooth Mojo interface to listen for or connect to a Bluetooth Classic device.
  2. For an incoming connection (where the channel is already open and _strongSelf is nil), the peer device abruptly disconnects, triggering the OS to dispatch -rfcommChannelClosed: to the delegate.
  3. The delegate re-enters C++ which immediately sets delegate_ = nil;, destroying the delegate mid-execution.
  4. Execution returns to the delegate method frame, causing a Use-After-Free read and write on the system malloc heap.

To resolve this issue, the delegate should safely keep itself alive for the duration of the delegate callback method. This can be achieved by holding a local __strong reference to self before re-entering C++ or by postponing the release of the delegate strong pointer until after the callback has finished executing (e.g., via a post-task).

Example fix in -rfcommChannelClosed::

- (void)rfcommChannelClosed:(IOBluetoothRFCOMMChannel*)rfcommChannel {
  [_rfcommChannel setDelegate:nil];
  
  // Maintain a strong local reference to ensure delegate survives the callback
  BluetoothRfcommChannelDelegate* __strong keepAlive = self;

  if (_channel) {
    _channel->OnChannelClosed(rfcommChannel);
  }
  _rfcommChannel = nil;
  _strongSelf = nil;
}

Evaluated with Chrome root at commit: 208ca3371d87589335b108c431b95a36d768dc47


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.

View on issue tracker