CVE-2024-40776
Overview
Background
- MultiChannelResampler / AudioBus
- Audio resampling helper whose AudioBus holds per-channel sample buffers.
- setChannelMemory
- Points an AudioBus channel at a caller-provided memory block instead of owned storage — an aliasing optimization.
- AudioWorklet
- Runs author JS on the real-time audio rendering thread, driving processing concurrently with graph changes.
- Buffer aliasing
- Referencing externally-owned memory whose size/lifetime the referencing object does not control, risking stale/OOB access.
Root Cause Analysis
MultiChannelResampler resamples multi-channel audio. As an optimization it avoided allocating memory for the first channel by borrowing the caller-provided output buffer as the channel-0 backing store of its internal m_multiChannelBus: the constructor created the AudioBus without memory and only allocated AudioFloatArrays for channels 1..N-1 (m_channelsMemory), and process()/provideInputForChannel() called m_multiChannelBus->setChannelMemory(0, buffer.data(), framesToProcess) to point channel 0 at the caller’s transient buffer. That makes the AudioBus alias external memory whose lifetime and size are not owned by the resampler; when the resampler is driven from an AudioWorklet (author JS on the real-time audio thread) the borrowed buffer can be reused/freed or have a different length across the concurrent processing, so the AudioBus references stale or wrong-sized memory — a use-after-free / out-of-bounds on the audio channel data (the regression test is audioworklet-concurrent-resampler-crash).
The fix removes the borrowing optimization: the AudioBus is created with its own owned memory (AudioBus::create(numberOfChannels, requestFrames)), the separate m_channelsMemory vector is deleted, and process() no longer aliases the caller’s buffer for channel 0 (the ASSERT is relaxed from == to <= length).
The restored invariant is that the resampler’s AudioBus owns all its channel memory, so no channel points at externally-owned, potentially-stale buffers.
Attack Path
- Set up an AudioWorklet resampling graph Create a WebAudio graph that drives a MultiChannelResampler with author code running in an AudioWorkletProcessor on the audio thread.
- Trigger channel-0 buffer borrowing Processing points the internal AudioBus’s channel 0 at the caller-provided transient buffer instead of owned memory.
- Invalidate the borrowed buffer Concurrent/re-entrant worklet processing reuses, resizes, or frees that buffer while the AudioBus still references it.
- Use-after-free / OOB The resampler reads/writes channel 0 through the stale external pointer, corrupting memory in the WebContent process.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
MultiChannelResampler::MultiChannelResamplerSource/WebCore/platform/audio/MultiChannelResampler.cpp |
modified | Creates m_multiChannelBus with owned memory (AudioBus::create(numberOfChannels, requestFrames)) and drops the m_channelsMemory borrowing optimization. |
MultiChannelResampler::processSource/WebCore/platform/audio/MultiChannelResampler.cpp |
modified | No longer aliases the caller's buffer as channel-0 memory via setChannelMemory; relaxes the length ASSERT to <=. |
MultiChannelResampler::m_channelsMemory (removed)Source/WebCore/platform/audio/MultiChannelResampler.h |
modified | Removes the externally-borrowed channel-memory vector and the AudioArray include. |
Audit Directions
- Other setChannelMemory borrowersgrep platform/audio for setChannelMemory / external buffer aliasing where the AudioBus outlives or races the borrowed buffer.
- AudioWorklet-thread lifetimesAudit audio DSP objects driven from AudioWorklet for buffers borrowed across process() calls.
- Length assumptionsReview resampler/bus code assuming framesToProcess exactly equals a fixed length rather than <=.