Low watch CVSS 5.3 chrome Info Disclosure 🔧 Commit mapped

Overview

Low
Severity
5.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInformation leak in Bluetooth
DescriptionInformation leak in Bluetooth
ComponentBluetooth
Bug ClassInfo Disclosure
Action tierwatch — information disclosure — security-relevant, below the RCE-class bar
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.
Tracker500484520
Fix commitee79c0611ff7 (chromium/src) +19/-20
CWECWE-200 (Information exposure)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-04-07 (per vendor advisory)
Fix landed2026-07-13 (public git)
Disclosed2026-08-25
Patch gap43 days — fix public in source before the advisory

Background

GATT identifier
a per-object string (identifier_) that WebBluetooth assigns to each remote GATT service, characteristic, and descriptor to reference it across the Mojo boundary.
PartitionAlloc Everywhere (PA-E)
macOS Chromium’s allocator configuration under which formatting a live object pointer with %p yields a real 64-bit virtual heap address.
`base::UnguessableToken`
a cryptographically random, stateless, thread-safe token used to produce unique identifiers without encoding any address or predictable state.
`operator<<` logging
the stream operators for the Mac GATT objects that previously logged raw object pointers (&characteristic, service_mac, etc.).

Root Cause Analysis

On macOS, BluetoothRemoteGattServiceMac, BluetoothRemoteGattCharacteristicMac, and BluetoothRemoteGattDescriptorMac built their identifier_ with the Objective-C stringWithFormat:@"%s-%p", embedding the address of the backing CoreBluetooth object into the identifier string. Because macOS Chromium uses PA-E, %p renders an actual 64-bit browser-process heap address, and that address became part of a value handed across Mojo IPC to the renderer. A compromised renderer receiving these identifiers could recover the raw pointers and use them to infer browser heap layout, defeating ASLR. The invariant violated is that data crossing the sandbox boundary to the renderer must not encode absolute browser memory addresses.

The fix replaces the %p component with base::UnguessableToken::Create().ToString() via base::StrCat, giving each object an unguessable, address-free unique suffix, and rewrites the operator<< overloads to log GetIdentifier() instead of raw pointers so the leak does not reappear in logs.

Key insight
The single mistake was constructing renderer-visible GATT identifiers from a live heap pointer via %p; the fix substitutes a random base::UnguessableToken so identifiers stay unique without disclosing any address.

Attack Path

  1. Compromise or control a renderer The attacker runs in (or has compromised) a renderer process that can use WebBluetooth.
  2. Enumerate GATT objects Web content triggers discovery of remote GATT services, characteristics, and descriptors, causing the Mac backends to construct identifier_ strings from %p.
  3. Receive identifiers over Mojo The identifier strings, containing embedded 64-bit heap addresses, are sent to the renderer over Mojo IPC.
  4. Recover heap addresses and defeat ASLR The renderer parses the pointer values out of the identifiers to map browser-process heap layout and bypass ASLR, aiding a later exploit. (inferred)

Impact Assessment

A compromised or malicious renderer with WebBluetooth access gains disclosure of browser-process heap pointer addresses, enabling it to bypass ASLR when constructing a further exploit (inferred). The vendor advisory rates this low severity and does not report in-the-wild exploitation; NVD scores it CVSS 5.3. The precondition is renderer-side WebBluetooth usage on a macOS build using PA-E.

Changed Functions

FunctionChangeNotes
weak_ptr_factory_
device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm
modified
discovery_pending_count_
device/bluetooth/bluetooth_remote_gatt_service_mac.mm
modified

Files Changed

  • device/bluetooth/bluetooth_remote_gatt_descriptor_mac.mm
  • device/bluetooth/bluetooth_remote_gatt_service_mac.mm
  • device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm

Audit Directions

  • `%p` in cross-process strings
    Search for stringWithFormat/sprintf/StringPrintf uses of %p (or streamed raw pointers) whose result reaches the renderer via Mojo, especially under PA-E where addresses are real.
  • Pointers used as identity
    Audit places that derive unique IDs from object addresses; replace address-derived uniqueness with base::UnguessableToken or opaque handles when the ID is externally visible.
  • `operator<<` and logging leaks
    Review stream operators and log statements that print &obj or object pointers, since these can surface addresses in logs and, if incorporated into IPC payloads, cross the sandbox boundary.
From ee79c0611ff70932f2672da3d580b2fe69b7b8d0 Mon Sep 17 00:00:00 2001
From: Rob Pitkin <robpitkin@google.com>
Date: Mon, 13 Jul 2026 09:57:17 -0700
Subject: [PATCH] Prevent macOS WebBluetooth GATT identifiers from leaking heap pointers

On macOS, BluetoothRemoteGattServiceMac, BluetoothRemoteGattCharacteristicMac,
and BluetoothRemoteGattDescriptorMac constructed GATT identifiers using Objective-C
format string stringWithFormat:@"%s-%p". Because macOS Chromium uses PartitionAlloc
Everywhere (PA-E), formatting %p embeds 64-bit virtual heap addresses into the identifier string.

When these strings are sent over Mojo IPC to the renderer process, a compromised
renderer could read these pointer addresses to map browser heap layout and bypass ASLR.

Replace %p with base::UnguessableToken::Create().ToString().c_str() to ensure
cryptographically unguessable, stateless, thread-safe uniqueness without leaking
browser heap layout. Also update operator<< logging to log GetIdentifier().

TAG=agy
CONV=60ab6f7d-f6e6-4bf5-8b14-067aefab4126

Bug: 500484520
Change-Id: Ic69106765f6a13c6746601321b50a13336f67412
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8025918
Reviewed-by: Matt Reynolds <mattreynolds@chromium.org>
Commit-Queue: Rob Pitkin <robpitkin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1661181}
---
diff --git a/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm b/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm
index b2ba785..20a2cfe2 100644
--- a/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm
+++ b/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm
@@ -9,9 +9,11 @@
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "base/notimplemented.h"
+#include "base/strings/strcat.h"
 #include "base/strings/sys_string_conversions.h"
 #import "base/task/single_thread_task_runner.h"
 #include "base/task/single_thread_task_runner.h"
+#include "base/unguessable_token.h"
 #include "device/bluetooth/bluetooth_device_mac.h"
 #include "device/bluetooth/bluetooth_gatt_notify_session.h"
 #include "device/bluetooth/bluetooth_low_energy_adapter_apple.h"
@@ -80,9 +82,8 @@
       weak_ptr_factory_(this) {
   uuid_ = BluetoothLowEnergyAdapterApple::BluetoothUUIDWithCBUUID(
       [cb_characteristic_ UUID]);
-  identifier_ = base::SysNSStringToUTF8(
-      [NSString stringWithFormat:@"%s-%p", uuid_.canonical_value().c_str(),
-                                 cb_characteristic_]);
+  identifier_ = base::StrCat({uuid_.canonical_value(), "-",
+                              base::UnguessableToken::Create().ToString()});
 }
 
 BluetoothRemoteGattCharacteristicMac::~BluetoothRemoteGattCharacteristicMac() {
@@ -483,9 +484,7 @@
       static_cast<const BluetoothRemoteGattServiceMac*>(
           characteristic.GetService());
   return out << "<BluetoothRemoteGattCharacteristicMac "
-             << characteristic.GetUUID().canonical_value() << "/"
-             << &characteristic
-             << ", service: " << service_mac->GetUUID().canonical_value() << "/"
-             << service_mac << ">";
+             << characteristic.GetIdentifier()
+             << ", service: " << service_mac->GetIdentifier() << ">";
 }
 }  // namespace device.
diff --git a/device/bluetooth/bluetooth_remote_gatt_descriptor_mac.mm b/device/bluetooth/bluetooth_remote_gatt_descriptor_mac.mm
index 169dab0..dbc25d7 100644
--- a/device/bluetooth/bluetooth_remote_gatt_descriptor_mac.mm
+++ b/device/bluetooth/bluetooth_remote_gatt_descriptor_mac.mm
@@ -7,9 +7,11 @@
 #import "base/apple/foundation_util.h"
 #include "base/functional/bind.h"
 #include "base/notimplemented.h"
+#include "base/strings/strcat.h"
 #include "base/strings/sys_string_conversions.h"
 #import "base/task/single_thread_task_runner.h"
 #include "base/task/single_thread_task_runner.h"
+#include "base/unguessable_token.h"
 #include "device/bluetooth/bluetooth_low_energy_adapter_apple.h"
 #import "device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h"
 
@@ -50,9 +52,8 @@
     : gatt_characteristic_(characteristic), cb_descriptor_(descriptor) {
   uuid_ = BluetoothLowEnergyAdapterApple::BluetoothUUIDWithCBUUID(
       [cb_descriptor_ UUID]);
-  identifier_ = base::SysNSStringToUTF8(
-      [NSString stringWithFormat:@"%s-%p", uuid_.canonical_value().c_str(),
-                                 cb_descriptor_]);
+  identifier_ = base::StrCat({uuid_.canonical_value(), "-",
+                              base::UnguessableToken::Create().ToString()});
 }
 
 std::string BluetoothRemoteGattDescriptorMac::GetIdentifier() const {
@@ -188,10 +189,9 @@
       static_cast<const BluetoothRemoteGattCharacteristicMac*>(
           descriptor.GetCharacteristic());
   return out << "<BluetoothRemoteGattDescriptorMac "
-             << descriptor.GetUUID().canonical_value() << "/" << &descriptor
-             << ", characteristic: "
-             << characteristic_mac->GetUUID().canonical_value() << "/"
-             << characteristic_mac << ">";
+             << descriptor.GetIdentifier()
+             << ", characteristic: " << characteristic_mac->GetIdentifier()
+             << ">";
 }
 
 }  // namespace device.
diff --git a/device/bluetooth/bluetooth_remote_gatt_service_mac.mm b/device/bluetooth/bluetooth_remote_gatt_service_mac.mm
index 1a51be0..81900c1 100644
--- a/device/bluetooth/bluetooth_remote_gatt_service_mac.mm
+++ b/device/bluetooth/bluetooth_remote_gatt_service_mac.mm
@@ -12,7 +12,9 @@
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "base/notimplemented.h"
+#include "base/strings/strcat.h"
 #include "base/strings/sys_string_conversions.h"
+#include "base/unguessable_token.h"
 #include "device/bluetooth/bluetooth_low_energy_adapter_apple.h"
 #include "device/bluetooth/bluetooth_low_energy_device_mac.h"
 #include "device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h"
@@ -30,8 +32,8 @@
       discovery_pending_count_(0) {
   uuid_ =
       BluetoothLowEnergyAdapterApple::BluetoothUUIDWithCBUUID([service_ UUID]);
-  identifier_ = base::SysNSStringToUTF8([NSString
-      stringWithFormat:@"%s-%p", uuid_.canonical_value().c_str(), service_]);
+  identifier_ = base::StrCat({uuid_.canonical_value(), "-",
+                              base::UnguessableToken::Create().ToString()});
 }
 
 BluetoothRemoteGattServiceMac::~BluetoothRemoteGattServiceMac() {}
@@ -195,10 +197,8 @@
     const BluetoothRemoteGattServiceMac& service) {
   const BluetoothLowEnergyDeviceMac* bluetooth_device_mac_ =
       static_cast<const BluetoothLowEnergyDeviceMac*>(service.GetDevice());
-  return out << "<BluetoothRemoteGattServiceMac "
-             << service.GetUUID().canonical_value() << "/" << &service
-             << ", device: " << bluetooth_device_mac_->GetAddress() << "/"
-             << bluetooth_device_mac_ << ">";
+  return out << "<BluetoothRemoteGattServiceMac " << service.GetIdentifier()
+             << ", device: " << bluetooth_device_mac_->GetAddress() << ">";
 }
 
 }  // namespace device
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.