Overview

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

Changed Functions

FunctionChangeNotes
if
device/bluetooth/bluetooth_discovery_manager_mac.mm
modified

Files Changed

  • device/bluetooth/bluetooth_discovery_manager_mac.mm
From 673a5aee77ad12e118c8edac73359acdeb7f491c Mon Sep 17 00:00:00 2001
From: Alvin Ji <[email protected]>
Date: Tue, 02 Jun 2026 13:04:49 -0700
Subject: [PATCH] Fix UAF in BluetoothDeviceInquiryDelegate on macOS

On destruction, BluetoothDiscoveryManagerMacClassic releases its strong
reference to BluetoothDeviceInquiryDelegate while an asynchronous
callback may be enqueued on the main run loop. The subsequent callback
execution on a deallocated receiver could lead to memory corruption.

This CL fixes the issue by:
1. Adding a `resetOwner` method to `BluetoothDeviceInquiryDelegate` to
   clear its weak pointer to the manager.
2. Checking if the manager is still valid in the delegate callbacks.
3. In `~BluetoothDiscoveryManagerMacClassic`, stopping the inquiry,
   resetting the delegate's owner, and keeping the delegate alive for
   one turn of the main run loop using `dispatch_async`.

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

diff --git a/device/bluetooth/bluetooth_discovery_manager_mac.mm b/device/bluetooth/bluetooth_discovery_manager_mac.mm
index bccb75c..42a26ab 100644
--- a/device/bluetooth/bluetooth_discovery_manager_mac.mm
+++ b/device/bluetooth/bluetooth_discovery_manager_mac.mm
@@ -26,6 +26,8 @@
 - (instancetype)initWithManager:
     (device::BluetoothDiscoveryManagerMacClassic*)manager;
 
+- (void)resetOwner;
+
 @end
 
 namespace device {
@@ -48,10 +50,21 @@
       const BluetoothDiscoveryManagerMacClassic&) = delete;
 
   ~BluetoothDiscoveryManagerMacClassic() override {
+    [inquiry_ stop];
+    [inquiry_delegate_ resetOwner];
     // IOBluetoothDeviceInquiry's delegate property is configured as "assign"
     // rather than "weak". If it is not manually reset then our delegate could be
     // accessed after we drop our strong reference and the object is freed.
     inquiry_.delegate = nil;
+
+    // Keep the delegate alive for one more turn of the main run loop to allow
+    // any already-enqueued callbacks to fire safely (and become no-ops since
+    // the owner has been reset to nullptr) rather than hitting a deallocated
+    // object. See FB13705522.
+    BluetoothDeviceInquiryDelegate* __strong delegate = inquiry_delegate_;
+    dispatch_async(dispatch_get_main_queue(), ^{
+      (void)delegate;
+    });
   }
 
   // BluetoothDiscoveryManagerMac override.
@@ -213,18 +226,31 @@
   return self;
 }
 
+- (void)resetOwner {
+  _manager = nullptr;
+}
+
 - (void)deviceInquiryStarted:(IOBluetoothDeviceInquiry*)sender {
+  if (!_manager) {
+    return;
+  }
   _manager->DeviceInquiryStarted(sender);
 }
 
 - (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry*)sender
                           device:(IOBluetoothDevice*)device {
+  if (!_manager) {
+    return;
+  }
   _manager->DeviceFound(sender, device);
 }
 
 - (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry*)sender
                         error:(IOReturn)error
                       aborted:(BOOL)aborted {
+  if (!_manager) {
+    return;
+  }
   _manager->DeviceInquiryComplete(sender, error, aborted);
 }
 
Loading diff…

Original Bug Report

reported by [email protected]

Potential macOS browser-process Use-After-Free in BluetoothDeviceInquiryDelegate

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 browser process due to a missing callback keep-alive in BluetoothDiscoveryManagerMacClassic. On destruction, the manager releases its strong reference to BluetoothDeviceInquiryDelegate while an asynchronous callback may be enqueued on the main run loop. The subsequent callback execution on a deallocated receiver could lead to memory corruption in the unsandboxed browser process.

Affected files:

  • device/bluetooth/bluetooth_discovery_manager_mac.mm

Estimated timestamp from git blame: 2014-06-13

Description

A potential Use-After-Free (UAF) vulnerability exists in device/bluetooth/bluetooth_discovery_manager_mac.mm due to a missing keep-alive mechanism for BluetoothDeviceInquiryDelegate during the destruction of BluetoothDiscoveryManagerMacClassic.

In macOS Chromium Bluetooth code, several classes use Objective-C delegates to receive asynchronous callbacks from system frameworks (such as IOBluetooth). Due to a known macOS issue (referenced internally as FB13705522), these system frameworks can enqueue asynchronous callback messages on the main thread’s run loop and capture the delegate’s raw address at enqueue time. If the owner is destroyed before these enqueued callbacks run, they will target a deallocated delegate receiver.

To mitigate this, a keep-alive campaign was implemented across sibling IOBluetooth delegate classes (e.g., in BluetoothRfcommChannelMac, BluetoothL2capChannelMac, BluetoothSocketMac, etc.) using dispatch_async to keep the delegate alive for an extra turn of the main run loop. However, BluetoothDiscoveryManagerMacClassic was missed.

Potential Root Cause

In device/bluetooth/bluetooth_discovery_manager_mac.mm, the destructor ~BluetoothDiscoveryManagerMacClassic only resets the delegate property of the inquiry object:

~BluetoothDiscoveryManagerMacClassic() override {
  inquiry_.delegate = nil;
}

Directly after this destructor body completes, the strong reference inquiry_delegate_ is released, and the BluetoothDeviceInquiryDelegate is deallocated. If an asynchronous callback was already enqueued by IOBluetooth, it will invoke objc_msgSend on the deallocated delegate pointer.

Potential Step-by-Step Trigger Sequence

Note: The following steps are theoretical and represent a potential path, as our tooling does not currently have the capability to execute code or verify exploitation dynamically:

  1. A page or extension triggers Bluetooth classic scanning (e.g., via Web Serial or chrome.bluetooth.startDiscovery()).
  2. BluetoothAdapterMac::StartScanWithFilter lazily initializes classic_discovery_manager_ (an instance of BluetoothDiscoveryManagerMacClassic) and starts the device inquiry via [inquiry_ start].
  3. A nearby physical Bluetooth device responds, causing the macOS framework to enqueue an asynchronous delegate callback (e.g., -deviceInquiryDeviceFound:device:) on the main run loop.
  4. The user closes the tab or navigates away, destroying the adapter and destroying classic_discovery_manager_ via its unique pointer.
  5. ~BluetoothDiscoveryManagerMacClassic() sets inquiry_.delegate = nil and immediately deallocates inquiry_delegate_.
  6. The enqueued callback on the main run loop executes, invoking objc_msgSend on the freed BluetoothDeviceInquiryDelegate address, potentially leading to memory corruption in the unsandboxed browser process.

Suggested Fix

To remediate this issue, implement the same lifetime extension workaround used by the sibling classes. Keep a strong reference to the delegate alive across one main run loop turn during destruction:

~BluetoothDiscoveryManagerMacClassic() override {
  inquiry_.delegate = nil;
  BluetoothDeviceInquiryDelegate* __strong delegate = inquiry_delegate_;
  dispatch_async(dispatch_get_main_queue(), ^{
    (void)delegate;
  });
}

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