High watch CVSS 8.8 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Bluetooth
DescriptionUse after free in Bluetooth
ComponentBluetooth
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker518235412
Fix commit407929b2c76b (chromium/src) +14/-0
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-05-30 (per vendor advisory)
Fix landed2026-06-02 (public git)
Disclosed2026-06-08
Patch gap6 days — fix public in source before the advisory

Background

`BluetoothL2capChannelMac` / `BluetoothRfcommChannelMac`
C++ wrapper classes on macOS that manage an IOBluetooth channel and own a strong reference (delegate_) to an Objective-C delegate object.
Objective-C delegate
BluetoothL2capChannelDelegate / BluetoothRfcommChannelDelegate, which receive IOBluetooth callbacks such as l2capChannelClosed: / rfcommChannelClosed: and forward them to the C++ wrapper.
ARC reference release during callback
When the C++ wrapper reacts to a close callback, it releases its strong delegate_ reference; if that was the last strong reference, the delegate is deallocated immediately while its own method is still on the stack.
`NS_VALID_UNTIL_END_OF_SCOPE` / `_strongSelf`
An ARC annotation forcing a local strong reference to live until scope exit; _strongSelf is a separate strong self-reference armed only for successful outgoing connections (not for incoming or failed outgoing ones).

Root Cause Analysis

In l2capChannelClosed: and rfcommChannelClosed:, the delegate first clears the IOBluetooth channel delegate and then notifies its owning C++ _channel, which in response synchronously releases the C++ wrapper’s strong reference (delegate_) to this Objective-C delegate. For incoming connections or failed outgoing connections, _strongSelf was never armed, so delegate_ in C++ is the only strong reference keeping the delegate alive; releasing it deallocates the delegate object while its ...ChannelClosed: method is still executing. When that method returns and touches instance state (self/ivars), it reads and writes freed memory — a use-after-free.

The fix takes a strong local reference keepAlive = self (annotated NS_VALID_UNTIL_END_OF_SCOPE) at the top of each callback, guaranteeing the delegate outlives the callback’s full execution regardless of when the C++ side drops delegate_.

Key insight
The single mistake is that a delegate lets its sole owner (the C++ wrapper’s delegate_) release the last strong reference mid-callback, deallocating self before the method returns; the fix pins self in a stack-local strong reference that survives to end of scope.

Attack Path

  1. Establish a Bluetooth channel An attacker or peer device establishes an RFCOMM or L2CAP channel via an incoming connection (or a failed outgoing connection), the case where _strongSelf is never armed.
  2. Trigger channel close The channel is closed, invoking the delegate’s rfcommChannelClosed: / l2capChannelClosed: while the delegate method sits on the call stack.
  3. Owner drops last reference The callback notifies the C++ _channel, which releases its strong delegate_ reference; since it is the only strong reference, the Objective-C delegate is deallocated immediately.
  4. Use-after-free on return As the still-executing ...ChannelClosed: method continues and returns, accesses to self/ivars read and write the freed delegate object.
  5. Exploitation The freed delegate memory could be reclaimed and shaped to corrupt state during the UAF reads/writes. (inferred)

Impact Assessment

An attacker gains a use-after-free of a Bluetooth channel delegate object in the process handling macOS Bluetooth (the browser process), triggered when an incoming or failed-outgoing RFCOMM/L2CAP channel is closed so the C++ wrapper’s delegate_ is the last strong reference. The advisory classifies this as a use-after-free in Bluetooth on macOS with no exploited-in-the-wild status stated; escalation beyond the memory-safety violation is (inferred).

Files Changed

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

Audit Directions

  • Owner-released delegates during callbacks
    Audit callback methods that cause their sole owner to release the last strong reference to the callback receiver; the receiver must retain self for the callback’s duration.
  • Conditionally-armed self references
    Review lifetime schemes like _strongSelf that are armed only on certain paths (successful outgoing connections), since the unarmed paths (incoming/failed) leave the object owned solely by an external reference.
  • Objective-C/C++ ownership bridges
    Examine ObjC delegate ↔ C++ wrapper boundaries on macOS where a C++ strong ref (delegate_) is the backing lifetime of an ObjC object, ensuring synchronous release cannot outrun an in-progress delegate method.
From 407929b2c76b20b7d2e291ffd5cdea7c2c383380 Mon Sep 17 00:00:00 2001
From: Alvin Ji <alvinji@chromium.org>
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 <mattreynolds@chromium.org>
Commit-Queue: Alvin Ji <alvinji@chromium.org>
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 vm...@google.com

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