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.
Tracker523704570
Fix commit30838188f5ff (chromium/src) +6/-3
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-06-13 (per vendor advisory)
Fix landed2026-06-18 (public git)
Disclosed2026-06-23
Patch gap5 days — fix public in source before the advisory

Background

`BluetoothSocketMac`
the macOS RFCOMM/L2CAP Bluetooth socket object, reference-counted, that owns an IOBluetooth channel plus its connect/receive callback state.
`ReleaseChannel`
the method that tears down the socket’s channel and callback/queue state; it runs on the socket’s checked thread.
`ConnectCallbacks` / `connect_callbacks_`
a std::unique_ptr-held bundle of callbacks whose destruction can, transitively, drop the last reference-counted reference to the owning BluetoothSocketMac.
Refcount-driven self-destruction
releasing a scoped_refptr/callback that captured the last reference to this can synchronously run the object’s destructor mid-method.

Root Cause Analysis

ReleaseChannel reset channel_, then called connect_callbacks_.reset() before touching receive_callbacks_, receive_queue_, and send_queue_. Destroying connect_callbacks_ could release the last reference to the BluetoothSocketMac (the connect callbacks or bound state transitively held a reference to this), causing this to be destroyed synchronously inside reset(). Execution would then return into ReleaseChannel and continue accessing member variables (receive_callbacks_, the queues) on already-freed this, a use-after-free. The invariant violated is that this must remain live for the duration of ReleaseChannel.

The fix moves connect_callbacks_ into a local std::unique_ptr<ConnectCallbacks> temp_connect instead of destroying it in place, so any last reference is not dropped until temp_connect is destroyed at method scope exit, keeping this alive through the remaining member accesses.

Key insight
The mistake was destroying connect_callbacks_ in the middle of ReleaseChannel when that destruction could drop the last reference to this; the fix defers the destruction to end-of-scope by transferring ownership into a local variable, guaranteeing this outlives the rest of the method.

Attack Path

  1. Reach channel release An attacker-influenced Bluetooth socket lifecycle event (disconnect/close/error) causes ReleaseChannel to run on the socket thread.
  2. Sole reference in callbacks At that point connect_callbacks_ (or state it holds) is the last holder of the reference-counted BluetoothSocketMac (inferred as the specific ownership chain).
  3. Synchronous self-destruction connect_callbacks_.reset() runs the callbacks’ destructor, which drops the final reference and synchronously destroys this.
  4. Use-after-free Control returns into ReleaseChannel, which then accesses receive_callbacks_, receive_queue_, and send_queue_ on the freed BluetoothSocketMac, reading/writing freed memory.
  5. Corrupt reclaimed memory If the freed socket storage has been reallocated, these member accesses operate on attacker-influenced memory, potentially leading to memory corruption (inferred).

Impact Assessment

An attacker able to drive Bluetooth socket connect/release on macOS such that the callback state holds the last reference can trigger a use-after-free in the browser process, yielding operations on freed memory that may be leveraged toward memory corruption (inferred). Preconditions are macOS with an active Bluetooth socket and a lifecycle event that invokes ReleaseChannel with the reference topology described. The vendor advisory rates this high severity and does not report in-the-wild exploitation; NVD scores it CVSS 8.8.

Files Changed

  • device/bluetooth/bluetooth_socket_mac.mm

Audit Directions

  • Refcounted self-destruction mid-method
    Audit methods that reset()/clear a smart pointer or callback that may transitively own the last reference to this, then continue touching members afterward; move such releases to end of scope or take a local scoped_refptr guard.
  • Callback bundles holding `this`
    Review objects whose stored callbacks capture this (or a self reference) and whose teardown paths destroy those callbacks before finishing cleanup.
  • Ordering in teardown/`Release`/`Close` methods
    Look across the codebase for teardown routines that free callback/queue state in an order where an early free can invalidate this before later statements execute.
From 30838188f5ff65357672bcd00dfaff982ca7ca25 Mon Sep 17 00:00:00 2001
From: Alvin Ji <alvinji@chromium.org>
Date: Wed, 17 Jun 2026 17:37:38 -0700
Subject: [PATCH] Fix Use-After-Free in BluetoothSocketMac::ReleaseChannel

Prevent synchronous destruction of BluetoothSocketMac when releasing the
channel by moving connect and receive callbacks to local variables. This
ensures the socket remains alive until ReleaseChannel finishes
execution, avoiding use-after-free when accessing other member
variables.

Change-Id: I872d682b6a4d0887b8f93e118db54e51f57321f5
Bug: 523704570
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7948137
Commit-Queue: Alvin Ji <alvinji@chromium.org>
Reviewed-by: Matt Reynolds <mattreynolds@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1648713}
---

diff --git a/device/bluetooth/bluetooth_socket_mac.mm b/device/bluetooth/bluetooth_socket_mac.mm
index 315f1ddf..abdc9a1 100644
--- a/device/bluetooth/bluetooth_socket_mac.mm
+++ b/device/bluetooth/bluetooth_socket_mac.mm
@@ -1038,9 +1038,12 @@
   DCHECK(thread_checker_.CalledOnValidThread());
   channel_.reset();
 
-  // Closing the channel above prevents the callback delegate from being called
-  // so it is now safe to release all callback state.
-  connect_callbacks_.reset();
+  // Move connect_callbacks_ to a local variable to prevent synchronous
+  // destruction of 'this' if it holds the last reference to the socket.
+  // This keeps 'this' alive until the end of this method.
+  std::unique_ptr<ConnectCallbacks> temp_connect =
+      std::move(connect_callbacks_);
+
   receive_callbacks_.reset();
   empty_queue(receive_queue_);
   empty_queue(send_queue_);
Loading diff…

Original Bug Report

reported by rj...@google.com

Potential Use-After-Free and Double-Free in BluetoothSocketMac::ReleaseChannel

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 Use-After-Free vulnerability exists in BluetoothSocketMac::ReleaseChannel on macOS when handling incoming Bluetooth connections. If an attacker disconnects during connection setup, a circular reference is broken, causing synchronous object destruction while methods are still executing on it. This leads to a deterministic double-free and heap freelist corruption in the Browser process.

Affected files:

  • device/bluetooth/bluetooth_socket_mac.mm

Estimated timestamp from git blame: Unknown (Google3 checkout)

Summary

A potential Use-After-Free (UAF) and Double-Free vulnerability exists in BluetoothSocketMac on macOS. It can be triggered when an incoming Bluetooth connection is closed before the connection setup is fully complete. This leads to a situation where the BluetoothSocketMac instance is destroyed while its ReleaseChannel method is still executing, resulting in memory corruption within the highly-privileged Browser process.

Root Cause Analysis

The vulnerability stems from a circular reference created during incoming connection handling, combined with the synchronous destruction of the socket object.

  1. Circular Reference Creation: In BluetoothSocketMac::AcceptConnectionRequest (device/bluetooth/bluetooth_socket_mac.mm), a new client_socket is created. A success_callback is bound using base::BindOnce (line 1001), which captures the scoped_refptr<BluetoothSocketMac> by value. This callback is stored in client_socket->connect_callbacks_. This establishes a cycle: client_socket owns connect_callbacks_, which owns the closure holding the scoped_refptr to client_socket. When the function returns, this closure holds the only reference (refcount = 1).
  2. The Race Condition: The code expects setting the channel delegate (SetSocket) to synchronously trigger the OS rfcommChannelOpenComplete: callback, which would post a task to the message loop (adding another reference). However, macOS IOBluetooth callbacks for already-open incoming channels are dispatched asynchronously. This leaves a window where the refcount remains exactly 1.
  3. Synchronous Destruction: If an attacker disconnects during this window, the OS triggers rfcommChannelClosed:, calling BluetoothSocketMac::OnChannelClosed(), which calls ReleaseChannel(). At line 1043, ReleaseChannel calls connect_callbacks_.reset(). This destroys the closure, dropping the refcount to 0. The ~BluetoothSocketMac() destructor immediately runs, freeing the object and its internal queues (receive_queue_, send_queue_).
  4. Use-After-Free & Double-Free: Execution returns to ReleaseChannel() immediately after the reset(), but this has now been freed. The code executes empty_queue(receive_queue_) (line 1045) on the freed object. The empty_queue helper uses std::swap with a local stack variable. This swap copies the already-freed buffer pointer into the local variable, and writes the local variable’s null state into the freed object memory (corrupting the PartitionAlloc freelist). When the local variable goes out of scope, it attempts to free() the queue’s internal structures a second time, causing a deterministic double-free.

Potential Exploitation Steps

(Note: These are suggested steps based on static analysis; a working Proof of Concept has not yet been executed by our tooling.)

  1. An attacker targets a Chrome instance on macOS where an extension is actively listening via the chrome.bluetoothSocket API.
  2. The attacker connects a malicious Bluetooth device to the advertised RFCOMM or L2CAP service.
  3. Immediately upon establishing the connection, the attacker sends a small payload (to ensure receive_queue_ allocates memory) and then forcibly disconnects the channel before the OS has time to dispatch the open-complete callback on the run loop.
  4. The disconnect triggers the UAF and Double-Free. By grooming the heap, the attacker could leverage the freelist corruption or the double-free to achieve arbitrary Remote Code Execution (RCE) in the unsandboxed Browser process.

Suggested Fix

To fix this issue, ReleaseChannel() must ensure that this is kept alive until the method finishes executing, or it must avoid triggering the destructor synchronously. The connect_callbacks_ should be moved into a local variable before destruction, a pattern already correctly used in BluetoothSocketMac::OnChannelOpeningTimeout() (line 712).

void BluetoothSocketMac::ReleaseChannel() {
  DCHECK(thread_checker_.CalledOnValidThread());
  channel_.reset();

  // Move callbacks to local variables to prevent synchronous destruction of
  // 'this' if they hold the last reference to the socket.
  std::unique_ptr<ConnectCallbacks> temp_connect = std::move(connect_callbacks_);
  std::unique_ptr<ReceiveCallbacks> temp_receive = std::move(receive_callbacks_);

  empty_queue(receive_queue_);
  empty_queue(send_queue_);

  // temp_connect and temp_receive are destroyed here, safely dropping refcounts
  // after we are done accessing member variables.
}

Evaluated with Chrome root at commit: 65b3256311f3ab6fb9870eaa522de7e6dd2663bb


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