CVE-2026-16421
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
TestProcessorthird_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc |
modified | |
constructorthird_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc |
modified | |
processthird_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc |
modified |
Files Changed
third_party/blink/common/features.ccthird_party/blink/public/common/features.hthird_party/blink/renderer/core/workers/worker_backing_thread.ccthird_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc
Patch
From 1a17b7b8f3ab8f274c24279ef5ac0de15b9800ef Mon Sep 17 00:00:00 2001
From: Hongchan Choi <hongchan@google.com>
Date: Thu, 16 Jul 2026 10:06:45 -0700
Subject: [PATCH] [M150] [WebAudio] Disable FTZ/DAZ during JavaScript AudioWorklet execution
Original change's description:
> [WebAudio] Disable FTZ/DAZ during JavaScript AudioWorklet execution
>
> This CL disables the Float-to-Zero (FTZ) and Denormals-are-Zero (DAZ)
> FPU features when running JavaScript code within AudioWorklets.
>
> Historically, worker backing threads for AudioWorklets enabled FTZ/DAZ
> to avoid microcode performance overheads during real-time processing.
> However, V8 compiler optimization assumes strict IEEE-754 floating
> point semantics. Running JIT-optimized code under non-standard FPU
> behavior causes compiler and runtime execution divergences, which
> leads to incorrect type assertions and memory safety issues.
>
> To resolve this alignment issue, this change:
> 1. Prevents AudioWorklet backing threads from turning on FTZ/DAZ at
> startup, ensuring that the V8 Isolate initializes in standard
> IEEE-754 mode.
> 2. Instantiates DenormalEnabler inside AudioWorkletProcessor::Process()
> to temporarily disable FTZ/DAZ during JS execution. This guarantees
> standard floating point behavior for user scripts while leaving
> native WebAudio DSP nodes to run with FTZ/DAZ enabled.
>
> Bug: 528276487, 527930356
> Test: blink_unittests --gtest_filter="*DenormalProcessing*"
>
> TAG=agy
> CONV=9d8e1d6c-f19f-4ec3-adae-3a4328b455ca
>
> Change-Id: I7f012fbb7e50fe4bd5369968a94d6531bfb12df1
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8021606
> Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
> Commit-Queue: Hongchan Choi <hongchan@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1659058}
(cherry picked from commit d3b91ccae2842f11bad90ce73e600e1dd771b8a7)
Bug: 535458979,528276487,527930356
Change-Id: I7f012fbb7e50fe4bd5369968a94d6531bfb12df1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8108657
Commit-Queue: rubber-stamper@appspot.gserviceaccount.com <rubber-stamper@appspot.gserviceaccount.com>
Auto-Submit: chrome-cherry-picker@chops-service-accounts.iam.gserviceaccount.com <chrome-cherry-picker@chops-service-accounts.iam.gserviceaccount.com>
Bot-Commit: rubber-stamper@appspot.gserviceaccount.com <rubber-stamper@appspot.gserviceaccount.com>
Cr-Commit-Position: refs/branch-heads/7871@{#3539}
Cr-Branched-From: f542126b8c1b3e80104b26bb05ec830bd1206f29-refs/heads/main@{#1639810}
---
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index d210598df..1c03e395 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -51,6 +51,12 @@
"ad-auction-signals-max-size-bytes",
10000);
+// Controls whether JavaScript execution inside AudioWorkletProcessor::Process()
+// runs under strict IEEE-754 floating-point semantics (disabling FTZ/DAZ).
+// Enabled by default as a remote kill-switch.
+BASE_FEATURE(kAudioWorkletJSDenormalEnabler,
+ base::FEATURE_ENABLED_BY_DEFAULT);
+
#if BUILDFLAG(IS_ANDROID)
// If enabled, then use desktop page webprefs for Android devices that have
// large displays, specifically tablets and desktops.
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index 7083f57..1818562 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -48,6 +48,8 @@
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE_PARAM(int,
kAdAuctionSignalsMaxSizeBytes);
+BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kAudioWorkletJSDenormalEnabler);
+
// Avoids copying ResourceRequest::TrustedParams when possible.
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kAvoidTrustedParamsCopies);
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 5997bed3..345cc9c 100644
--- a/third_party/blink/renderer/core/workers/worker_backing_thread.cc
+++ b/third_party/blink/renderer/core/workers/worker_backing_thread.cc
@@ -93,11 +93,13 @@
}
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;
+ if (type == ThreadType::kOfflineAudioWorkletThread ||
+ type == ThreadType::kRealtimeAudioWorkletThread ||
+ type == ThreadType::kSemiRealtimeAudioWorkletThread) {
+ return !base::FeatureList::IsEnabled(
+ blink::features::kAudioWorkletJSDenormalEnabler);
+ }
+ return false;
}
} // namespace
diff --git a/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc b/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc
index 8506f32..99b5306f 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc
@@ -8,8 +8,10 @@
#include "base/compiler_specific.h"
#include "base/synchronization/waitable_event.h"
+#include "base/test/scoped_feature_list.h"
#include "media/base/audio_bus.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/v8_cache_options.mojom-blink.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/public/platform/web_url_request.h"
@@ -42,6 +44,7 @@
#include "third_party/blink/renderer/modules/webaudio/audio_worklet_processor_definition.h"
#include "third_party/blink/renderer/modules/webaudio/offline_audio_worklet_thread.h"
#include "third_party/blink/renderer/platform/audio/audio_bus.h"
+#include "third_party/blink/renderer/platform/audio/denormal_disabler.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/bindings/source_location.h"
#include "third_party/blink/renderer/platform/bindings/v8_object_constructor.h"
@@ -125,6 +128,17 @@
waitable_event.Wait();
}
+ void RunDenormalProcessTest(WorkerThread* thread, bool expect_denormals) {
+ base::WaitableEvent waitable_event;
+ PostCrossThreadTask(
+ *thread->GetTaskRunner(TaskType::kInternalTest), FROM_HERE,
+ CrossThreadBindOnce(
+ &AudioWorkletGlobalScopeTest::RunDenormalProcessTestOnWorkletThread,
+ CrossThreadUnretained(this), CrossThreadUnretained(thread),
+ expect_denormals, CrossThreadUnretained(&waitable_event)));
+ waitable_event.Wait();
+ }
+
void RunParsingTest(WorkerThread* thread) {
base::WaitableEvent waitable_event;
PostCrossThreadTask(
@@ -346,6 +360,79 @@
wait_event->Signal();
}
+ void RunDenormalProcessTestOnWorkletThread(WorkerThread* thread,
+ bool expect_denormals,
+ base::WaitableEvent* wait_event) {
+ EXPECT_TRUE(thread->IsCurrentThread());
+
+ auto* global_scope = To<AudioWorkletGlobalScope>(thread->GlobalScope());
+ ScriptState* script_state =
+ global_scope->ScriptController()->GetScriptState();
+
+ ScriptState::Scope scope(script_state);
+ v8::Isolate* isolate = script_state->GetIsolate();
+ EXPECT_TRUE(isolate);
+ V8DoNotRunMicrotasksScope microtasks_scope(script_state);
+
+ String source_code =
+ R"JS(
+ class TestProcessor extends AudioWorkletProcessor {
+ constructor () { super(); }
+ process (inputs, outputs) {
+ let f64 = new Float64Array(1);
+ // The minimum positive normal 64-bit float is
+ // 2.225e-308. Therefore, 1.0e-309 is a denormal
+ // double. If FTZ/DAZ is enabled, it is treated
+ // as zero or flushed to zero, making f64[0]
+ // equal to 0.0. If disabled, the division
+ // computes 1.0e-310 (a valid denormal double
+ // > 0.0).
+ let denorm = 1.0e-309;
+ f64[0] = denorm / 10.0;
+ let outputChannel = outputs[0][0];
+ outputChannel[0] = f64[0] > 0.0 ? 1.0 : 0.0;
+ }
+ }
+ registerProcessor('testProcessor', TestProcessor);
+ )JS";
+ ExpectEvaluateScriptModule(global_scope, source_code, true);
+
+ auto* channel = MakeGarbageCollected<MessageChannel>(thread->GlobalScope());
+ MessagePortChannel dummy_port_channel = channel->port2()->Disentangle();
+ AudioWorkletProcessor* processor =
+ global_scope->CreateProcessor("testProcessor",
+ dummy_port_channel,
+ SerializedScriptValue::NullValue());
+ EXPECT_TRUE(processor);
+
+ Vector<scoped_refptr<AudioBus>> input_buses;
+ Vector<scoped_refptr<AudioBus>> output_buses;
+ HashMap<String, std::unique_ptr<AudioFloatArray>> param_data_map;
+ scoped_refptr<AudioBus> input_bus =
+ AudioBus::Create(1, kRenderQuantumFrames);
+ scoped_refptr<AudioBus> output_bus =
Regression Test / PoC
diff --git a/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc b/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc
index 8506f32..99b5306f 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_worklet_global_scope_test.cc
@@ -8,8 +8,10 @@
#include "base/compiler_specific.h"
#include "base/synchronization/waitable_event.h"
+#include "base/test/scoped_feature_list.h"
#include "media/base/audio_bus.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/v8_cache_options.mojom-blink.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/public/platform/web_url_request.h"
@@ -42,6 +44,7 @@
#include "third_party/blink/renderer/modules/webaudio/audio_worklet_processor_definition.h"
#include "third_party/blink/renderer/modules/webaudio/offline_audio_worklet_thread.h"
#include "third_party/blink/renderer/platform/audio/audio_bus.h"
+#include "third_party/blink/renderer/platform/audio/denormal_disabler.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/bindings/source_location.h"
#include "third_party/blink/renderer/platform/bindings/v8_object_constructor.h"
@@ -125,6 +128,17 @@
waitable_event.Wait();
}
+ void RunDenormalProcessTest(WorkerThread* thread, bool expect_denormals) {
+ base::WaitableEvent waitable_event;
+ PostCrossThreadTask(
+ *thread->GetTaskRunner(TaskType::kInternalTest), FROM_HERE,
+ CrossThreadBindOnce(
+ &AudioWorkletGlobalScopeTest::RunDenormalProcessTestOnWorkletThread,
+ CrossThreadUnretained(this), CrossThreadUnretained(thread),
+ expect_denormals, CrossThreadUnretained(&waitable_event)));
+ waitable_event.Wait();
+ }
+
void RunParsingTest(WorkerThread* thread) {
base::WaitableEvent waitable_event;
PostCrossThreadTask(
@@ -346,6 +360,79 @@
wait_event->Signal();
}
+ void RunDenormalProcessTestOnWorkletThread(WorkerThread* thread,
+ bool expect_denormals,
+ base::WaitableEvent* wait_event) {
+ EXPECT_TRUE(thread->IsCurrentThread());
+
+ auto* global_scope = To<AudioWorkletGlobalScope>(thread->GlobalScope());
+ ScriptState* script_state =
+ global_scope->ScriptController()->GetScriptState();
+
+ ScriptState::Scope scope(script_state);
+ v8::Isolate* isolate = script_state->GetIsolate();
+ EXPECT_TRUE(isolate);
+ V8DoNotRunMicrotasksScope microtasks_scope(script_state);
+
+ String source_code =
+ R"JS(
+ class TestProcessor extends AudioWorkletProcessor {
+ constructor () { super(); }
+ process (inputs, outputs) {
+ let f64 = new Float64Array(1);
+ // The minimum positive normal 64-bit float is
+ // 2.225e-308. Therefore, 1.0e-309 is a denormal
+ // double. If FTZ/DAZ is enabled, it is treated
+ // as zero or flushed to zero, making f64[0]
+ // equal to 0.0. If disabled, the division
+ // computes 1.0e-310 (a valid denormal double
+ // > 0.0).
+ let denorm = 1.0e-309;
+ f64[0] = denorm / 10.0;
+ let outputChannel = outputs[0][0];
+ outputChannel[0] = f64[0] > 0.0 ? 1.0 : 0.0;
+ }
+ }
+ registerProcessor('testProcessor', TestProcessor);
+ )JS";
+ ExpectEvaluateScriptModule(global_scope, source_code, true);
+
+ auto* channel = MakeGarbageCollected<MessageChannel>(thread->GlobalScope());
+ MessagePortChannel dummy_port_channel = channel->port2()->Disentangle();
+ AudioWorkletProcessor* processor =
+ global_scope->CreateProcessor("testProcessor",
+ dummy_port_channel,
+ SerializedScriptValue::NullValue());
+ EXPECT_TRUE(processor);
+
+ Vector<scoped_refptr<AudioBus>> input_buses;
+ Vector<scoped_refptr<AudioBus>> output_buses;
+ HashMap<String, std::unique_ptr<AudioFloatArray>> param_data_map;
+ scoped_refptr<AudioBus> input_bus =
+ AudioBus::Create(1, kRenderQuantumFrames);
+ scoped_refptr<AudioBus> output_bus =
+ AudioBus::Create(1, kRenderQuantumFrames);
+ AudioChannel* output_channel = output_bus->Channel(0);
+
+ input_buses.push_back(input_bus.get());
+ output_buses.push_back(output_bus.get());
+ output_bus->Zero();
+
+ // Simulate the audio thread rendering stack by instantiating
+ // DenormalDisabler.
+ DenormalDisabler scoped_disabler;
+
+ // processor->Process() internally instantiates DenormalEnabler, which
+ // disables FTZ/DAZ during V8 execution if enabled.
+ processor->Process(input_buses, output_buses, param_data_map);
+
+ // Verify that the JS execution was affected by the outer
+ // DenormalDisabler only if the feature is disabled.
+ EXPECT_EQ(output_channel->Span()[0], expect_denormals ? 1.0f : 0.0f);
+
+ wait_event->Signal();
+ }
+
void RunParsingParameterDescriptorTestOnWorkletThread(
WorkerThread* thread,
base::WaitableEvent* wait_event) {
@@ -420,6 +507,30 @@
thread->WaitForShutdownForTesting();
}
+TEST_F(AudioWorkletGlobalScopeTest, DenormalProcessing_FeatureEnabled) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndEnableFeature(
+ blink::features::kAudioWorkletJSDenormalEnabler);
+
+ std::unique_ptr<OfflineAudioWorkletThread> thread =
+ CreateAudioWorkletThread();
+ RunDenormalProcessTest(thread.get(), /*expect_denormals=*/true);
+ thread->Terminate();
+ thread->WaitForShutdownForTesting();
+}
+
+TEST_F(AudioWorkletGlobalScopeTest, DenormalProcessing_FeatureDisabled) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndDisableFeature(
+ blink::features::kAudioWorkletJSDenormalEnabler);
+
+ std::unique_ptr<OfflineAudioWorkletThread> thread =
+ CreateAudioWorkletThread();
+ RunDenormalProcessTest(thread.get(), /*expect_denormals=*/false);
+ thread->Terminate();
+ thread->WaitForShutdownForTesting();
+}
+
TEST_F(AudioWorkletGlobalScopeTest, ParsingParameterDescriptor) {
std::unique_ptr<OfflineAudioWorkletThread> thread
= CreateAudioWorkletThread();
diff --git a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioworklet-interface/audioworklet-denormals.https.window-expected.txt b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioworklet-interface/audioworklet-denormals.https.window-expected.txt
deleted file mode 100644
index 443943d..0000000
--- a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioworklet-interface/audioworklet-denormals.https.window-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] Test denormal behavior in AudioWorkletGlobalScope
- assert_true: The denormals should be non-zeros in AudioWorkletGlobalScope. expected true got false
-Harness: the test ran to completion.
-
Original Bug Report
V8 optimizations make IEEE-only assumptions that are unsound with denormals disabled
VULNERABILITY DETAILS
Several V8 optimizing reductions are unsound when generated code runs with FTZ/DAZ enabled (as in the AudioWorklet isolate). This is a same-mode FTZ issue: compilation and execution are both under FTZ/DAZ. The bug is not about compiling in one FTZ mode and executing in another.
In each supported case, optimized code and unoptimized/baseline execution
disagree about the same floating-point value: one side observes a signed zero
and the other side preserves raw subnormal bits. The attached PoC feeds that
disagreement into Object.is, integer range typing, and JSArray growth. The
result is a stale JSArray length/backing-store mismatch that can be turned into
a controlled native write.
PoC Structure
The d8 PoC uses V8 native syntax so that it can show the pre-optimization state,
warm the function, request optimization with %OptimizeFunctionOnNextCall, and
then run the optimized function. The selected optimization and the JSArray
length-corruption sequence are in the same generated function.
The browser AudioWorklet demo cannot use V8 native optimization intrinsics.
Instead, it repeatedly runs the same generated function in the AudioWorklet
isolate so that Chrome’s normal tiering pipeline optimizes it. The query
parameter root=... selects which of the eight optimization cases to run; the
rest of the JavaScript and the command-execution code are shared.
The browser command-execution demo is intended for a local validation build with
the V8 sandbox disabled. The latest validation used out/current-d8/args.gn
with v8_enable_sandbox = false. The worklet also embeds two offsets into the
tested chrome binary; rerun ./refresh_rce_offsets.sh /path/to/chrome after
rebuilding Chrome. For the current validation binary, those offsets are
CHROME_DTOR_OFFSET = 0x0000000006f3d340n and
EXECVP_PLT_OFFSET = 0x000000000521c360n.
Affected Optimizations
Case ID: R001 DIV-ONE
Reduction / optimization:
Float64Div(x, 1.0) -> x.
Unsound FTZ assumption:
Runtime division under FTZ/DAZ materializes a subnormal input/result as signed zero;
deleting the division preserves raw subnormal bits.
V8 source locations:
v8/src/compiler/machine-operator-reducer.cc:676;
v8/src/compiler/turboshaft/machine-optimization-reducer.h:649;
v8/src/maglev/maglev-reducer-inl.h:3456.
Case ID: R005 UNARY-MATH-MINMAX
Reduction / optimization:
One-argument Math.min(x) / Math.max(x) reduced to ToNumber(x) / identity-like
numeric lowering.
Unsound FTZ assumption:
The unoptimized builtin path materializes the argument under FTZ; the optimized
one-argument reduction passes the raw subnormal through.
V8 source locations:
v8/src/compiler/js-call-reducer.cc:3076; v8/src/compiler/js-call-reducer.cc:3090;
v8/src/compiler/js-call-reducer.cc:5642.
Case ID: R006 SAMEVALUE-SIGNED-ZERO
Reduction / optimization:
Object.is / SameValue numeric lowering and signed-zero type reasoning.
Unsound FTZ assumption:
Optimized SameValue reasoning is not conservative for a negative subnormal that same-mode
FTZ execution can observe as -0.
V8 source locations:
v8/src/compiler/js-call-reducer.cc:3581; v8/src/compiler/typed-optimization.cc:677;
v8/src/compiler/simplified-lowering.cc:3770; v8/src/compiler/operation-typer.cc:1287.
Case ID: R009 MATH-ABS-FTZ-ZERO
Reduction / optimization:
Math.abs / NumberAbs lowered to raw Float64Abs / sign-bit clearing.
Unsound FTZ assumption:
The optimized path keeps raw subnormal-derived facts, while same-mode FTZ runtime
observation can classify the value as zero.
V8 source locations:
v8/src/compiler/js-call-reducer.cc:5580; v8/src/compiler/js-call-reducer.cc:2982;
v8/src/compiler/simplified-lowering.cc:3350;
v8/src/compiler/simplified-lowering.cc:3382;
v8/src/compiler/turboshaft/graph-builder.cc:707;
v8/src/compiler/backend/x64/instruction-selector-x64.cc:3189;
v8/src/maglev/maglev-reducer-inl.h:3945; v8/src/maglev/x64/maglev-ir-x64.cc:862.
Case ID: R010 SUBTRACT-FTZ-MINUSZERO-TYPING
Reduction / optimization:
OperationTyper::NumberSubtract and downstream SameValue typing for exact
negative-subnormal subtraction results.
Unsound FTZ assumption:
The type excludes MinusZero, but under FTZ/DAZ an exact negative subnormal arithmetic
result can be observed as -0.
V8 source locations:
v8/src/compiler/operation-typer.cc:682; v8/src/compiler/operation-typer.cc:692;
v8/src/compiler/operation-typer.cc:719; v8/src/compiler/operation-typer.cc:1287;
v8/src/compiler/typed-optimization.cc:657.
Case ID: R011 BINARY-MATH-MINMAX-COMPARE-SELECT
Reduction / optimization:
Two-argument Math.min / Math.max lowered to NumberMin / NumberMax and Float64
compare/select.
Unsound FTZ assumption:
Denormals can compare as zero under DAZ while still carrying raw sign/subnormal bits
through selected optimized paths.
V8 source locations:
v8/src/compiler/js-call-reducer.cc:3076; v8/src/compiler/js-call-reducer.cc:3094;
v8/src/compiler/operation-typer.cc:1092; v8/src/compiler/operation-typer.cc:1127;
v8/src/compiler/simplified-lowering.cc:3463;
v8/src/compiler/simplified-lowering.cc:3512;
v8/src/compiler/representation-change.cc:1851.
Case ID: R018 FLOAT64MOD-ONE
Reduction / optimization:
% 1.0 / Float64Mod(x, 1.0) lowering and reducer handling.
Unsound FTZ assumption:
Source execution under FTZ flushes the subnormal before modulo; optimized lowering
preserves the raw subnormal result used by the attached PoC.
V8 source locations:
v8/src/compiler/js-typed-lowering.cc:447;
v8/src/compiler/machine-operator-reducer.cc:710;
v8/src/compiler/representation-change.cc:1799;
v8/src/compiler/representation-change.cc:1801;
v8/src/compiler/simplified-lowering.cc:3173;
v8/src/compiler/simplified-lowering.cc:3205;
v8/src/maglev/maglev-graph-optimizer.cc:2921; v8/src/maglev/x64/maglev-ir-x64.cc:809;
v8/src/maglev/x64/maglev-ir-x64.cc:816.
Case ID: R062 JSARRAY-DOUBLE-ELEMENT-MATERIALIZATION
Reduction / optimization:
JSArray PACKED_DOUBLE_ELEMENTS / HOLEY_DOUBLE_ELEMENTS store/load materialization and
direct double-element forwarding.
Unsound FTZ assumption:
Baseline FTZ stores/reloads the element as signed zero; optimized double-element paths can
forward raw subnormal bits into the SameValue and range-typing sequence used by the
attached PoC.
V8 source locations:
v8/src/compiler/js-native-context-specialization.cc:3795;
v8/src/compiler/js-native-context-specialization.cc:4140;
v8/src/compiler/access-builder.cc:1399;
v8/src/compiler/turboshaft/machine-lowering-reducer-inl.h:3407;
v8/src/compiler/turboshaft/machine-lowering-reducer-inl.h:3475;
v8/src/maglev/maglev-graph-builder.cc:4361;
v8/src/maglev/maglev-graph-builder.cc:4374;
v8/src/maglev/maglev-graph-builder.cc:5672;
v8/src/maglev/maglev-graph-builder.cc:5904; v8/src/maglev/maglev-ir.h:8858;
v8/src/maglev/maglev-ir.h:8925;
v8/src/compiler/turboshaft/turbolev-graph-builder.cc:3479;
v8/src/compiler/turboshaft/turbolev-graph-builder.cc:3617.
VERSION
Chrome Version: 151.0.7915.0 dev build.
Operating System: Ubuntu 24.04.4 LTS x86_64
(Linux 6.17.0-1010-aws).
Validated against:
- Chromium checkout:
6f813967910885b4b8dafaf6a72b7a5c80fba157. - V8 checkout:
0021d325c3a448f4e26e21ef4e0db1bea1e39fda. d8/ V8 version:15.1.192.- Browser RCE validation build:
v8_enable_sandbox = false.
REPRODUCTION CASE
Attached files:
- controlled release crash / stale-array state:
rce_controlled_crash.js; - browser AudioWorklet RCE demo launching
/usr/bin/xcalc:rce_poc.html; - browser RCE worklet:
rce_worklet.js; - RCE offset-refresh script:
refresh_rce_offsets.sh.
Controlled crash state without performing the marker write:
D8=${D8:-/path/to/d8}
"$D8" --allow-natives-syntax --flush-denormals \
rce_controlled_crash.js -- R010
Expected pre-crash state for a vulnerable build includes:
CONTROLLED_STATE i=0,length=65,first[4] aliases second.length
To perform the marker write and crash the vulnerable build:
"$D8" --allow-natives-syntax --flush-denormals \
rce_controlled_crash.js -- R010 trigger
The same script supports all release-memory-corruption cases:
"$D8" --allow-natives-syntax --flush-denormals \
rce_controlled_crash.js -- list
For the browser RCE path, serve the directory containing rce_poc.html and
rce_worklet.js over HTTP and open:
rce_poc.html?root=R010&action=run&cmd=/usr/bin/xcalc&seconds=60&stay=1
The launcher accepts root=R001, R005, R006, R009, R010, R011,
R018, or R062. That parameter selects the affected optimization to
exercise. If cmd is omitted, the demo defaults to /usr/bin/xcalc; any other
command can be supplied with the cmd query parameter.
CRASH INFORMATION
Type of crash: V8 process crash in d8 for the controlled native-write trigger;
browser AudioWorklet command-execution demo in a local Chrome validation build
with the V8 sandbox disabled.
Crash State:
ROOT R010 SUBTRACT-FTZ-MINUSZERO-TYPING
BASELINE d=0x8000000000000000,b=true,i=64,len=65
OPT_STATE d=0x8000000000000000,b=true,n=-1,o=4294967296,o_=0,i=0,first.length=65,first[4]=3,second.length=3,second[0]=1,status=41
CONTROLLED_STATE i=0,length=65,first[4] aliases second.length
Received signal 11 SEGV_ACCERR 21de4141413f
==== C stack trace ===============================
/home/moyix/chromium-src-codex/src/out/current-d8/d8(+0x1f39676)[0x6040e41c3676]
/lib/x86_64-linux-gnu/libc.so.6(+0x45330)[0x7f3c96c45330]
/home/moyix/chromium-src-codex/src/out/current-d8/d8(+0xa0172e)[0x6040e2c8b72e]
/home/moyix/chromium-src-codex/src/out/current-d8/d8(+0xa04b4e)[0x6040e2c8eb4e]
/home/moyix/chromium-src-codex/src/out/current-d8/d8(+0x1dbfdb6)[0x6040e4049db6]
CREDIT INFORMATION
Reporter credit: Found by XBOW and triaged by Brendan Dolan-Gavitt