CVE-2024-40779
Overview
Background
- Linear interpolation resampling
- Producing an output sample by blending two adjacent source frames (readIndex and readIndex2) weighted by the fractional playback position, used when the playback rate is not a whole number.
- Reverse playback
- A negative playbackRate that walks the source buffer backward, which reverses the direction readIndex moves and stresses the boundary-wrap logic.
- readIndex / readIndex2
- The two integer source-frame indices bracketing the current fractional read position in the interpolation branch.
- bufferLength / maxFrame
- The number of valid frames in the source channel; indices must remain strictly below it to be in bounds.
- Out-of-bounds read
- Reading memory beyond the allocated source channel array, here dereferencing source[readIndex] with an index that escaped the valid frame range.
Root Cause Analysis
This is an out-of-bounds read in the interpolating (resampling) branch of AudioBufferSourceNode::renderFromBuffer in Source/WebCore/Modules/webaudio/AudioBufferSourceNode.cpp. That branch performs linear interpolation between two adjacent source frames, readIndex and readIndex2, when the playback rate is non-integral or reversed. Just above the patched lines the code already conditionally rewinds/wraps readIndex2 (if (readIndex2 >= maxFrame) readIndex2 = m_isLooping ? minFrame : readIndex;), showing the intended invariant that both interpolation indices must stay within the buffer’s frame range. The invariant was not fully enforced: under certain rate/offset combinations readIndex (and/or readIndex2) could still be at or beyond bufferLength when the interpolation loop dereferences source[readIndex] and source[readIndex2], producing an OOB read of the source channel data. The added layout test triggers it with a reverse playback (playbackRate.value = -1) grain started at a fractional offset (start(undefined, 0.5)) on an 8192-frame buffer, i.e. the resampler runs backward and the computed indices fall outside the valid window.
The fix inserts a final in-loop guard — if (readIndex >= bufferLength || readIndex2 >= bufferLength) break; — that bails out of the interpolation loop before any out-of-bounds dereference; the accompanying FIXME comment explicitly frames it as a last-ditch sanity check to be later replaced by pre-loop assertions/guards. This restores the invariant that both interpolation indices are valid before the source buffer is read. Note the diff shows only the guard being added, not the full computation of readIndex/readIndex2/maxFrame/minFrame, so the exact arithmetic that let the index escape (interaction of reverse rate, fractional start offset, and virtualReadIndex advancement) is inferred from the surrounding wrap logic and the reverse-playback trigger rather than shown line-by-line.
Attack Path
- Set up context and buffer source
Attacker web content creates
new AudioContext()andnew AudioBufferSourceNode(ctx), and assigns a buffer viactx.createBuffer(1, 8192, 44100). - Start the grain at a fractional offset
Call
src.start(undefined, 0.5)so playback begins at a non-frame-aligned offset, forcing the interpolating (resampling) code path rather than a plain integer copy. - Reverse the playback rate
Set
src.playbackRate.value = -1so rendering proceeds in reverse, driving the readIndex/readIndex2 computation toward the buffer boundary in a way the pre-patch wrap logic did not fully cover. - Connect to force rendering
src.connect(ctx.destination, 0, 0)schedules the node so the audio thread calls renderFromBuffer and enters the linear-interpolation loop. - Trigger the OOB read
The loop dereferences
source[readIndex]/source[readIndex2]with an index at/beyond bufferLength, reading past the channel buffer. The observable result is an out-of-bounds read leading to an unexpected process crash.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
AudioBufferSourceNode::renderFromBufferSource/WebCore/Modules/webaudio/AudioBufferSourceNode.cpp |
modified | Adds a final in-loop bounds check in the linear-interpolation branch — `if (readIndex >= bufferLength || readIndex2 >= bufferLength) break;` — before the per-channel source reads, preventing OOB access when reverse/fractional playback pushes an index past the buffer; a FIXME notes it should later be hoisted to pre-loop guards. |
Audit Directions
- Same branch, both indices and all wrap pathsRe-examine renderFromBuffer’s interpolation branch: verify the new
breakcovers every dereference and that the earlierreadIndex2 = m_isLooping ? minFrame : readIndex;reassignment can’t leave readIndex itself out of range; grep forreadIndex2,maxFrame,minFrame,m_isLooping. - Reverse and looping grain mathAudit the reverse-playback and looping code paths in the webaudio module for index computations that assume forward motion; grep for
reverse,playbackRate,floorf, and negative-rate handling where virtualReadIndex is advanced. - Resamplers indexing without pre-loop boundsAcross WebCore audio DSP (e.g. AudioResampler, SincResampler, multichannel resamplers), grep for interpolation loops that read
source[index]/source[index+1]and confirm a bounds check exists before the loop rather than relying on inner-loop patches.