Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Bluetooth
DescriptionUse after free in Bluetooth
ComponentBluetooth
Bug ClassUAF
Tracker505140741
Fix commit718de6b8e9b7 (chromium/src) +11/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-27

Changed Functions

FunctionChangeNotes
if
device/bluetooth/bluetooth_l2cap_channel_mac.mm
modified

Files Changed

  • device/bluetooth/bluetooth_l2cap_channel_mac.mm
From 718de6b8e9b77d656b340d708f79768098f576cc Mon Sep 17 00:00:00 2001
From: Andrew Paseltiner <apaseltiner@chromium.org>
Date: Wed, 22 Apr 2026 10:19:12 -0700
Subject: [PATCH] [macOS Bluetooth] Fix UAF in L2CAP channel teardown

For outgoing L2CAP connections, the BluetoothL2capChannelDelegate fails
to properly unregister itself from the native IOBluetoothL2CAPChannel
during connection teardown because it lacks a reference to the native
channel object. This leads to a dangling pointer within the macOS
Bluetooth framework, which can be triggered if subsequent events are
dispatched to the deallocated delegate.

This CL fixes the issue by adding a setL2capChannel: setter to the
delegate and calling it after the native channel is created in
OpenAsync. This matches the existing (and correct) implementation
pattern used for RFCOMM channels in bluetooth_rfcomm_channel_mac.mm,
which was fixed in https://crrev.com/c/5750170.

Fixed: 505140741
Change-Id: I18c0ccb6a145856d3ebeb1d62983a0c5518f3adc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7785775
Reviewed-by: Matt Reynolds <mattreynolds@chromium.org>
Commit-Queue: Andrew Paseltiner <apaseltiner@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1618950}
---

diff --git a/device/bluetooth/bluetooth_l2cap_channel_mac.mm b/device/bluetooth/bluetooth_l2cap_channel_mac.mm
index 5bcbde96..aafa433 100644
--- a/device/bluetooth/bluetooth_l2cap_channel_mac.mm
+++ b/device/bluetooth/bluetooth_l2cap_channel_mac.mm
@@ -28,6 +28,7 @@
 
 - (instancetype)initWithChannel:(device::BluetoothL2capChannelMac*)channel
                    l2capChannel:(IOBluetoothL2CAPChannel*)l2capChannel;
+- (void)setL2capChannel:(IOBluetoothL2CAPChannel*)l2capChannel;
 
 @end
 
@@ -45,6 +46,7 @@
 
 - (void)l2capChannelOpenComplete:(IOBluetoothL2CAPChannel*)l2capChannel
                           status:(IOReturn)error {
+  CHECK(_l2capChannel);
   if (error == kIOReturnSuccess) {
     // Keep the delegate alive until l2capChannelClosed.
     _strongSelf = self;
@@ -92,6 +94,11 @@
   _channel = nullptr;
 }
 
+- (void)setL2capChannel:(IOBluetoothL2CAPChannel*)l2capChannel {
+  CHECK(!_l2capChannel);
+  _l2capChannel = l2capChannel;
+}
+
 @end
 
 namespace device {
@@ -128,10 +135,12 @@
   *status = [device openL2CAPChannelAsync:&l2cap_channel
                                   withPSM:psm
                                  delegate:channel->delegate_];
-  if (*status == kIOReturnSuccess)
+  if (*status == kIOReturnSuccess) {
     channel->channel_ = l2cap_channel;
-  else
+    [channel->delegate_ setL2capChannel:l2cap_channel];
+  } else {
     channel.reset();
+  }
 
   return channel;
 }
Loading diff…

Original Bug Report

reported by li...@chromium.org

Potential Use-After-Free in BluetoothL2capChannelDelegate on macOS

Flapjack, 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 without the Chrome Security team. Please see go/chrome-ai-generated-security-bugs-faq for more information.

Overview: A potential Use-After-Free vulnerability exists in the macOS Bluetooth L2CAP implementation due to a failure to clear the native IOBluetoothL2CAPChannel delegate when closing outgoing connections. The delegate object is deallocated while the native OS channel still holds a dangling pointer to it, which can be triggered if late events are dispatched.

Affected files:

  • device/bluetooth/bluetooth_l2cap_channel_mac.mm

Estimated timestamp from git blame: 2024-04-03

Summary

A potential Use-After-Free (UAF) vulnerability exists in the macOS Bluetooth L2CAP implementation in Chromium. For outgoing L2CAP connections, the BluetoothL2capChannelDelegate fails to properly unregister itself from the native IOBluetoothL2CAPChannel during connection teardown. This leads to a dangling pointer within the macOS Bluetooth framework, which can be triggered if subsequent events (such as late data arrival) are dispatched to the deallocated delegate.

Technical Details

In device/bluetooth/bluetooth_l2cap_channel_mac.mm, when an outgoing L2CAP connection is initiated via BluetoothL2capChannelMac::OpenAsync, the following sequence occurs:

  1. A BluetoothL2capChannelMac wrapper object is constructed with its internal channel_ member explicitly set to nil (line 124).
  2. The constructor immediately calls SetSocket(), which instantiates a BluetoothL2capChannelDelegate. The delegate is initialized using channel_, meaning its internal _l2capChannel ivar is set to nil (lines 147-148).
  3. [device openL2CAPChannelAsync:...] is called, which creates the native IOBluetoothL2CAPChannel and registers the delegate with it (line 128).
  4. BluetoothL2capChannelMac::channel_ is updated with the new native channel object (line 132). However, the delegate’s internal _l2capChannel ivar is never updated.

When the connection is later closed, the macOS Bluetooth stack invokes l2capChannelClosed: on the delegate:

- (void)l2capChannelClosed:(IOBluetoothL2CAPChannel*)l2capChannel {
  [_l2capChannel setDelegate:nil];  // This is a no-op because _l2capChannel is nil
  // ...
  _strongSelf = nil; // This releases the delegate object
}

Because _l2capChannel is nil for outgoing connections, the delegate fails to call setDelegate:nil on the actual native channel object. When _strongSelf is cleared, the delegate is deallocated via ARC. However, the native IOBluetoothL2CAPChannel object inside the OS framework still holds an unsafe_unretained (raw) pointer to this deallocated delegate.

This implementation is inconsistent with the RFCOMM implementation in device/bluetooth/bluetooth_rfcomm_channel_mac.mm, which correctly provides a setter (setRfcommChannel:) to update the delegate’s channel reference during OpenAsync.

Potential Exploitation

(Note: These are suggested steps based on static analysis; a working proof-of-concept has not been executed.)

This UAF occurs in the Browser process. Furthermore, Objective-C objects are allocated using the system allocator and are not protected by MiraclePtr/PartitionAlloc.

An attacker could potentially trigger this by:

  1. Creating a malicious Web Extension that uses the chrome.bluetoothSocket API, or operating a malicious Bluetooth peripheral that Chrome connects to.
  2. Establishing an L2CAP connection.
  3. Racing a connection teardown (e.g., closing the socket) against incoming data packets from the peripheral.
  4. If a packet arrives and is processed by the OS after l2capChannelClosed: has released the delegate, but before the native channel is fully destroyed, the OS will attempt to dispatch an Objective-C message (e.g., l2capChannelData:) to the freed memory.
  5. Using heap spraying techniques, an attacker could replace the freed Objective-C object to gain control of the instruction pointer, leading to arbitrary code execution and a sandbox escape.

Suggested Fix

Similar to the RFCOMM implementation, update BluetoothL2capChannelDelegate to include a setter method for the channel, and call it after a successful connection:

  1. In BluetoothL2capChannelDelegate, add - (void)setL2capChannel:(IOBluetoothL2CAPChannel*)l2capChannel;.
  2. In BluetoothL2capChannelMac::OpenAsync, update the delegate after openL2CAPChannelAsync succeeds:
    if (*status == kIOReturnSuccess) {
      channel->channel_ = l2cap_channel;
      [channel->delegate_ setL2capChannel:l2cap_channel];
    } else {
    // ...
    

Evaluated with Chrome root at commit: 4a3e9db74111a3c6c4b3acfd70050a05077cf27a


Results 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.

View on issue tracker