CVE-2026-9881
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifdevice/bluetooth/bluetooth_l2cap_channel_mac.mm |
modified |
Files Changed
device/bluetooth/bluetooth_l2cap_channel_mac.mm
Patch
From 718de6b8e9b77d656b340d708f79768098f576cc Mon Sep 17 00:00:00 2001 From: Andrew Paseltiner <[email protected]> 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 <[email protected]> Commit-Queue: Andrew Paseltiner <[email protected]> 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; }
Original Bug Report
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:
- A
BluetoothL2capChannelMacwrapper object is constructed with its internalchannel_member explicitly set tonil(line 124). - The constructor immediately calls
SetSocket(), which instantiates aBluetoothL2capChannelDelegate. The delegate is initialized usingchannel_, meaning its internal_l2capChannelivar is set tonil(lines 147-148). [device openL2CAPChannelAsync:...]is called, which creates the nativeIOBluetoothL2CAPChanneland registers the delegate with it (line 128).BluetoothL2capChannelMac::channel_is updated with the new native channel object (line 132). However, the delegate’s internal_l2capChannelivar 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:
- Creating a malicious Web Extension that uses the
chrome.bluetoothSocketAPI, or operating a malicious Bluetooth peripheral that Chrome connects to. - Establishing an L2CAP connection.
- Racing a connection teardown (e.g., closing the socket) against incoming data packets from the peripheral.
- 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. - 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:
- In
BluetoothL2capChannelDelegate, add- (void)setL2capChannel:(IOBluetoothL2CAPChannel*)l2capChannel;. - In
BluetoothL2capChannelMac::OpenAsync, update the delegate afteropenL2CAPChannelAsyncsucceeds: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.