Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Audio
DescriptionUse after free in Audio
ComponentAudio
Bug ClassUAF
Tracker501557633
Fix commit64a4738a2796 (chromium/src) +51/-29
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
if
media/audio/win/audio_ducker_win.cc
modified
AudioSessionCreationObserverWin
media/audio/win/audio_session_creation_observer_win.h
modified
MEDIA_EXPORT
media/audio/win/audio_session_creation_observer_win.h
modified
AudioSessionCreationObserverWinTest
media/audio/win/audio_session_creation_observer_win_unittest.cc
modified
TEST_F
media/audio/win/audio_session_creation_observer_win_unittest.cc
modified

Files Changed

  • media/audio/BUILD.gn
  • media/audio/win/audio_ducker_win.cc
  • media/audio/win/audio_ducker_win.h
  • media/audio/win/audio_session_creation_observer_win.cc
  • media/audio/win/audio_session_creation_observer_win.h
  • media/audio/win/audio_session_creation_observer_win_unittest.cc
From 64a4738a27969f8d9af3f68e8a272a4f0f98342d Mon Sep 17 00:00:00 2001
From: Tommy Steimel <[email protected]>
Date: Mon, 20 Apr 2026 02:21:33 -0700
Subject: [PATCH] Ensure AudioSessionCreationObserverWin is not freed too early

This CL changes AudioDuckerWin's audio session creation observer to be
a COM object that lives in a ComPtr so that when the audio system holds
a reference to it it is guaranteed to stay alive. This prevents a
theoretically possible race condition where the audio system has queued
callbacks to the observer when it is being deleted.

Bug: 501557633
Change-Id: I1087efce7815bdb458d9f5f93391fd2083bb7b0d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7770577
Reviewed-by: Henrik Andreasson <[email protected]>
Commit-Queue: Henrik Andreasson <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1617341}
---

diff --git a/media/audio/BUILD.gn b/media/audio/BUILD.gn
index d3ceb8c..21d076f 100644
--- a/media/audio/BUILD.gn
+++ b/media/audio/BUILD.gn
@@ -530,6 +530,7 @@
       "win/audio_low_latency_input_win_unittest.cc",
       "win/audio_low_latency_output_win_unittest.cc",
       "win/audio_output_win_unittest.cc",
+      "win/audio_session_creation_observer_win_unittest.cc",
       "win/audio_session_event_listener_win_unittest.cc",
       "win/core_audio_util_win_unittest.cc",
       "win/device_enumeration_win_unittest.cc",
diff --git a/media/audio/win/audio_ducker_win.cc b/media/audio/win/audio_ducker_win.cc
index 0afab9a..def972f 100644
--- a/media/audio/win/audio_ducker_win.cc
+++ b/media/audio/win/audio_ducker_win.cc
@@ -121,13 +121,13 @@
 
   if (!session_creation_observer_) {
     session_creation_observer_ =
-        std::make_unique<AudioSessionCreationObserverWin>(
+        Microsoft::WRL::Make<AudioSessionCreationObserverWin>(
             base::BindPostTaskToCurrentDefault(base::BindRepeating(
                 &AudioDuckerWin::DuckNewAudioSessionsIfNecessary,
                 weak_factory_.GetWeakPtr())));
   }
   ducked_audio_session_manager_->RegisterSessionNotification(
-      session_creation_observer_.get());
+      session_creation_observer_.Get());
 
   // `base::Unretained()` is safe here because this callback is called
   // synchronously.
@@ -151,7 +151,7 @@
     if (ducked_audio_session_manager_) {
       CHECK(session_creation_observer_);
       ducked_audio_session_manager_->UnregisterSessionNotification(
-          session_creation_observer_.get());
+          session_creation_observer_.Get());
       ducked_audio_session_manager_.Reset();
     }
     return;
@@ -174,7 +174,7 @@
 
   ducked_applications_.clear();
   ducked_audio_session_manager_->UnregisterSessionNotification(
-      session_creation_observer_.get());
+      session_creation_observer_.Get());
   ducked_audio_session_manager_.Reset();
 }
 
diff --git a/media/audio/win/audio_ducker_win.h b/media/audio/win/audio_ducker_win.h
index 691dc5e..18bf98ff 100644
--- a/media/audio/win/audio_ducker_win.h
+++ b/media/audio/win/audio_ducker_win.h
@@ -58,7 +58,8 @@
   void DuckNewAudioSessionsIfNecessary();
 
   // Listens for audio session creation notifications from the OS.
-  std::unique_ptr<AudioSessionCreationObserverWin> session_creation_observer_;
+  Microsoft::WRL::ComPtr<AudioSessionCreationObserverWin>
+      session_creation_observer_;
 
   // Used to determine which applications we should duck.
   ShouldDuckProcessCallback should_duck_process_callback_;
diff --git a/media/audio/win/audio_session_creation_observer_win.cc b/media/audio/win/audio_session_creation_observer_win.cc
index 4fdf90e..3df84ef3 100644
--- a/media/audio/win/audio_session_creation_observer_win.cc
+++ b/media/audio/win/audio_session_creation_observer_win.cc
@@ -16,25 +16,6 @@
 
 AudioSessionCreationObserverWin::~AudioSessionCreationObserverWin() = default;
 
-ULONG AudioSessionCreationObserverWin::AddRef() {
-  return 1;
-}
-
-ULONG AudioSessionCreationObserverWin::Release() {
-  return 1;
-}
-
-HRESULT AudioSessionCreationObserverWin::QueryInterface(REFIID iid,
-                                                        void** object) {
-  if (iid == IID_IUnknown || iid == __uuidof(IAudioSessionNotification)) {
-    *object = static_cast<IAudioSessionNotification*>(this);
-    return S_OK;
-  }
-
-  *object = nullptr;
-  return E_NOINTERFACE;
-}
-
 HRESULT AudioSessionCreationObserverWin::OnSessionCreated(
     IAudioSessionControl* session) {
   session_created_cb_.Run();
diff --git a/media/audio/win/audio_session_creation_observer_win.h b/media/audio/win/audio_session_creation_observer_win.h
index 54982df..8a5ef67 100644
--- a/media/audio/win/audio_session_creation_observer_win.h
+++ b/media/audio/win/audio_session_creation_observer_win.h
@@ -6,14 +6,19 @@
 #define MEDIA_AUDIO_WIN_AUDIO_SESSION_CREATION_OBSERVER_WIN_H_
 
 #include <audiopolicy.h>
+#include <wrl/implements.h>
 
 #include "base/functional/callback.h"
+#include "media/base/media_export.h"
 
 namespace media {
 
 // Calls the given callback when notified by the IAudioSessionManager2 that an
 // audio session was created.
-class AudioSessionCreationObserverWin : public IAudioSessionNotification {
+class MEDIA_EXPORT AudioSessionCreationObserverWin
+    : public Microsoft::WRL::RuntimeClass<
+          Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
+          IAudioSessionNotification> {
  public:
   // `session_created_callback` is called when the system notifies us of a new
   // audio session via `OnSessionCreated()`.
@@ -23,12 +28,9 @@
       delete;
   AudioSessionCreationObserverWin& operator=(
       const AudioSessionCreationObserverWin&) = delete;
-  virtual ~AudioSessionCreationObserverWin();
+  ~AudioSessionCreationObserverWin() override;
 
   // IAudioSessionNotification:
-  IFACEMETHODIMP_(ULONG) AddRef() override;
-  IFACEMETHODIMP_(ULONG) Release() override;
-  IFACEMETHODIMP QueryInterface(REFIID iid, void** object) override;
   IFACEMETHODIMP OnSessionCreated(IAudioSessionControl* session) override;
 
  private:
diff --git a/media/audio/win/audio_session_creation_observer_win_unittest.cc b/media/audio/win/audio_session_creation_observer_win_unittest.cc
new file mode 100644
index 0000000..8015888
--- /dev/null
+++ b/media/audio/win/audio_session_creation_observer_win_unittest.cc
@@ -0,0 +1,37 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/audio/win/audio_session_creation_observer_win.h"
+
+#include <memory>
+
+#include "base/functional/callback_helpers.h"
+#include "base/test/mock_callback.h"
+#include "base/win/scoped_com_initializer.h"
+#include "media/audio/audio_unittest_util.h"
+#include "media/audio/win/core_audio_util_win.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+class AudioSessionCreationObserverWinTest : public testing::Test {
+ public:
+  AudioSessionCreationObserverWinTest() = default;
+  ~AudioSessionCreationObserverWinTest() override = default;
+
+ protected:
+  base::win::ScopedCOMInitializer com_init_;
+};
+
+TEST_F(AudioSessionCreationObserverWinTest, NotifiesCallback) {
+  base::MockRepeatingClosure session_created_callback;
+  auto observer = Microsoft::WRL::Make<AudioSessionCreationObserverWin>(
+      session_created_callback.Get());
+
+  EXPECT_CALL(session_created_callback, Run()).Times(1);
+  observer->OnSessionCreated(nullptr);
+}
+
+}  // namespace media
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/media/audio/win/audio_session_creation_observer_win_unittest.cc b/media/audio/win/audio_session_creation_observer_win_unittest.cc
new file mode 100644
index 0000000..8015888
--- /dev/null
+++ b/media/audio/win/audio_session_creation_observer_win_unittest.cc
@@ -0,0 +1,37 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/audio/win/audio_session_creation_observer_win.h"
+
+#include <memory>
+
+#include "base/functional/callback_helpers.h"
+#include "base/test/mock_callback.h"
+#include "base/win/scoped_com_initializer.h"
+#include "media/audio/audio_unittest_util.h"
+#include "media/audio/win/core_audio_util_win.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+class AudioSessionCreationObserverWinTest : public testing::Test {
+ public:
+  AudioSessionCreationObserverWinTest() = default;
+  ~AudioSessionCreationObserverWinTest() override = default;
+
+ protected:
+  base::win::ScopedCOMInitializer com_init_;
+};
+
+TEST_F(AudioSessionCreationObserverWinTest, NotifiesCallback) {
+  base::MockRepeatingClosure session_created_callback;
+  auto observer = Microsoft::WRL::Make<AudioSessionCreationObserverWin>(
+      session_created_callback.Get());
+
+  EXPECT_CALL(session_created_callback, Run()).Times(1);
+  observer->OnSessionCreated(nullptr);
+}
+
+}  // namespace media
Loading diff…

Original Bug Report

reported by [email protected]

Potential Browser-process UAF in AudioSessionCreationObserverWin due to stubbed COM refcounting

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.

Overview: A potential use-after-free vulnerability exists in the browser process due to incorrect COM lifetime management in AudioSessionCreationObserverWin. The class stubs its AddRef() and Release() methods, meaning the Windows audio subsystem holds a dangling pointer after the object is destroyed by its owning unique_ptr. Concurrent audio session notifications from the OS multimedia thread can then execute on this freed memory.

Affected files:

  • media/audio/win/audio_session_creation_observer_win.cc
  • media/audio/win/audio_ducker_win.cc
  • media/audio/win/audio_ducker_win.h
  • chrome/browser/media/audio_ducker.cc
  • chrome/browser/glic/host/glic_page_handler.cc

Estimated timestamp from git blame: 2025-05-29

Summary

A potential use-after-free (UAF) vulnerability exists in the Chrome browser process on Windows due to incorrect lifetime management of the AudioSessionCreationObserverWin COM object. The class violates COM reference counting contracts by stubbing its AddRef() and Release() methods, allowing the object to be destroyed while the Windows audio subsystem still holds a pointer to it. This can lead to a race condition where the OS attempts to dispatch an event on a background thread to an object that has already been freed on the UI thread.

Technical Details

AudioSessionCreationObserverWin (in media/audio/win/audio_session_creation_observer_win.cc) implements the IAudioSessionNotification interface. However, it overrides the IUnknown lifetime management methods to simply return a constant:

ULONG AudioSessionCreationObserverWin::AddRef() {
  return 1;
}

ULONG AudioSessionCreationObserverWin::Release() {
  return 1;
}

Because of this, the Windows OS cannot properly pin the object’s lifetime.

The observer is instantiated by AudioDuckerWin (media/audio/win/audio_ducker_win.cc) and stored in a std::unique_ptr<AudioSessionCreationObserverWin> session_creation_observer_. It is registered with the OS via IAudioSessionManager2::RegisterSessionNotification.

The vulnerability is triggered during teardown. When the Glic guest page is closed or navigates away:

  1. The content::Page is destroyed, which destroys the AudioDucker (PageUserData).
  2. ~AudioDucker destroys the AudioDuckerWin instance on the UI thread.
  3. AudioDuckerWin’s destructor calls UnregisterSessionNotification(session_creation_observer_.get()).
  4. Immediately after, the unique_ptr is destroyed, freeing the AudioSessionCreationObserverWin memory.

Crucially, the Windows API UnregisterSessionNotification is synchronous but does not block or cancel callbacks that are currently being dispatched or queued on the background OS multimedia thread. If an audio session is created concurrently (e.g., by another application or tab), the OS multimedia thread may invoke IAudioSessionNotification::OnSessionCreated using the pointer it holds.

Because the OS’s internal AddRef calls were ignored, it will dereference the freed pointer to perform a virtual method call. If an attacker reclaims this freed memory (e.g., via heap spraying), they can hijack the vtable.

Note: This vulnerability is not protected by MiraclePtr (BRP) because the dangling pointer is held internally by a Windows system DLL (audioses.dll), not by a Chromium raw_ptr.

Potential Reproduction Steps

Note: These are suggested steps derived from static analysis; our tooling cannot execute code to provide a working proof of concept.

  1. An attacker compromises a renderer process (e.g., the Glic guest page via a separate vulnerability or by loading malicious code in the guest context).
  2. The attacker calls the Glic JS API glic.host.setAudioDucking(true). This routes via Mojo to the browser process and instantiates the AudioSessionCreationObserverWin, registering it with the OS.
  3. The attacker triggers the creation of new audio sessions system-wide (e.g., repeatedly playing an <audio> element in another compromised frame).
  4. Concurrently, the attacker quickly unloads the Glic guest page (e.g., via location.reload() or closing the panel) to trigger the AudioDucker teardown sequence on the UI thread.
  5. By spraying the browser process heap, the attacker reclaims the freed observer object.
  6. When the race condition is won, the OS multimedia thread executes an indirect call using the attacker-controlled vtable, leading to arbitrary code execution in the browser process.

Suggested Fix

AudioSessionCreationObserverWin should properly implement COM reference counting. It can be implemented using Microsoft::WRL::RuntimeClass with RuntimeClassFlags<ClassicCom> or by making it inherit from base::RefCountedThreadSafe.

Instead of managing the observer strictly via a std::unique_ptr in AudioDuckerWin, AudioDuckerWin should hold a COM reference (Microsoft::WRL::ComPtr). During teardown, AudioDuckerWin should call UnregisterSessionNotification and release its reference. The object itself should only be destroyed when its reference count drops to zero, which ensures it stays alive if the Windows OS is concurrently dispatching a callback.

Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b


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.

View on issue tracker