Medium chrome Info Disclosure 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInformation leak in Audio
DescriptionInformation leak in Audio
ComponentAudio
Sub-componentAudio
Bug ClassInfo Disclosure
Tracker513365978
Fix commit552949a94f79 (chromium/src) +40/-20
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-06

Changed Functions

FunctionChangeNotes
if
services/audio/input_glitch_counter.cc
modified
if
services/audio/output_controller.cc
modified
if
services/audio/output_stream.cc
modified

Files Changed

  • services/audio/loopback_reference_manager.h
  • services/audio/input_sync_writer.cc
  • services/audio/output_stream.cc
  • services/audio/input_glitch_counter.cc
  • services/audio/output_controller.cc
  • services/audio/output_stream.h
  • services/audio/loopback_reference_manager.cc
  • services/audio/output_controller.h
  • services/audio/input_sync_writer.h
  • services/audio/input_glitch_counter.h
From 552949a94f798f24f75c894d44e6196cf4307bf5 Mon Sep 17 00:00:00 2001
From: henrika <[email protected]>
Date: Mon, 18 May 2026 04:16:40 -0700
Subject: [PATCH] audio: Fix ASLR bypass by replacing raw pointer logging with tokens

Several classes in services/audio were logging raw heap pointers
(e.g.,'this') in diagnostic messages, which could be used to bypass
ASLR.

This CL replaces these raw pointer logs with base::UnguessableToken
identifiers, following the pattern established in services/audio/input_stream.cc.

Affected classes:
    - OutputController
    - OutputStream
    - InputSyncWriter
    - InputGlitchCounter
    - LoopbackReferenceManagerCore

Bug: 513365978
Change-Id: Iad47193fc68e971d10a9ba3652aacefd88a6e70c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7852376
Commit-Queue: Henrik Andreasson <[email protected]>
Reviewed-by: Olga Sharonova <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1632075}
---
diff --git a/services/audio/input_glitch_counter.cc b/services/audio/input_glitch_counter.cc
index c6002e9..f93fddc 100644
--- a/services/audio/input_glitch_counter.cc
+++ b/services/audio/input_glitch_counter.cc
@@ -29,7 +29,8 @@
 
 InputGlitchCounter::InputGlitchCounter(
     base::RepeatingCallback<void(const std::string&)> log_callback)
-    : log_callback_(std::move(log_callback)) {
+    : id_(base::UnguessableToken::Create()),
+      log_callback_(std::move(log_callback)) {
   // Reserve one minutes worth of complete samples (assuming 10ms buffers) to
   // hopefully avoid allocating them on the realtime thread.
   complete_samples_.reserve(6);
@@ -61,9 +62,9 @@
       "AISW::%s => (number of detected audio glitches: %" PRIu64
       " out of %" PRIu64
       ") "
-      " [this=0x%" PRIXPTR "]",
+      " [id=%s]",
       __func__, global_sample_.dropped_data_count_, write_count_,
-      reinterpret_cast<uintptr_t>(this));
+      id_.ToString().c_str());
   log_callback_.Run(log_string);
 
   if (write_count_ < kSampleInterval) {
diff --git a/services/audio/input_glitch_counter.h b/services/audio/input_glitch_counter.h
index 60eb12d..f9b81ba6 100644
--- a/services/audio/input_glitch_counter.h
+++ b/services/audio/input_glitch_counter.h
@@ -7,8 +7,10 @@
 
 #include <cstddef>
 #include <vector>
+
 #include "base/functional/callback.h"
 #include "base/time/time.h"
+#include "base/unguessable_token.h"
 namespace audio {
 
 // Class for logging audio glitch data at 10 second intervals, for use by
@@ -68,6 +70,8 @@
     size_t trailing_dropped_data_count_ = 0;
   } global_sample_;
 
+  const base::UnguessableToken id_;
+
   base::RepeatingCallback<void(const std::string&)> log_callback_;
 
   // The number of calls to ReportMissedReadDeadline(), which corresponds to
diff --git a/services/audio/input_sync_writer.cc b/services/audio/input_sync_writer.cc
index 02865062..c7d73815 100644
--- a/services/audio/input_sync_writer.cc
+++ b/services/audio/input_sync_writer.cc
@@ -63,7 +63,8 @@
     uint32_t shared_memory_segment_count,
     const media::AudioParameters& params,
     std::unique_ptr<InputGlitchCounter> glitch_counter)
-    : log_callback_(std::move(log_callback)),
+    : id_(base::UnguessableToken::Create()),
+      log_callback_(std::move(log_callback)),
       socket_(std::move(socket)),
       shared_memory_region_(std::move(shared_memory)),
       shared_memory_mapping_(shared_memory_region_.Map()),
@@ -406,8 +407,7 @@
   va_start(args, format);
   log_callback_.Run(
       base::StrCat({"AISW::", UNSAFE_TODO(base::StringPrintV(format, args)),
-                    base::StringPrintf(" [this=0x%" PRIXPTR "]",
-                                       reinterpret_cast<uintptr_t>(this))}));
+                    base::StringPrintf(" [id=%s]", id_.ToString().c_str())}));
   va_end(args);
 }
 
diff --git a/services/audio/input_sync_writer.h b/services/audio/input_sync_writer.h
index 2ccd796..e91c173 100644
--- a/services/audio/input_sync_writer.h
+++ b/services/audio/input_sync_writer.h
@@ -15,6 +15,7 @@
 #include "base/memory/unsafe_shared_memory_region.h"
 #include "base/sync_socket.h"
 #include "base/time/time.h"
+#include "base/unguessable_token.h"
 #include "build/build_config.h"
 #include "input_glitch_counter.h"
 #include "media/base/audio_bus.h"
@@ -108,6 +109,8 @@
   // Helper method for creating internal log messages prefixed with "AISW::".
   PRINTF_FORMAT(2, 3) void SendLogMessage(const char* format, ...);
 
+  const base::UnguessableToken id_;
+
   const base::RepeatingCallback<void(const std::string&)> log_callback_;
 
   // Socket used to signal that audio data is ready.
diff --git a/services/audio/loopback_reference_manager.cc b/services/audio/loopback_reference_manager.cc
index 3510ed88..a455f6e 100644
--- a/services/audio/loopback_reference_manager.cc
+++ b/services/audio/loopback_reference_manager.cc
@@ -110,7 +110,8 @@
       media::AudioManager* audio_manager,
       LoopbackReferenceStreamIdProvider* stream_id_provider,
       ErrorCallback on_error_callback)
-      : audio_manager_(audio_manager),
+      : id_(base::UnguessableToken::Create()),
+        audio_manager_(audio_manager),
         glitch_reporter_(
             media::SystemGlitchReporter::StreamType::kLoopbackReference),
         task_runner_(base::SequencedTaskRunner::GetCurrentDefault()),
@@ -141,8 +142,8 @@
     }
 
     audio_log_->OnLogMessage(base::StringPrintf(
-        "LRMC::%s [id=%u] [this=0x%" PRIXPTR "]", message.c_str(),
-        stream_id_provider_->GetId(), reinterpret_cast<uintptr_t>(this)));
+        "LRMC::%s [stream_id=%u] [id=%s]", message.c_str(),
+        stream_id_provider_->GetId(), id_.ToString().c_str()));
   }
 
   void ReportAndResetGlitchStats() {
@@ -287,6 +288,7 @@
     audio_log_.reset();
   }
 
+  const base::UnguessableToken id_;
   const raw_ptr<media::AudioManager> audio_manager_;
   media::SystemGlitchReporter glitch_reporter_;
   const scoped_refptr<base::SequencedTaskRunner> task_runner_;
diff --git a/services/audio/loopback_reference_manager.h b/services/audio/loopback_reference_manager.h
index 2dd37c6..30f3c9d6 100644
--- a/services/audio/loopback_reference_manager.h
+++ b/services/audio/loopback_reference_manager.h
@@ -10,6 +10,7 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/sequence_checker.h"
+#include "base/unguessable_token.h"
 #include "media/audio/audio_manager.h"
 #include "services/audio/reference_signal_provider.h"
 
diff --git a/services/audio/output_controller.cc b/services/audio/output_controller.cc
index 5bd7433..2a254ef 100644
--- a/services/audio/output_controller.cc
+++ b/services/audio/output_controller.cc
@@ -167,7 +167,8 @@
     SyncReader* sync_reader,
     ManagedDeviceOutputStreamCreateCallback
         managed_device_output_stream_create_callback)
-    : audio_manager_(audio_manager),
+    : id_(base::UnguessableToken::Create()),
+      audio_manager_(audio_manager),
       params_(params),
       managed_device_output_stream_create_callback_(
           std::move(managed_device_output_stream_create_callback)),
@@ -534,11 +535,11 @@
 }
 
 void OutputController::SendLogMessage(const std::string& message) {
-  if (!handler_)
+  if (!handler_) {
     return;
-  handler_->OnLog(base::StringPrintf("AOC::%s [this=0x%" PRIXPTR "]",
-                                     message.c_str(),
-                                     reinterpret_cast<uintptr_t>(this)));
+  }
+  handler_->OnLog(base::StringPrintf("AOC::%s [id=%s]", message.c_str(),
+                                     id_.ToString().c_str()));
 }
 
 void OutputController::LogAudioPowerLevel(const char* call_name) {
diff --git a/services/audio/output_controller.h b/services/audio/output_controller.h
index a926070..21a7e2e 100644
--- a/services/audio/output_controller.h
+++ b/services/audio/output_controller.h
@@ -142,6 +142,8 @@
 
   ~OutputController() override;
 
+  const base::UnguessableToken& id() const { return id_; }
+
   // Methods to control playback of the stream.
 
   // Creates the audio output stream. This must be called before Play(). Returns
@@ -300,6 +302,8 @@
   // being called.
   void ProcessDeviceChange();
 
+  const base::UnguessableToken id_;
+
   const raw_ptr<media::AudioManager> audio_manager_;
   const media::AudioParameters params_;
 
diff --git a/services/audio/output_stream.cc b/services/audio/output_stream.cc
index c857c84..85ec7e0 100644
--- a/services/audio/output_stream.cc
+++ b/services/audio/output_stream.cc
@@ -175,7 +175,8 @@
     const media::AudioParameters& params,
     LoopbackCoordinator* coordinator,
     const base::UnguessableToken& loopback_group_id)
-    : delete_callback_(std::move(delete_callback)),
+    : id_(base::UnguessableToken::Create()),
+      delete_callback_(std::move(delete_callback)),
       receiver_(this, std::move(stream_receiver)),
       device_switch_receiver_(this, std::move(device_switch_receiver)),
       observer_(std::move(observer)),
@@ -464,12 +465,12 @@
 }
 
 void OutputStream::SendLogMessage(const std::string& message) {
-  if (!log_)
+  if (!log_) {
     return;
-  log_->OnLogMessage(
-      "audio::OS::" + message +
-      base::StringPrintf(" [controller=0x%" PRIXPTR "]",
-                         reinterpret_cast<uintptr_t>(&controller_)));
+  }
+  log_->OnLogMessage(base::StringPrintf(
+      "audio::OS::%s [id=%s] [controller_id=%s]", message.c_str(),
+      id_.ToString().c_str(), controller_.id().ToString().c_str()));
 }
 
 // Static
diff --git a/services/audio/output_stream.h b/services/audio/output_stream.h
index 6a56b089..4d30a6dc 100644
--- a/services/audio/output_stream.h
+++ b/services/audio/output_stream.h
@@ -15,6 +15,7 @@
 #include "base/sequence_checker.h"
 #include "base/sync_socket.h"
 #include "base/timer/timer.h"
+#include "base/unguessable_token.h"
 #include "media/mojo/mojom/audio_data_pipe.mojom.h"
 #include "media/mojo/mojom/audio_logging.mojom.h"
 #include "media/mojo/mojom/audio_output_stream.mojom.h"
@@ -124,6 +125,8 @@
 
   SEQUENCE_CHECKER(owning_sequence_);
 
+  const base::UnguessableToken id_;
+
   base::CancelableSyncSocket foreign_socket_;
   DeleteCallback delete_callback_;
   mojo::Receiver<AudioOutputStream> receiver_;
Loading diff…

Original Bug Report

reported by [email protected]

ASLR bypass via raw pointer leakage in audio service logs

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 without the Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: Multiple audio service components log raw heap pointers which are broadcast to renderers via WebRTC diagnostic logs. On Android and ChromeOS, where the audio service runs in the browser process, this allows a compromised renderer with specific permissions to obtain browser-process addresses and bypass ASLR.

Affected files:

  • services/audio/output_controller.cc
  • services/audio/output_stream.cc
  • services/audio/input_sync_writer.cc
  • services/audio/input_glitch_counter.cc
  • services/audio/loopback_reference_manager.cc

Estimated timestamp from git blame: 2021-04-01

Summary

Multiple classes within the audio service include raw heap pointers in their diagnostic log messages. These logs are transmitted via Mojo to the browser process, where they are broadcast to all renderers that have registered a WebRTC log callback. On platforms such as Android and ChromeOS, where the audio service runs within the browser process, this leakage allows a compromised renderer to obtain live browser-process heap addresses, providing a deterministic primitive for bypassing ASLR.

Root Cause Analysis

Several components in services/audio/ explicitly log the this pointer or other object addresses using the %PRIXPTR format specifier. These logs are intended for field debugging but fail to mask sensitive memory locations.

Key occurrences include:

  • services/audio/output_controller.cc: OutputController::SendLogMessage logs [this=0x... ] (line 539).
  • services/audio/output_stream.cc: OutputStream::SendLogMessage logs the address of its controller_ member (line 471).
  • services/audio/input_sync_writer.cc: InputSyncWriter::SendLogMessage logs [this=0x... ] (line 409).
  • services/audio/input_glitch_counter.cc: InputGlitchCounter::Log logs [this=0x... ] (line 64).
  • services/audio/loopback_reference_manager.cc: LoopbackReferenceManager::SendLogMessage logs [this=0x... ] (line 144).

Vulnerability Path

  1. Logging: An audio service component generates a log message containing a raw pointer.
  2. Mojo Transmission: The message is sent through the media::mojom::AudioLog interface to MediaInternals::AudioLogImpl in the browser process.
  3. Broadcast: MediaInternals forwards the message to MediaStreamManager::SendMessageToNativeLog, which broadcasts it to all callbacks registered in log_callbacks_ on the IO thread without verifying the origin or the target renderer (content/browser/renderer_host/media/media_stream_manager.cc:3537).
  4. Renderer Access: A compromised renderer belonging to an extension with the webrtcLoggingPrivate permission (such as the Hangouts component extension) can register a log callback. It can then retrieve these logs—including the leaked pointers—via the chrome.webrtcLoggingPrivate.store and getLogsDirectory APIs.

Impact

This vulnerability provides a reliable primitive to bypass ASLR:

  • Android and ChromeOS: The audio service runs in the browser process by default (kAudioServiceOutOfProcess is disabled). The leaked pointers are live browser-process PartitionAlloc heap addresses.
  • Linux: The audio service runs out-of-process but is typically unsandboxed, leading to an ASLR bypass for that process.

Potential Steps to Reproduce

  1. Compromise a renderer process that has access to the webrtcLoggingPrivate API (e.g., via the Hangouts extension).
  2. Invoke chrome.webrtcLoggingPrivate.start() to begin collecting diagnostic logs.
  3. Trigger audio activity (e.g., play a video or trigger a system notification) to generate audio service logs.
  4. Invoke chrome.webrtcLoggingPrivate.store() followed by chrome.webrtcLoggingPrivate.getLogsDirectory() to access the log files.
  5. Read and parse the log files (e.g., looking for the AOC:: or AISW:: prefixes) to extract hex-encoded heap addresses.

Replace the use of raw pointers in log messages with non-sensitive unique identifiers, such as base::UnguessableToken, for diagnostic correlation. This pattern is already correctly implemented in services/audio/input_stream.cc.

Evaluated with Chrome root at commit: b3153093eb3c78c3e88ccf562bcbc20437a04b0e


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