Medium CVSS 7.8 webkit Type Confusion 🔧 Commit mapped

Overview

Medium
Severity
7.8
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionA type confusion issue could lead to memory corruption
ComponentWebCore
Sub-componentWebCore Platform/Audio
Bug ClassType Confusion
Tracker286694
Fix commitc1b04541303e (WebKit/WebKit) +171/-111
CWECWE-843 (Type confusion)
CVSS vectorCVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle V8 Security Team
Disclosed2025-05-12

Background

Denormal (subnormal) floats
Very small floating-point values near zero; processing them is slow, so audio code flushes them to zero via CPU mode bits.
FP status/control word (MXCSR / FPCR)
The per-thread register controlling FP behavior, including flush-to-zero (FTZ) and denormals-are-zero (DAZ); changing it alters arithmetic semantics for all subsequent FP ops on that thread.
AudioWorklet
A WebAudio feature that runs author-supplied JavaScript on the real-time audio rendering thread, where DenormalDisabler governs the FP environment.
JIT FP soundness
The DFG/FTL constant-fold and range-analyze floating-point assuming IEEE-754; if the runtime FP mode differs (FTZ/DAZ), runtime results can diverge from compile-time assumptions.
RAII scoping
Acquiring/releasing the FP mode in a constructor/destructor so it is changed and restored exactly, preventing leakage beyond the intended region.

Root Cause Analysis

OBSERVED (from the diff): DenormalDisabler is an RAII guard used around WebAudio rendering to put the CPU floating-point environment into flush-to-zero / denormals-are-zero mode (denormals badly hurt audio DSP performance). The pre-patch implementation defined the guard only for OS(WINDOWS) && COMPILER(MSVC) or COMPILER(GCC_COMPATIBLE) && defined(__SSE__) (x86), wrapping the whole class in #ifdef HAVE_DENORMAL; on ARM (Apple Silicon / iOS / iPadOS) HAVE_DENORMAL was undefined, so there was effectively no working, correctly-scoped denormal control on Apple’s primary architecture. Its constructor unconditionally OR’d flush bits into the status word (setCSR(m_savedCSR | 0x8040/0x8000)) and the destructor wrote back m_savedCSR, with x86 detection done via inline stmxcsr/ldmxcsr and an fxsave-based DAZ probe.

The fix rewrites it: it reads the status word once (readStatusWord: stmxcsr on x86, mrs FPCR on ARM64, vmrs FPSCR on ARM), records m_disablingActivated = areDenormalsEnabled(m_savedCSR), changes the mode ONLY when denormals were actually enabled, and in the destructor restores the exact saved word only if it changed it; HAVE_DENORMAL moves into PlatformHave.h for X86_SSE2/ARM/ARM64 and a manual flushDenormalFloatToZero fallback covers platforms without hardware support. The net effect is a precisely-scoped, save-exactly/restore-exactly FP-environment guard that is correct on ARM as well as x86. INFERENCE (mechanism, not shown by the diff): the security significance is the interaction between this non-IEEE FP mode and JIT-compiled JavaScript. AudioWorklet executes author JS on the audio rendering thread where the denormal guard is active, and the DFG/FTL JIT constant-folds and range-analyzes floating-point operations assuming standard IEEE-754 semantics. If the flush-to-zero/denormals-are-zero mode is left active, mis-scoped, or (on ARM) uncontrolled while JIT-compiled JS runs, runtime FP results (denormals becoming zero, changed comparisons) diverge from the values the optimizer proved at compile time, breaking a JIT soundness invariant; such a divergence can be turned into a type confusion where a value the compiler treated as a specific type/shape differs at runtime, leading to memory corruption.

The patch removes that divergence by making the FP-mode change tightly scoped and architecture-correct so it cannot bleed into JIT-compiled code with mismatched assumptions.

Key insight
The bug is an FP-environment scoping problem: WebAudio’s denormal guard flushed denormals to zero (and was absent/uncontrolled on ARM), and that non-IEEE FP mode could bleed into JIT-compiled JavaScript on the audio thread, breaking the optimizer’s floating-point assumptions into a type confusion. The fix makes the mode change precisely saved/restored and architecture-correct.

Attack Path

  1. Set up a WebAudio graph with AudioWorklet Create an AudioContext and register an AudioWorkletProcessor so attacker JavaScript runs on the audio rendering thread, where DenormalDisabler controls the FP environment.
  2. Induce an FP-mode mismatch [inference] Rely on the flush-to-zero/denormals-are-zero mode being active (or, on ARM, uncontrolled) while JIT-compiled JS on that thread executes floating-point the DFG/FTL folded assuming IEEE semantics.
  3. Diverge runtime from compile-time FP [inference] Craft FP computations (denormal-producing arithmetic, comparisons against tiny values) whose runtime results under flush-to-zero differ from the constant-folded/range-analyzed values the JIT baked in.
  4. Break a JIT invariant into type confusion [inference] Use the divergence so a value the optimizer proved to be a given type/shape (or an index proved in-bounds) is different at runtime — a type confusion.
  5. Corrupt memory [inference] Escalate the type confusion to out-of-bounds or confused-object read/write in the WebContent process.

Impact Assessment

The patch itself is a correctness/scoping fix to the audio FP-environment guard (precise save/restore, only-when-needed, and a previously-absent ARM implementation). Its security weight — reflected in the advisory’s ’type confusion could lead to memory corruption’ — comes from the inferred interaction between a mis-scoped flush-to-zero FP mode and JIT-compiled JavaScript (notably AudioWorklet on the same thread), where an FP-semantics mismatch can break JIT soundness into a type confusion and thence memory corruption. That exploitation chain is reasoned inference, not shown by the diff; what the diff establishes is the removal of the FP-mode divergence. Confined to the WebContent process. Rated medium (CVSS per NVD).

Changed Functions

FunctionChangeNotes
if
Source/WebCore/platform/audio/DenormalDisabler.cpp
modified
DenormalDisabler
Source/WebCore/platform/audio/DenormalDisabler.h
modified
DenormalDisabler
Source/WebCore/platform/audio/DenormalDisabler.h
modified
DenormalProcessor
LayoutTests/webaudio/audoworklet-denormal-float-values.html
modified
process
LayoutTests/webaudio/audoworklet-denormal-float-values.html
modified

Files Changed

  • LayoutTests/platform/ios/imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audioworklet-interface/audioworklet-denormals.https.window-expected.txt
  • LayoutTests/platform/mac/TestExpectations
  • LayoutTests/webaudio/audoworklet-denormal-float-values-expected.txt
  • LayoutTests/webaudio/audoworklet-denormal-float-values.html
  • Source/WTF/wtf/PlatformHave.h
  • Source/WebCore/Sources.txt
  • Source/WebCore/WebCore.xcodeproj/project.pbxproj
  • Source/WebCore/platform/audio/DenormalDisabler.cpp
  • Source/WebCore/platform/audio/DenormalDisabler.h

Audit Directions

  • Other FP-mode changers
    grep for MXCSR/FPCR/FPSCR access (stmxcsr/ldmxcsr, mrs/msr FPCR, _controlfp) and rounding/flush-mode changes anywhere reachable from a thread that also runs JIT-compiled JS (audio, WebGL, workers).
  • AudioWorklet thread FP invariants
    Audit the AudioWorklet render path for places where author JS executes while a non-default FP environment is active, and confirm the environment is IEEE (or restored) before JIT-compiled JS runs.
  • RAII scope leaks
    Review DenormalDisabler use sites (and similar scoped-mode guards) for early returns, exceptions, or nested/threaded scopes where the mode could outlive the guard.
diff --git a/Source/WTF/wtf/PlatformHave.h b/Source/WTF/wtf/PlatformHave.h
index e25b258608a0..6d14f77c6af6 100644
--- a/Source/WTF/wtf/PlatformHave.h
+++ b/Source/WTF/wtf/PlatformHave.h
@@ -1891,3 +1891,8 @@
     || (PLATFORM(COCOA) && !PLATFORM(MAC))
 #define HAVE_X25519_ZERO_CHECKS 1
 #endif
+
+#if !defined(HAVE_DENORMAL) \
+    && (CPU(X86_SSE2) || CPU(ARM) || CPU(ARM64))
+#define HAVE_DENORMAL 1
+#endif
diff --git a/Source/WebCore/Sources.txt b/Source/WebCore/Sources.txt
index 310c103c6500..be57a81d9ce7 100644
--- a/Source/WebCore/Sources.txt
+++ b/Source/WebCore/Sources.txt
@@ -2381,6 +2381,7 @@ platform/audio/AudioResamplerKernel.cpp
 platform/audio/AudioUtilities.cpp
 platform/audio/Biquad.cpp
 platform/audio/Cone.cpp
+platform/audio/DenormalDisabler.cpp
 platform/audio/DirectConvolver.cpp
 platform/audio/Distance.cpp
 platform/audio/DownSampler.cpp
diff --git a/Source/WebCore/WebCore.xcodeproj/project.pbxproj b/Source/WebCore/WebCore.xcodeproj/project.pbxproj
index 8a94c2ea1562..04e41af50e1b 100644
--- a/Source/WebCore/WebCore.xcodeproj/project.pbxproj
+++ b/Source/WebCore/WebCore.xcodeproj/project.pbxproj
@@ -19273,6 +19273,7 @@
 		CDD8D310271F2C8D00DAA256 /* FormatDescriptionUtilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FormatDescriptionUtilities.h; sourceTree = "<group>"; };
 		CDD8D311271F2C8D00DAA256 /* FormatDescriptionUtilities.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = FormatDescriptionUtilities.cpp; sourceTree = "<group>"; };
 		CDDC1E7918A952F30027A9D4 /* MediaSourcePrivateClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MediaSourcePrivateClient.h; sourceTree = "<group>"; };
+		CDDD004A2D49B890000B23CC /* DenormalDisabler.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DenormalDisabler.cpp; sourceTree = "<group>"; };
 		CDDDEA222538CD8000A1300C /* MediaSession.idl */ = {isa = PBXFileReference; lastKnownFileType = text; path = MediaSession.idl; sourceTree = "<group>"; };
 		CDDDEA232538CD8000A1300C /* MediaSession.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MediaSession.h; sourceTree = "<group>"; };
 		CDDDEA242538CD8000A1300C /* MediaSession.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MediaSession.cpp; sourceTree = "<group>"; };
@@ -39324,6 +39325,7 @@
 				FD31605712B026F700C1A359 /* Biquad.h */,
 				FD31605812B026F700C1A359 /* Cone.cpp */,
 				FD31605912B026F700C1A359 /* Cone.h */,
+				CDDD004A2D49B890000B23CC /* DenormalDisabler.cpp */,
 				FD1660A413787C6D001FFA7B /* DenormalDisabler.h */,
 				FDAF19961513D131008DB0C3 /* DirectConvolver.cpp */,
 				FDAF19971513D131008DB0C3 /* DirectConvolver.h */,
diff --git a/Source/WebCore/platform/audio/DenormalDisabler.cpp b/Source/WebCore/platform/audio/DenormalDisabler.cpp
new file mode 100644
index 000000000000..35715e663ce7
--- /dev/null
+++ b/Source/WebCore/platform/audio/DenormalDisabler.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2025 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "DenormalDisabler.h"
+
+namespace WebCore {
+
+#if HAVE(DENORMAL)
+
+#if CPU(X86_SSE2)
+constexpr int kDenormalBitMask = 0x8040;
+#elif CPU(ARM) || CPU(ARM64)
+constexpr int kDenormalBitMask = 1 << 24;
+#endif
+
+
+#if CPU(X86_SSE2)
+constexpr int kFlushDenormalsValue = kDenormalBitMask;
+#elif CPU(ARM) || CPU(ARM64)
+constexpr int kFlushDenormalsValue = kDenormalBitMask;
+#endif
+
+static intptr_t readStatusWord()
+{
+    intptr_t result = DenormalDisabler::kUndefinedStatusWord;
+#if CPU(X86_SSE2)
+    asm volatile("stmxcsr %0" : "=m"(result));
+#elif CPU(ARM)
+    asm volatile("vmrs %[result], FPSCR" : [result] "=r"(result));
+#elif CPU(ARM64)
+    asm volatile("mrs %x[result], FPCR" : [result] "=r"(result));
+#endif
+    return result;
+}
+
+static void setStatusWord(intptr_t statusWord)
+{
+#if CPU(X86_SSE2)
+    asm volatile("ldmxcsr %0" : : "m"(statusWord));
+#elif CPU(ARM)
+    asm volatile("vmsr FPSCR, %[src]" : : [src] "r"(statusWord));
+#elif CPU(ARM64)
+    asm volatile("msr FPCR, %x[src]" : : [src] "r"(statusWord));
+#endif
+}
+
+constexpr bool areDenormalsEnabled(intptr_t statusWord)
+{
+    return (statusWord & kDenormalBitMask) != kFlushDenormalsValue;
+}
+
+DenormalDisabler::DenormalDisabler()
+    : m_savedCSR { readStatusWord() }
+    , m_disablingActivated { areDenormalsEnabled(m_savedCSR) }
+{
+    if (m_disablingActivated) {
+        ASSERT(m_savedCSR != kUndefinedStatusWord);
+        setStatusWord((m_savedCSR & ~kDenormalBitMask) | kFlushDenormalsValue);
+        ASSERT(!areDenormalsEnabled(readStatusWord()));
+    }
+}
+
+DenormalDisabler::~DenormalDisabler()
+{
+    if (m_disablingActivated) {
+        ASSERT(m_savedCSR != kUndefinedStatusWord);
+        setStatusWord(m_savedCSR);
+    }
+}
+#else
+DenormalDisabler::DenormalDisabler() = default
+DenormalDisabler::~DenormalDisabler() = default;
+#endif
+
+}
diff --git a/Source/WebCore/platform/audio/DenormalDisabler.h b/Source/WebCore/platform/audio/DenormalDisabler.h
index eb90e9523fe4..e8391096b7d7 100644
--- a/Source/WebCore/platform/audio/DenormalDisabler.h
+++ b/Source/WebCore/platform/audio/DenormalDisabler.h
@@ -22,132 +22,35 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef DenormalDisabler_h
-#define DenormalDisabler_h
+#pragma once
 
-#include <cinttypes>
-#include <wtf/MathExtras.h>
-#include <wtf/StdLibExtras.h>
 #include <wtf/TZoneMallocInlines.h>
 
 namespace WebCore {
 
-// Deal with denormals. They can very seriously impact performance on x86.
-
-// Define HAVE_DENORMAL if we support flushing denormals to zero.
-#if OS(WINDOWS) && COMPILER(MSVC)
-#define HAVE_DENORMAL
-#endif
-
-#if COMPILER(GCC_COMPATIBLE) && defined(__SSE__)
-#define HAVE_DENORMAL
-#endif
-
-#ifdef HAVE_DENORMAL
 class DenormalDisabler final {
     WTF_MAKE_TZONE_ALLOCATED_INLINE(DenormalDisabler);
 public:
-    DenormalDisabler()
-            : m_savedCSR(0)
-    {
-#if OS(WINDOWS) && COMPILER(MSVC)
-        // Save the current state, and set mode to flush denormals.
-        //
-        // http://stackoverflow.com/questions/637175/possible-bug-in-controlfp-s-may-not-restore-control-word-correctly
-        _controlfp_s(&m_savedCSR, 0, 0);
-        unsigned int unused;
-        _controlfp_s(&unused, _DN_FLUSH, _MCW_DN);
-#else
-        m_savedCSR = getCSR();
-        setCSR(m_savedCSR | (isDAZSupported() ? 0x8040 : 0x8000));
-#endif
-    }
+    DenormalDisabler();
+    ~DenormalDisabler();
 
-    ~DenormalDisabler()
-    {
-#if OS(WINDOWS) && COMPILER(MSVC)
-        unsigned int unused;
-        _controlfp_s(&unused, m_savedCSR, _MCW_DN);
-#else
-        setCSR(m_savedCSR);
-#endif
-    }
-
-    // This is a nop if we can flush denormals to zero in hardware.
     static inline float flushDenormalFloatToZero(float f)
     {
-#if OS(WINDOWS) && COMPILER(MSVC) && (!_M_IX86_FP)
-        // For systems using x87 instead of sse, there's no hardware support
-        // to flush denormals automatically. Hence, we need to flush
-        // denormals to zero manually.
+#if !HAVE(DENORMAL)
+        // For systems without hardware support to flush denormals
+        // automatically, we need to flush denormals to zero manually.
         return (std::abs(f) < FLT_MIN) ? 0.0f : f;
 #else
         return f;
-#endif
-    }
-private:
-#if COMPILER(GCC_COMPATIBLE) && defined(__SSE__)
-    static inline bool isDAZSupported()
-    {
-#if CPU(X86_64)
-        return true;
-#else
-        static bool s_isInited = false;
-        static bool s_isSupported = false;
-        if (s_isInited)
-            return s_isSupported;
-
-        struct fxsaveResult {
-            uint8_t before[28];
-            uint32_t CSRMask;
-            uint8_t after[480];
-        } __attribute__ ((aligned (16)));
-
-        fxsaveResult registerData;
-        zeroBytes(registerData);
-        asm volatile("fxsave %0" : "=m" (registerData));
-        s_isSupported = registerData.CSRMask & 0x0040;
-        s_isInited = true;
-        return s_isSupported;
 #endif
     }
 
-    inline int getCSR()
-    {
-        int result;
-        asm volatile("stmxcsr %0" : "=m" (result));
-        return result;
-    }
-
-    inline void setCSR(int a)
-    {
-        int temp = a;
-        asm volatile("ldmxcsr %0" : : "m" (temp));
-    }
+    static constexpr intptr_t kUndefinedStatusWord { -1 };
 
-#endif
-
-    unsigned int m_savedCSR;
-};
-
-#else
-// FIXME: add implementations for other architectures and compilers
-class DenormalDisabler final {
-    WTF_MAKE_TZONE_ALLOCATED_INLINE(DenormalDisabler);
-public:
-    DenormalDisabler() { }
+private:
 
-    // Assume the worst case that other architectures and compilers
-    // need to flush denormals to zero manually.
-    static inline float flushDenormalFloatToZero(float f)
-    {
-        return (std::abs(f) < FLT_MIN) ? 0.0f : f;
-    }
+    const intptr_t m_savedCSR { kUndefinedStatusWord };
+    const bool m_disablingActivated { false };
 };
 
-#endif
-
 } // WebCore
-
-#undef HAVE_DENORMAL
-#endif // DenormalDisabler_h
diff --git a/LayoutTests/platform/ios/imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audioworklet-interface/audioworklet-denormals.https.window-expected.txt b/LayoutTests/platform/ios/imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audioworklet-interface/audioworklet-denormals.https.window-expected.txt
deleted file mode 100644
index 4f23ae90695c..000000000000
--- a/LayoutTests/platform/ios/imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audioworklet-interface/audioworklet-denormals.https.window-expected.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-
-PASS Test denormal behavior in AudioWorkletGlobalScope
-
diff --git a/LayoutTests/platform/mac/TestExpectations b/LayoutTests/platform/mac/TestExpectations
index 03b80350d704..399eac62697e 100644
--- a/LayoutTests/platform/mac/TestExpectations
+++ b/LayoutTests/platform/mac/TestExpectations
@@ -2085,7 +2085,6 @@ webkit.org/b/256108 media/video-audio-session-mode.html [ Failure ]
 webkit.org/b/258181 [ Debug ] inspector/debugger/async-stack-trace-truncate.html [ Slow Failure ]
 
 # webkit.org/b/258328 Umbrella Bug: Batch mark expectations slowing down EWS
-[ arm64 ] imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audioworklet-interface/audioworklet-denormals.https.window.html [ Failure ]
 [ Debug ] media/modern-media-controls/pip-support/pip-support-enabled.html [ Pass Crash ]
 
 webkit.org/b/236128 imported/w3c/web-platform-tests/html/user-activation/activation-trigger-mouse-right.html [ Skip ]
diff --git a/LayoutTests/webaudio/audoworklet-denormal-float-values-expected.txt b/LayoutTests/webaudio/audoworklet-denormal-float-values-expected.txt
new file mode 100644
index 000000000000..df9266d13521
--- /dev/null
+++ b/LayoutTests/webaudio/audoworklet-denormal-float-values-expected.txt
@@ -0,0 +1,3 @@
+
+PASS audioworklet-denormal-float-values
+
diff --git a/LayoutTests/webaudio/audoworklet-denormal-float-values.html b/LayoutTests/webaudio/audoworklet-denormal-float-values.html
new file mode 100644
index 000000000000..59b7ee018b5b
--- /dev/null
+++ b/LayoutTests/webaudio/audoworklet-denormal-float-values.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>audioworklet-denormal-float-values</title>
+    <script src="../resources/testharness.js"></script>
+    <script src="../resources/testharnessreport.js"></script>
+    <script>
+    promise_test(async test => {
+        const worklet_source = `
+            const denormal = 5e-324;  
+
+            class DenormalProcessor extends AudioWorkletProcessor {
+                process(inputs, outputs, parameters) {
+                    this.port.postMessage(\`Denormal value in inside processor: \${ denormal }\`);  
+                    return true;
+                }
+            }
+            registerProcessor('denormal-processor', DenormalProcessor);
+        `;
+
+        const denormal = 5e-324;
+        assert_equals(`Denormal value outside processor: ${ denormal }`, 'Denormal value outside processor: 5e-324');
+
+        let blob = new Blob([worklet_source], { type: 'application/javascript' });
+        let worklet = URL.createObjectURL(blob);
+
+        var context = new AudioContext({ sampleRate: 44100});
+        const dest = context.destination;
+
+        await context.audioWorklet.addModule(worklet);
+        var audioWorklet = new AudioWorkletNode(context, 'denormal-processor');
+        audioWorklet.connect(dest);
+
+        audioWorklet.port.onmessage = (msg) => console.log(msg.data);
+        audioWorkletMessagePromise = new Promise((resolve, reject) => {
+            audioWorklet.port.onmessage = resolve;
+            setTimeout(() => reject('audioWorklet message promise timed out'), 5000);
+        });
+
+        let message = await audioWorkletMessagePromise;
+        assert_equals(message.data, 'Denormal value in inside processor: 0');
+
+        context.suspend();
+    });
+</script>
+</head>
+<body>
+
+</body>
+</html>
\ No newline at end of file
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/LayoutTests/platform/ios/imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audioworklet-interface/audioworklet-denormals.https.window-expected.txt b/LayoutTests/platform/ios/imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audioworklet-interface/audioworklet-denormals.https.window-expected.txt
deleted file mode 100644
index 4f23ae90695c..000000000000
--- a/LayoutTests/platform/ios/imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audioworklet-interface/audioworklet-denormals.https.window-expected.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-
-PASS Test denormal behavior in AudioWorkletGlobalScope
-
diff --git a/LayoutTests/platform/mac/TestExpectations b/LayoutTests/platform/mac/TestExpectations
index 03b80350d704..399eac62697e 100644
--- a/LayoutTests/platform/mac/TestExpectations
+++ b/LayoutTests/platform/mac/TestExpectations
@@ -2085,7 +2085,6 @@ webkit.org/b/256108 media/video-audio-session-mode.html [ Failure ]
 webkit.org/b/258181 [ Debug ] inspector/debugger/async-stack-trace-truncate.html [ Slow Failure ]
 
 # webkit.org/b/258328 Umbrella Bug: Batch mark expectations slowing down EWS
-[ arm64 ] imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audioworklet-interface/audioworklet-denormals.https.window.html [ Failure ]
 [ Debug ] media/modern-media-controls/pip-support/pip-support-enabled.html [ Pass Crash ]
 
 webkit.org/b/236128 imported/w3c/web-platform-tests/html/user-activation/activation-trigger-mouse-right.html [ Skip ]
diff --git a/LayoutTests/webaudio/audoworklet-denormal-float-values-expected.txt b/LayoutTests/webaudio/audoworklet-denormal-float-values-expected.txt
new file mode 100644
index 000000000000..df9266d13521
--- /dev/null
+++ b/LayoutTests/webaudio/audoworklet-denormal-float-values-expected.txt
@@ -0,0 +1,3 @@
+
+PASS audioworklet-denormal-float-values
+
diff --git a/LayoutTests/webaudio/audoworklet-denormal-float-values.html b/LayoutTests/webaudio/audoworklet-denormal-float-values.html
new file mode 100644
index 000000000000..59b7ee018b5b
--- /dev/null
+++ b/LayoutTests/webaudio/audoworklet-denormal-float-values.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>audioworklet-denormal-float-values</title>
+    <script src="../resources/testharness.js"></script>
+    <script src="../resources/testharnessreport.js"></script>
+    <script>
+    promise_test(async test => {
+        const worklet_source = `
+            const denormal = 5e-324;  
+
+            class DenormalProcessor extends AudioWorkletProcessor {
+                process(inputs, outputs, parameters) {
+                    this.port.postMessage(\`Denormal value in inside processor: \${ denormal }\`);  
+                    return true;
+                }
+            }
+            registerProcessor('denormal-processor', DenormalProcessor);
+        `;
+
+        const denormal = 5e-324;
+        assert_equals(`Denormal value outside processor: ${ denormal }`, 'Denormal value outside processor: 5e-324');
+
+        let blob = new Blob([worklet_source], { type: 'application/javascript' });
+        let worklet = URL.createObjectURL(blob);
+
+        var context = new AudioContext({ sampleRate: 44100});
+        const dest = context.destination;
+
+        await context.audioWorklet.addModule(worklet);
+        var audioWorklet = new AudioWorkletNode(context, 'denormal-processor');
+        audioWorklet.connect(dest);
+
+        audioWorklet.port.onmessage = (msg) => console.log(msg.data);
+        audioWorkletMessagePromise = new Promise((resolve, reject) => {
+            audioWorklet.port.onmessage = resolve;
+            setTimeout(() => reject('audioWorklet message promise timed out'), 5000);
+        });
+
+        let message = await audioWorkletMessagePromise;
+        assert_equals(message.data, 'Denormal value in inside processor: 0');
+
+        context.suspend();
+    });
+</script>
+</head>
+<body>
+
+</body>
+</html>
\ No newline at end of file
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker.