High chrome UAF 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebAudio
DescriptionUse after free in WebAudio
ComponentWebAudio
Sub-componentWebAudio
Bug ClassUAF
Tracker565612897
Fix commite8cfa0184ae5 (chromium/src) +58/-4
CISA KEVNot listed
CreditedTeam Allied (Hyeongeun Ji, h4nk3r1n, h4vrut4, HunSec, nag0x)
Disclosed2026-10-06

Changed Functions

FunctionChangeNotes
TEST_F
third_party/blink/renderer/modules/webaudio/audio_context_test.cc
modified

Files Changed

  • third_party/blink/renderer/modules/webaudio/audio_node.cc
  • third_party/blink/renderer/modules/webaudio/audio_context_test.cc
From e8cfa0184ae597598dff05b7cfb1c590ef492d48 Mon Sep 17 00:00:00 2001
From: Hongchan Choi <[email protected]>
Date: Wed, 30 Sep 2026 12:16:21 -0700
Subject: [PATCH] [WebAudio] Defer AudioHandler disposal for all non-closed states

AudioNode::Dispose() previously checked only for kSuspended and
kInterrupted in addition to IsPullingAudioGraph() when adding the
handler to DeferredTaskHandler::AddRenderingOrphanHandler(). If
rendering was paused while ContextState() remained kRunning, disposed
handlers were not deferred through DeferredTaskHandler.

Changes:
- Update AudioNode::Dispose() to defer handler cleanup whenever
  ContextState() is not kClosed (or when the graph is being pulled).
- Add AudioContextTest.DisposeOrphansHandlerWhenRunningWithoutPulling
  to cover node disposal in this state.

Bug: 565612897
Test: AudioContextTest.DisposeOrphansHandlerWhenRunningWithoutPulling
Change-Id: I808b5776061d3c5eecad0651722b23b301625c8f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8462555
Commit-Queue: Hongchan Choi <[email protected]>
Reviewed-by: Mahesh Kannan <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1708385}
---
diff --git a/third_party/blink/renderer/modules/webaudio/audio_node.cc b/third_party/blink/renderer/modules/webaudio/audio_node.cc
index 90ce5f78..0b1f792 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_node.cc
@@ -77,12 +77,12 @@
 
   // Add the handler to the orphan list.  This keeps the handler alive until it
   // can be deleted at a safe point (in pre/post handler task).  If the graph is
-  // being processed, the handler must be added.  If the context is suspended,
-  // the handler still needs to be added in case the context is resumed.
+  // being processed, the handler must be added.  If the context is not closed,
+  // the handler still needs to be added in case rendering is paused or the
+  // context is later resumed.
   DCHECK(context());
   if (context()->IsPullingAudioGraph() ||
-      context()->ContextState() == V8AudioContextState::Enum::kSuspended ||
-      context()->ContextState() == V8AudioContextState::Enum::kInterrupted) {
+      context()->ContextState() != V8AudioContextState::Enum::kClosed) {
     context()->GetDeferredTaskHandler().AddRenderingOrphanHandler(
         std::move(handler_));
   }
diff --git a/third_party/blink/renderer/modules/webaudio/audio_context_test.cc b/third_party/blink/renderer/modules/webaudio/audio_context_test.cc
index 29ae192b..c09deed 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_context_test.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_context_test.cc
@@ -518,6 +518,60 @@
   EXPECT_FALSE(handler->HasOneRef());
 }
 
+TEST_F(AudioContextTest, DisposeOrphansHandlerWhenRunningWithoutPulling) {
+  AudioContextOptions* options = AudioContextOptions::Create();
+  AudioContext* context = AudioContext::Create(
+      GetFrame().DomWindow(), options, ASSERT_NO_EXCEPTION);
+  ExpectContextBecomesRunningAsync(context);
+  ExpectContextRunning(context);
+
+  RealtimeAudioDestinationHandler* dest_handler =
+      &context->destinationNode()->GetAudioDestinationHandler();
+  FakeAudioThread audio_thread(ThreadType::kRealtimeAudioWorkletThread);
+  {
+    DelayNode* node = context->createDelay(ASSERT_NO_EXCEPTION);
+    node->connect(context->destinationNode(), 0, 0, ASSERT_NO_EXCEPTION);
+
+    // Run a render cycle so the summing junction updates its rendering state.
+    audio_thread.RunOnAudioThreadWithContext(
+        context,
+        CrossThreadBindOnce(
+            [](RealtimeAudioDestinationHandler* handler) {
+              const AudioIOPosition pos{0, 0, 0};
+              const AudioCallbackMetric metric;
+              scoped_refptr<AudioBus> bus =
+                  AudioBus::Create(2, 128, /*create_memory=*/true);
+              handler->Render(bus.get(), 128, pos, metric, base::TimeDelta(),
+                              {});
+            },
+            CrossThreadUnretained(dest_handler)));
+  }
+
+  // Stop pulling the graph while the context state remains kRunning.
+  dest_handler->StopRendering();
+  ASSERT_EQ(context->ContextState(), V8AudioContextState::Enum::kRunning);
+  ASSERT_FALSE(context->IsPullingAudioGraph());
+
+  WebHeap::CollectAllGarbageForTesting();
+
+  // Resume rendering and verify the destination renders cleanly while the
+  // graph lock is held on the main thread.
+  dest_handler->StartRendering();
+  {
+    DeferredTaskHandler::GraphAutoLocker locker(
+        context->GetDeferredTaskHandler());
+    audio_thread.RunOnAudioThread(CrossThreadBindOnce(
+        [](RealtimeAudioDestinationHandler* handler) {
+          const AudioIOPosition pos{0, 0, 0};
+          const AudioCallbackMetric metric;
+          scoped_refptr<AudioBus> bus =
+              AudioBus::Create(2, 128, /*create_memory=*/true);
+          handler->Render(bus.get(), 128, pos, metric, base::TimeDelta(), {});
+        },
+        CrossThreadUnretained(dest_handler)));
+  }
+}
+
 TEST_F(AudioContextTest, AudioContextOptions_WebAudioLatencyHint) {
   AudioContextOptions* interactive_options = AudioContextOptions::Create();
   interactive_options->setLatencyHint(
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/renderer/modules/webaudio/audio_context_test.cc b/third_party/blink/renderer/modules/webaudio/audio_context_test.cc
index 29ae192b..c09deed 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_context_test.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_context_test.cc
@@ -518,6 +518,60 @@
   EXPECT_FALSE(handler->HasOneRef());
 }
 
+TEST_F(AudioContextTest, DisposeOrphansHandlerWhenRunningWithoutPulling) {
+  AudioContextOptions* options = AudioContextOptions::Create();
+  AudioContext* context = AudioContext::Create(
+      GetFrame().DomWindow(), options, ASSERT_NO_EXCEPTION);
+  ExpectContextBecomesRunningAsync(context);
+  ExpectContextRunning(context);
+
+  RealtimeAudioDestinationHandler* dest_handler =
+      &context->destinationNode()->GetAudioDestinationHandler();
+  FakeAudioThread audio_thread(ThreadType::kRealtimeAudioWorkletThread);
+  {
+    DelayNode* node = context->createDelay(ASSERT_NO_EXCEPTION);
+    node->connect(context->destinationNode(), 0, 0, ASSERT_NO_EXCEPTION);
+
+    // Run a render cycle so the summing junction updates its rendering state.
+    audio_thread.RunOnAudioThreadWithContext(
+        context,
+        CrossThreadBindOnce(
+            [](RealtimeAudioDestinationHandler* handler) {
+              const AudioIOPosition pos{0, 0, 0};
+              const AudioCallbackMetric metric;
+              scoped_refptr<AudioBus> bus =
+                  AudioBus::Create(2, 128, /*create_memory=*/true);
+              handler->Render(bus.get(), 128, pos, metric, base::TimeDelta(),
+                              {});
+            },
+            CrossThreadUnretained(dest_handler)));
+  }
+
+  // Stop pulling the graph while the context state remains kRunning.
+  dest_handler->StopRendering();
+  ASSERT_EQ(context->ContextState(), V8AudioContextState::Enum::kRunning);
+  ASSERT_FALSE(context->IsPullingAudioGraph());
+
+  WebHeap::CollectAllGarbageForTesting();
+
+  // Resume rendering and verify the destination renders cleanly while the
+  // graph lock is held on the main thread.
+  dest_handler->StartRendering();
+  {
+    DeferredTaskHandler::GraphAutoLocker locker(
+        context->GetDeferredTaskHandler());
+    audio_thread.RunOnAudioThread(CrossThreadBindOnce(
+        [](RealtimeAudioDestinationHandler* handler) {
+          const AudioIOPosition pos{0, 0, 0};
+          const AudioCallbackMetric metric;
+          scoped_refptr<AudioBus> bus =
+              AudioBus::Create(2, 128, /*create_memory=*/true);
+          handler->Render(bus.get(), 128, pos, metric, base::TimeDelta(), {});
+        },
+        CrossThreadUnretained(dest_handler)));
+  }
+}
+
 TEST_F(AudioContextTest, AudioContextOptions_WebAudioLatencyHint) {
   AudioContextOptions* interactive_options = AudioContextOptions::Create();
   interactive_options->setLatencyHint(
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.