High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionIncorrect boundary conditions in the Web Audio component
ComponentDOM
Bug ClassLogic Error
Tracker2038465
Fix commit83557d718c22 (firefox) +3/-3
CISA KEVNot listed
CreditedZijie Zhao
Disclosed2026-06-16

Background

Web Audio API
the browser subsystem that lets untrusted web content synthesize, process, and read back audio sample data as float arrays.
`ThreadSharedFloatArrayBufferList`
a reference-counted container holding per-channel float buffers that are shared across the audio thread and can be transferred into JavaScript-visible ArrayBuffers.
`js_pod_malloc` vs `js_pod_calloc`
allocator helpers where js_pod_malloc returns uninitialized memory while js_pod_calloc returns zero-filled memory.
`JS::NewArrayBufferWithContents`
the SpiderMonkey entry point that wraps a raw C++ buffer as a JavaScript ArrayBuffer, exposing its bytes directly to script.

Root Cause Analysis

ThreadSharedFloatArrayBufferList::Create allocated each channel’s float buffer with js_pod_malloc<float>(aLength), which leaves the aLength floats uninitialized. The buffers are documented as being suitable for transfer to JS::NewArrayBufferWithContents, so their contents become directly readable by web content once wrapped in an ArrayBuffer. The violated invariant is that any memory reachable by untrusted script must be fully initialized before exposure; when a caller failed to overwrite every element via GetDataForWrite() before the buffer reached JavaScript, stale heap bytes leaked out.

The fix replaces js_pod_malloc with js_pod_calloc, guaranteeing the buffer is zero-initialized at allocation, so any unwritten region reads back as zero rather than as residual heap data. The accompanying header comment was updated to state that contents are now zero-initialised, correcting the previous contract that assumed callers always fully wrote the buffer.

Key insight
The single mistake was allocating a script-exposable audio buffer with uninitialized js_pod_malloc while relying on callers to fully overwrite it before it reached JavaScript; switching to zero-filling js_pod_calloc removes the dependency on that unreliable assumption and closes the uninitialized-memory disclosure.

Attack Path

  1. Trigger buffer allocation Malicious web content uses the Web Audio API in a way that causes ThreadSharedFloatArrayBufferList::Create to allocate one or more channel buffers.
  2. Leave region unwritten Arrange for a code path where not every float element of a channel buffer is written through GetDataForWrite() before the buffer is finalized.
  3. Transfer to JavaScript The buffer is wrapped via JS::NewArrayBufferWithContents, surfacing its raw bytes as an ArrayBuffer accessible to script.
  4. Read back stale memory Script reads the Float32Array view and recovers uninitialized heap bytes that were never overwritten.

Impact Assessment

An attacker running JavaScript in a content process gains a heap information-disclosure primitive, reading residual bytes from previously used or adjacent allocations as float values. This runs in the browser’s content process under the origin of the malicious page, requiring only that the victim load attacker-controlled content that exercises the affected Web Audio path. The leaked data could include sensitive values (e.g., pointers or secrets) useful for defeating ASLR or bootstrapping a further exploit.

Changed Functions

FunctionChangeNotes
for
dom/media/webaudio/AudioNodeEngine.cpp
modified
if
dom/media/webaudio/AudioNodeEngine.cpp
modified

Files Changed

  • dom/media/webaudio/AudioNodeEngine.cpp
  • dom/media/webaudio/AudioNodeEngine.h

Audit Directions

  • Uninitialized script-visible buffers
    Audit every allocation that feeds JS::NewArrayBufferWithContents or other JavaScript-exposed memory for use of js_pod_malloc/malloc without a guaranteed full overwrite before exposure.
  • Caller-fully-writes assumptions
    Flag APIs whose safety depends on callers writing every element via a helper like GetDataForWrite(); prefer zero-initializing allocation so a missed write degrades to zeros rather than a disclosure.
  • Media buffer boundaries
    Review Web Audio and other media components for length/boundary mismatches where the written region can be shorter than the allocated buffer handed to untrusted code.
diff --git a/dom/media/webaudio/AudioNodeEngine.cpp b/dom/media/webaudio/AudioNodeEngine.cpp
index eb80304c5be..1ff03bbfca7 100644
--- a/dom/media/webaudio/AudioNodeEngine.cpp
+++ b/dom/media/webaudio/AudioNodeEngine.cpp
@@ -29,7 +29,7 @@ ThreadSharedFloatArrayBufferList::Create(uint32_t aChannelCount, size_t aLength,
       new ThreadSharedFloatArrayBufferList(aChannelCount);
 
   for (uint32_t i = 0; i < aChannelCount; ++i) {
-    float* channelData = js_pod_malloc<float>(aLength);
+    float* channelData = js_pod_calloc<float>(aLength);
     if (!channelData) {
       return nullptr;
     }
diff --git a/dom/media/webaudio/AudioNodeEngine.h b/dom/media/webaudio/AudioNodeEngine.h
index de7785c086b..4f5af79362c 100644
--- a/dom/media/webaudio/AudioNodeEngine.h
+++ b/dom/media/webaudio/AudioNodeEngine.h
@@ -42,8 +42,8 @@ class ThreadSharedFloatArrayBufferList final : public ThreadSharedObject {
   }
   /**
    * Create with buffers suitable for transfer to
-   * JS::NewArrayBufferWithContents().  The buffer contents are uninitialized
-   * and so should be set using GetDataForWrite().
+   * JS::NewArrayBufferWithContents().  The buffer contents are
+   * zero-initialised; callers should set them using GetDataForWrite().
    */
   static already_AddRefed<ThreadSharedFloatArrayBufferList> Create(
       uint32_t aChannelCount, size_t aLength, const mozilla::fallible_t&);
Loading diff…