CVE-2026-8524
Overview
Background
- Denormal (subnormal) floats
- Very-small IEEE-754 values whose handling can be toggled to flush-to-zero; WebAudio disables them on audio threads for performance, which changes CPU/FPU state and JIT-generated code assumptions.
- `DenormalModifier::DisableDenormals`
- The platform helper (in
denormal_disabler.h) that sets the thread’s flush-to-zero/denormals-off flag. - `WorkerBackingThread`
- The Blink object wrapping a non-main worker thread and owning its
v8::Isolate, initialized inWorkerBackingThread::InitializeOnBackingThread. - `AudioWorkletGlobalScope`
- The V8 global scope for an AudioWorklet, previously the place where
DisableDenormalswas called, i.e. after the isolate already existed.
Root Cause Analysis
Denormals were being disabled inside the AudioWorkletGlobalScope constructor, which runs after the V8 isolate for the AudioWorklet thread has already been created and initialized. That left a window in which the isolate’s internal state and any generated code were set up under a different denormal-flag state than the global scope later ran with, producing an inconsistency between the isolate and the scope.
The patch moves the decision to thread-creation time: WorkerBackingThread records is_denormal_disabled_thread_ from IsDenormalDisabledThreadType(params.thread_type) for the three audio-worklet thread types, and InitializeOnBackingThread now calls DenormalModifier::DisableDenormals() before V8PerIsolateData::Initialize. This restores the invariant that the denormal flag is consistent across isolate creation and the global scope, and the old call site in AudioWorkletGlobalScope is removed. The advisory classifies the resulting defect as an out-of-bounds write in WebAudio; the diff itself only shows the flag-ordering correction, so the precise OOB-write sink is not visible in the hunks shown.
V8PerIsolateData::Initialize so both agree.Attack Path
- Create an AudioWorklet
A page spins up an AudioWorklet, causing Blink to create a
WorkerBackingThreadof an audio-workletThreadTypeand initialize a V8 isolate on it. - Inconsistent FPU/flag state at isolate init
Pre-patch, the isolate and its generated code were initialized while denormals were still enabled, then denormals were disabled later in
AudioWorkletGlobalScope, leaving the isolate’s assumptions out of sync with the scope’s actual flag state. - Trigger the inconsistency in generated code Audio-processing JavaScript/generated code runs under the mismatched denormal state. (inferred: how the mismatch is driven to a specific miscomputation is not shown in the diff)
- Out-of-bounds write The advisory reports this leads to an out-of-bounds write in WebAudio; the exact sink, offset, and object are not present in the shown diff and are therefore (inferred).
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifthird_party/blink/renderer/core/workers/worker_backing_thread.cc |
modified |
Files Changed
third_party/blink/renderer/core/workers/worker_backing_thread.ccthird_party/blink/renderer/core/workers/worker_backing_thread.hthird_party/blink/renderer/modules/webaudio/audio_worklet_global_scope.cc
Audit Directions
- Order of state changes vs. isolate/JIT setupAudit any per-thread CPU/FPU/mode flag (denormals, rounding mode, SIMD flags) set after
V8PerIsolateData::Initialize; such state must be fixed before the isolate and its generated code are created. - Thread-type-specific configurationReview other
ThreadType-conditional setup that happens in a global-scope constructor rather than atWorkerBackingThreadcreation, since late configuration can diverge from isolate assumptions. - WebAudio numeric-state assumptionsExamine WebAudio buffer-size / index computations that depend on floating-point results, where a flushed-vs-unflushed denormal difference could perturb a length or offset used for memory access.
Patch
From 5c37ebf855147997e7cbc84d8ef67ab142cc78b4 Mon Sep 17 00:00:00 2001
From: Michael Wilson <mjwilson@chromium.org>
Date: Wed, 15 Apr 2026 03:37:44 -0700
Subject: [PATCH] Disable denormals before isolate creation for AudioWorklet threads
This is to ensure consistency in the denormal flag state between the
isolate and the global scope.
Bug: 499565267
Change-Id: Iec7735bd6bc543da2ad08aea2f7bc716265dc54d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7744866
Reviewed-by: Hiroshige Hayashizaki <hiroshige@chromium.org>
Commit-Queue: Michael Wilson <mjwilson@chromium.org>
Reviewed-by: Hongchan Choi <hongchan@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1615043}
---
diff --git a/third_party/blink/renderer/core/workers/worker_backing_thread.cc b/third_party/blink/renderer/core/workers/worker_backing_thread.cc
index 38d221a6..5997bed3 100644
--- a/third_party/blink/renderer/core/workers/worker_backing_thread.cc
+++ b/third_party/blink/renderer/core/workers/worker_backing_thread.cc
@@ -18,6 +18,7 @@
#include "third_party/blink/renderer/bindings/core/v8/v8_initializer.h"
#include "third_party/blink/renderer/core/inspector/worker_thread_debugger.h"
#include "third_party/blink/renderer/core/workers/worker_backing_thread_startup_data.h"
+#include "third_party/blink/renderer/platform/audio/denormal_disabler.h"
#include "third_party/blink/renderer/platform/heap/thread_state.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/scheduler/public/main_thread.h"
@@ -91,6 +92,14 @@
ForegroundedIsolates().erase(isolate);
}
+bool IsDenormalDisabledThreadType(ThreadType type) {
+ // Disable denormals on WebAudio threads for performance reasons. See:
+ // https://esdiscuss.org/topic/float-denormal-issue-in-javascript-processor-node-in-web-audio-api
+ return type == ThreadType::kOfflineAudioWorkletThread ||
+ type == ThreadType::kRealtimeAudioWorkletThread ||
+ type == ThreadType::kSemiRealtimeAudioWorkletThread;
+}
+
} // namespace
// Wrapper functions defined in third_party/blink/public/web/blink.h
@@ -119,7 +128,9 @@
WorkerBackingThread::WorkerBackingThread(const ThreadCreationParams& params)
: backing_thread_(blink::NonMainThread::CreateThread(
- ThreadCreationParams(params).SetSupportsGC(true))) {}
+ ThreadCreationParams(params).SetSupportsGC(true))),
+ is_denormal_disabled_thread_(
+ IsDenormalDisabledThreadType(params.thread_type)) {}
WorkerBackingThread::~WorkerBackingThread() = default;
@@ -127,6 +138,12 @@
const WorkerBackingThreadStartupData& startup_data) {
DCHECK(backing_thread_->IsCurrentThread());
+ // Denormals must be disabled before the V8 isolate is initialized so that the
+ // isolate's internal state and generated code respect the flag.
+ if (is_denormal_disabled_thread_) {
+ DenormalModifier::DisableDenormals();
+ }
+
DCHECK(!isolate_);
ThreadScheduler* scheduler = BackingThread().Scheduler();
isolate_ = V8PerIsolateData::Initialize(
diff --git a/third_party/blink/renderer/core/workers/worker_backing_thread.h b/third_party/blink/renderer/core/workers/worker_backing_thread.h
index 445aad71..4c5f3cc7 100644
--- a/third_party/blink/renderer/core/workers/worker_backing_thread.h
+++ b/third_party/blink/renderer/core/workers/worker_backing_thread.h
@@ -56,6 +56,7 @@
private:
std::unique_ptr<blink::NonMainThread> backing_thread_;
v8::Isolate* isolate_ = nullptr;
+ const bool is_denormal_disabled_thread_;
};
} // namespace blink
diff --git a/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope.cc b/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope.cc
index b258751..e3c9d55 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope.cc
@@ -23,7 +23,6 @@
#include "third_party/blink/renderer/modules/webaudio/audio_worklet_processor.h"
#include "third_party/blink/renderer/modules/webaudio/audio_worklet_processor_definition.h"
#include "third_party/blink/renderer/modules/webaudio/cross_thread_audio_worklet_processor_info.h"
-#include "third_party/blink/renderer/platform/audio/denormal_disabler.h"
#include "third_party/blink/renderer/platform/bindings/callback_method_retriever.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/wtf/text/strcat.h"
@@ -36,9 +35,6 @@
: WorkletGlobalScope(std::move(creation_params),
thread->GetWorkerReportingProxy(),
thread) {
- // Disable denormals for performance.
- DenormalModifier::DisableDenormals();
-
// Audio is prone to jank introduced by e.g. the garbage collector. Workers
// are generally put in a background mode (as they are non-visible). Audio is
// an exception here, requiring low-latency behavior similar to any visible