CVE-2026-74961
Overview
Background
- `AudioWorkletNode`
- a Web Audio node whose audio processing runs as author-supplied JavaScript on a dedicated real-time rendering thread.
- `SharedArrayBuffer`
- a shared-memory object that multiple agents can read and write concurrently, which is what
allowSharedMemoryObjectspermits through structured clone. - `JS::CloneDataPolicy`
- a policy object passed to structured clone that declares which classes of transferable or shareable objects (including shared memory) may cross an agent boundary.
- `IsSharedMemoryAllowed`
- a per-global predicate that reports whether the current global is permitted to use shared memory (gated by cross-origin isolation).
Root Cause Analysis
In AudioWorkletNode::Constructor, the structured-clone policy used to pass the node’s options and processor data to the rendering thread unconditionally called cloneDataPolicy.allowSharedMemoryObjects(). This meant a SharedArrayBuffer could be cloned into the AudioWorklet regardless of whether the constructing global was actually allowed to use shared memory. The violated invariant is that shared memory must only be shareable when nsIGlobalObject::IsSharedMemoryAllowed() holds for the current global — the same cross-origin-isolation gate that governs SharedArrayBuffer availability elsewhere.
The fix retrieves the current global via xpc::CurrentNativeGlobal(cx) and only enables shared-memory objects in the clone policy when IsSharedMemoryAllowed() returns true. This closes the path that let non-isolated contexts obtain shared memory, and thus a high-resolution timing side channel, through the Web Audio worklet.
allowSharedMemoryObjects on the current global’s IsSharedMemoryAllowed() state.Attack Path
- Load a non-isolated page
An attacker serves a page that is not cross-origin isolated, so
SharedArrayBufferand shared memory should be unavailable to it. - Construct an `AudioWorkletNode`
The page creates an
AudioWorkletNode, whose constructor builds a clone policy that unconditionally permits shared-memory objects. - Smuggle shared memory into the worklet Shared-memory objects are cloned across the agent boundary into the rendering thread despite the missing isolation gate.
- Build a high-resolution timer The attacker uses the smuggled shared memory to construct a fine-grained timing primitive normally denied to non-isolated contexts.
- Run a timing side channel The timer is used to mount Spectre-style or other timing-based side-channel measurements.
Impact Assessment
AudioWorkletNode. The gain is an information-disclosure side channel rather than direct memory corruption, consistent with the moderate severity rating.Files Changed
dom/media/webaudio/AudioWorkletNode.cpp
Audit Directions
- Unconditional `allowSharedMemoryObjects`Search other structured-clone sites for
allowSharedMemoryObjects()orallowIntraClusterClonableSharedObjects()calls that are not guarded byIsSharedMemoryAllowed(). - Cross-agent boundariesReview worker, worklet, and message-port constructors that clone data to a different thread or agent to ensure they re-check the destination or current global’s shared-memory permission.
- Isolation-gated capabilitiesAudit features that should be gated by cross-origin isolation (
SharedArrayBuffer, high-resolution timers) for paths that expose them without consulting the per-global permission predicate.
Patch
diff --git a/dom/media/webaudio/AudioWorkletNode.cpp b/dom/media/webaudio/AudioWorkletNode.cpp
index ba2a5be4e01..2ce03b433c8 100644
--- a/dom/media/webaudio/AudioWorkletNode.cpp
+++ b/dom/media/webaudio/AudioWorkletNode.cpp
@@ -765,7 +765,10 @@ already_AddRefed<AudioWorkletNode> AudioWorkletNode::Constructor(
// can share memory.
JS::CloneDataPolicy cloneDataPolicy;
cloneDataPolicy.allowIntraClusterClonableSharedObjects();
- cloneDataPolicy.allowSharedMemoryObjects();
+ nsIGlobalObject* currentGlobal = xpc::CurrentNativeGlobal(cx);
+ if (currentGlobal->IsSharedMemoryAllowed()) {
+ cloneDataPolicy.allowSharedMemoryObjects();
+ }
// StructuredCloneHolder does not have a move constructor. Instead allocate
// memory so that the pointer can be passed to the rendering thread.