CVE-2026-20652
Overview
Background
- Audio resampler kernel
- The per-channel core of WebCore’s sample-rate converter that pulls source frames and produces output frames at a different rate, tracking how much source data is buffered between calls.
- m_fillIndex / endIndex
- m_fillIndex is the count of source frames already buffered from a prior call; endIndex is the highest source frame index needed for the current output block, so 1 + endIndex - m_fillIndex is the additional frames to fetch.
- Unsigned integer underflow
- Subtracting a larger unsigned value from a smaller one wraps around to a huge value near the type maximum instead of producing a negative number, since size_t cannot represent negatives.
- WTF::safeSub
- A WebKit checked-arithmetic helper that performs a subtraction and returns false (writing nothing usable) if the operation overflows/underflows, letting callers bail out safely.
- std::span
- A non-owning (pointer, length) view over contiguous memory; an incorrectly large length lets consumers read past the backing buffer’s bounds.
Root Cause Analysis
AudioResamplerKernel::getSourceSpan() computes how many input (source) frames it must read to produce a requested block of output frames, then returns a std::span<float> over the source buffer describing that region. The frame count was computed as ‘size_t framesNeeded = 1 + endIndex - m_fillIndex;’, where endIndex is the highest source index needed for this output block and m_fillIndex is the number of source frames already buffered from the previous call (the ‘+1’ accounts for filling up to and including endIndex). This arithmetic silently assumes the invariant 1 + endIndex >= m_fillIndex. If that assumption is violated — i.e. m_fillIndex exceeds 1 + endIndex, which can arise from a crafted combination of resampling rate/scale factor, buffer state, and framesToProcess — the subtraction underflows. Because framesNeeded is an unsigned size_t, the underflow wraps to a near-SIZE_MAX value rather than going negative. That gigantic framesNeeded is then written through *numberOfSourceFramesNeededP and used to size/derive the returned source span, so downstream consumers read far past the end of the actual source buffer: an out-of-bounds read whose length is effectively unbounded, producing a crash (denial of service, as the CVE states).
The fix adds <wtf/CheckedArithmetic.h> and replaces the raw subtraction with WTF::safeSub(1 + endIndex, m_fillIndex, framesNeeded); if the checked subtraction underflows it returns false and getSourceSpan() returns an empty span (‘return { };’) instead of propagating a wrapped length. This restores the invariant by refusing to produce a source span (and refusing to report a frame count) when the computed need would be negative, converting a would-be OOB access into a benign empty-span early return. The precise upstream conditions that let m_fillIndex exceed endIndex+1 are not in the shown diff (only getSourceSpan is changed), so the exact trigger state is an inference from the arithmetic and the resampler’s buffering model.
Attack Path
- Reach the resampler from web content Drive audio through a path that instantiates an AudioResampler — e.g. a Web Audio graph or media element whose source sample rate differs from the output, requiring sample-rate conversion — so AudioResamplerKernel::getSourceSpan() is invoked during rendering.
- Control resampling parameters and buffering Choose a playback/resampling ratio (scale factor) and feed frame counts such that the internally tracked m_fillIndex becomes larger than 1 + endIndex for a processing block, e.g. via an extreme or dynamically changed rate.
- Trigger the underflow On the next getSourceSpan() call the unsigned subtraction 1 + endIndex - m_fillIndex wraps to a near-SIZE_MAX framesNeeded.
- Force the OOB read The oversized framesNeeded is used to size the returned source span (and reported via numberOfSourceFramesNeededP), so the resampling loop reads far beyond the source buffer, dereferencing unmapped/adjacent memory and crashing the process.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
AudioResamplerKernel::getSourceSpanSource/WebCore/platform/audio/AudioResamplerKernel.cpp |
modified | Replaced 'size_t framesNeeded = 1 + endIndex - m_fillIndex;' with WTF::safeSub(1 + endIndex, m_fillIndex, framesNeeded) and an early 'return { };' on underflow, preventing an unsigned wraparound from producing an oversized source span. |
Files Changed
Source/WebCore/platform/audio/AudioResamplerKernel.cpp
Audit Directions
- Other index arithmetic in the same file/classReview the rest of AudioResamplerKernel.cpp and AudioResampler for other size_t index/offset computations (m_fillIndex, endIndex, frame counts) that subtract or add without CheckedArithmetic, especially any that also feed span sizes or numberOfSourceFramesNeeded.
- Unchecked subtraction feeding span/buffer lengthsGrep the platform/audio tree for patterns like ‘size_t <name> = … - …;’ whose result is used as a length, span size, or loop bound, and confirm each is either provably non-negative or wrapped in WTF::safeSub / checkedSum-style helpers.
- Codebase-wide length-from-difference patternMore broadly, search for span/buffer constructions where the length is computed as ’end - start’ or ‘1 + end - fill’ on unsigned types (std::span<…>, makeSpan, subspan, memcpy sizes) and audit for missing underflow checks, using #include <wtf/CheckedArithmetic.h> additions in recent commits as a lead for known-fragile call sites.