Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactRace in WebAudio
DescriptionRace in WebAudio
ComponentWebAudio
Bug ClassRace
Tracker513750691
Fix commit1b860cdc44d0 (chromium/src) +12/-3
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-27

Files Changed

  • third_party/blink/renderer/modules/webaudio/audio_worklet_processor.cc
  • third_party/blink/renderer/modules/webaudio/offline_audio_destination_handler.cc
  • third_party/blink/renderer/modules/webaudio/offline_audio_destination_handler.h
From 1b860cdc44d0160f4c3eb0d3f4ebcde981803877 Mon Sep 17 00:00:00 2001
From: Hongchan Choi <[email protected]>
Date: Wed, 20 May 2026 07:05:48 -0700
Subject: [PATCH] [webaudio] Fix race condition in OfflineAudioDestinationHandler

OfflineAudioDestinationHandler fails to update the render task runner
when an AudioWorklet is added during context suspension, causing
rendering to execute on the incorrect thread (the OfflineAudioRender
thread instead of the AudioWorkletThread). This leads to concurrent
V8 isolate access and potential heap corruption.

This CL:
1. Declares and implements PrepareTaskRunnerForWorklet in
OfflineAudioDestinationHandler to update the task runner.
2. Hardens thread checks in AudioWorkletProcessor by upgrading
DCHECK to CHECK.

Bug: 513750691
Test: blink_unittests --gtest_filter="*OfflineAudioContext*:*AudioWorkletGlobalScope*"
Change-Id: I0c1e081325e80c942e574b8c0dd6cce69101615b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7856956
Commit-Queue: Hongchan Choi <[email protected]>
Reviewed-by: Michael Wilson <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1633568}
---

diff --git a/third_party/blink/renderer/modules/webaudio/audio_worklet_processor.cc b/third_party/blink/renderer/modules/webaudio/audio_worklet_processor.cc
index 9d4533f..4b35cc4 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_worklet_processor.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_worklet_processor.cc
@@ -426,7 +426,7 @@
     ExceptionState& exception_state) {
   AudioWorkletGlobalScope* global_scope = To<AudioWorkletGlobalScope>(context);
   DCHECK(global_scope);
-  DCHECK(global_scope->IsContextThread());
+  CHECK(global_scope->IsContextThread());
 
   // Get the stored initialization parameter from the global scope.
   std::unique_ptr<ProcessorCreationParams> params =
@@ -466,7 +466,7 @@
   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("audio-worklet"),
                "AudioWorkletProcessor::Process");
 
-  DCHECK(global_scope_->IsContextThread());
+  CHECK(global_scope_->IsContextThread());
   DCHECK(!hasErrorOccurred());
 
   ScriptState* script_state =
diff --git a/third_party/blink/renderer/modules/webaudio/offline_audio_destination_handler.cc b/third_party/blink/renderer/modules/webaudio/offline_audio_destination_handler.cc
index 8331e44..13bbd23 100644
--- a/third_party/blink/renderer/modules/webaudio/offline_audio_destination_handler.cc
+++ b/third_party/blink/renderer/modules/webaudio/offline_audio_destination_handler.cc
@@ -371,6 +371,15 @@
   DCHECK(render_thread_task_runner_);
 }
 
+void OfflineAudioDestinationHandler::PrepareTaskRunnerForWorklet() {
+  CHECK(IsMainThread());
+
+  // Updates the render task runner to the AudioWorklet backing thread when a
+  // worklet becomes ready while the context is suspended, ensuring that
+  // rendering resumes on the correct thread.
+  PrepareTaskRunnerForRendering();
+}
+
 void OfflineAudioDestinationHandler::RestartRendering() {
   DCHECK(IsMainThread());
 
diff --git a/third_party/blink/renderer/modules/webaudio/offline_audio_destination_handler.h b/third_party/blink/renderer/modules/webaudio/offline_audio_destination_handler.h
index 461facf..b3c9dd5ba 100644
--- a/third_party/blink/renderer/modules/webaudio/offline_audio_destination_handler.h
+++ b/third_party/blink/renderer/modules/webaudio/offline_audio_destination_handler.h
@@ -47,7 +47,7 @@
   void Pause() override;
   void Resume() override;
   uint32_t MaxChannelCount() const override;
-  void PrepareTaskRunnerForWorklet() override {}
+  void PrepareTaskRunnerForWorklet() override;
 
   void RestartRendering() override;
 
Loading diff…

Original Bug Report

reported by [email protected]

Concurrent V8 Isolate Access in OfflineAudioDestinationHandler

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 potential race condition in OfflineAudioDestinationHandler fails to update the render task runner when an AudioWorklet is added during context suspension. This can cause worklet processing to execute on the incorrect thread, leading to concurrent V8 isolate access and heap corruption. This flaw could potentially be exploited for remote code execution in the renderer process.

Affected files:

  • third_party/blink/renderer/modules/webaudio/offline_audio_destination_handler.h
  • third_party/blink/renderer/modules/webaudio/offline_audio_destination_handler.cc
  • third_party/blink/renderer/modules/webaudio/base_audio_context.cc
  • third_party/blink/renderer/modules/webaudio/offline_audio_context.cc
  • third_party/blink/renderer/modules/webaudio/audio_worklet_handler.cc
  • third_party/blink/renderer/modules/webaudio/audio_worklet_processor.cc

Estimated timestamp from git blame: 2023-01-25

Summary

A potential vulnerability exists in the OfflineAudioDestinationHandler within Blink’s Web Audio implementation. When an AudioWorklet module is loaded into an OfflineAudioContext while it is in a suspended state, the destination handler fails to update its internal rendering task runner. Consequently, upon resuming rendering, the AudioWorkletProcessor::Process method may execute on the incorrect thread (the OfflineAudioRender thread instead of the designated AudioWorkletThread), leading to concurrent access to a v8::Isolate from multiple threads.

Potential Root Cause

The issue originates in third_party/blink/renderer/modules/webaudio/offline_audio_destination_handler.h, where the PrepareTaskRunnerForWorklet() method is overridden with an empty implementation:

// line 50
void PrepareTaskRunnerForWorklet() override {}

When an AudioWorklet becomes ready while the context is suspended, BaseAudioContext::NotifyWorkletIsReady() invokes this method. In the real-time destination handler, this method updates the render task runner. However, in the offline handler, this method does nothing, leaving render_thread_task_runner_ pointing to the initial OfflineAudioRender thread.

When resume() is subsequently called on the context, OfflineAudioContext::resumeContext() calls StartRendering(), which posts the rendering task (DoOfflineRendering) to the stale render_thread_task_runner_ without re-evaluating the active task runner.

Vulnerability Mechanism

As rendering proceeds on the incorrect thread, AudioWorkletHandler::Process() eventually calls AudioWorkletProcessor::Process(). This method enters the V8 isolate using ScriptState::Scope, which constructs a v8::HandleScope and enters the V8 context.

Since Blink does not utilize v8::Locker for AudioWorklets, V8 assumes the isolate is only accessed by its owner thread (the AudioWorkletThread). If the AudioWorkletThread is simultaneously active (e.g., handling MessagePort messages), the offline render thread will concurrently mutate isolate-specific data, such as HandleScope data and heap allocation pointers, leading to memory corruption.

Potential Impact

Successful exploitation could result in severe memory corruption within the V8 heap, potentially enabling an attacker to achieve arbitrary code execution within the sandboxed renderer process.

Suggested Steps to Reproduce (Potential)

  1. Create an OfflineAudioContext and call suspend(0) followed by startRendering(). This initializes the offline render thread and suspends rendering at the first frame.
  2. Use audioWorklet.addModule() to load a worklet script.
  3. Create an AudioWorkletNode and connect it to the destination.
  4. Call resume() on the context while simultaneously triggering activity on the worklet thread (e.g., via postMessage to the node’s port).
  5. Observe if the worklet’s processing logic executes concurrently with the worklet thread, potentially causing a crash or V8 corruption.

Suggested Fix

Implement OfflineAudioDestinationHandler::PrepareTaskRunnerForWorklet() to call PrepareTaskRunnerForRendering(), ensuring the render_thread_task_runner_ is correctly updated when a worklet becomes ready during suspension.

Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a


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