CVE-2026-12292
Overview
Background
- Web Audio API
- the browser subsystem that lets untrusted web content synthesize, process, and read back audio sample data as
floatarrays. - `ThreadSharedFloatArrayBufferList`
- a reference-counted container holding per-channel
floatbuffers that are shared across the audio thread and can be transferred into JavaScript-visibleArrayBuffers. - `js_pod_malloc` vs `js_pod_calloc`
- allocator helpers where
js_pod_mallocreturns uninitialized memory whilejs_pod_callocreturns 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.
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
- Trigger buffer allocation
Malicious web content uses the Web Audio API in a way that causes
ThreadSharedFloatArrayBufferList::Createto allocate one or more channel buffers. - Leave region unwritten
Arrange for a code path where not every
floatelement of a channel buffer is written throughGetDataForWrite()before the buffer is finalized. - Transfer to JavaScript
The buffer is wrapped via
JS::NewArrayBufferWithContents, surfacing its raw bytes as anArrayBufferaccessible to script. - Read back stale memory
Script reads the
Float32Arrayview and recovers uninitialized heap bytes that were never overwritten.
Impact Assessment
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
| Function | Change | Notes |
|---|---|---|
fordom/media/webaudio/AudioNodeEngine.cpp |
modified | |
ifdom/media/webaudio/AudioNodeEngine.cpp |
modified |
Files Changed
dom/media/webaudio/AudioNodeEngine.cppdom/media/webaudio/AudioNodeEngine.h
Audit Directions
- Uninitialized script-visible buffersAudit every allocation that feeds
JS::NewArrayBufferWithContentsor other JavaScript-exposed memory for use ofjs_pod_malloc/mallocwithout a guaranteed full overwrite before exposure. - Caller-fully-writes assumptionsFlag 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 boundariesReview 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.
Patch
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&);