Heap Buffer Overflow Write in SoundTouch Rate Transposer via Unchecked setPitchOctaves/setRate
SoundTouch's rate transposer contains a heap-buffer-overflow write vulnerability triggered through the public setPitchOctaves(), setPitch(), setRate(), or setPitchSemiTones() APIs. The root cause is a absence of input validation in TransposerBase::setRate() at RateTransposer.cpp:284, combined with an unprotected integer cast in the transpose() buffer sizing calculation. When rate is set to zero, near-zero, negative, or NaN, the (int)((double)numSrcSamples / rate) + 8 expression overflows, producing a truncated sizeDemand that allocates an undersized output buffer. The interpolation loop then writes attacker-influenced sample data past the buffer end indefinitely.
The bug was identified while fuzzing Firefox, where the bundled SoundTouch copy was updated via commit 83af3e64b68b. Firefox is not currently exploitable due to an 8-channel cap and RLBox wasm2c sandboxing.
No Rate Validation
TransposerBase::setRate() stores the caller-supplied rate with zero validation. No MIN_RATE or MAX_RATE constants exist in the SoundTouch codebase:
// RateTransposer.cpp:284-287
void TransposerBase::setRate(double newRate)
{
rate = newRate; // no validation
}The public API surface that reaches this code path through SoundTouch::calcEffectiveRateAndTempo() (at SoundTouch.cpp:224, computing rate = virtualPitch * virtualRate) includes setPitchOctaves(), setPitch(), setRate(), and setPitchSemiTones(). None of them validate the resulting effective rate before it reaches the transposer.
Integer Overflow in sizeDemand
TransposerBase::transpose() computes the output buffer size demand:
// RateTransposer.cpp:236-242
int TransposerBase::transpose(FIFOSampleBuffer &dest, FIFOSampleBuffer &src)
{
int numSrcSamples = src.numSamples();
int sizeDemand = (int)((double)numSrcSamples / rate) + 8;
SAMPLETYPE *pdest = dest.ptrEnd(sizeDemand);
// ... interpolation loop writes to pdest
}When rate is extremely small (9.5e-7 from setPitchOctaves(-20)), the division numSrcSamples / rate produces a value exceeding INT_MAX (~2.1 billion). The (int) cast of an out-of-range double is undefined behavior per the C++ standard. On x86-64, the cvttsd2si instruction returns 0x80000000 (INT_MIN) for out-of-range inputs, so sizeDemand wraps to a small or negative value. ptrEnd((uint)sizeDemand) calls ensureCapacity(), which allocates a buffer far too small for the actual output.
When rate = 0, the division produces +inf (IEEE 754), and (int)(+inf) is the same undefined behavior, same overflow path.
Unbounded Write Loop
The interpolation loop in InterpolateCubic::transposeMono() at InterpolateCubic.cpp:70-98 advances through source samples by adding rate to a fractional accumulator each iteration:
while (srcCount < srcSampleEnd)
{
// ... cubic interpolation math ...
pdest[i] = (SAMPLETYPE)out; // WRITE past buffer end
i++;
fract += rate; // rate ≈ 0 → fract never reaches 1.0
int whole = (int)fract;
fract -= whole;
psrc += whole; // whole = 0 → psrc stuck
srcCount += whole; // srcCount stuck → loop never terminates
}With rate near zero, fract never accumulates enough to produce a nonzero whole, so srcCount never advances toward srcSampleEnd. The loop writes one float (4 bytes) per iteration into pdest[i++] past the allocated buffer, overwriting heap metadata and adjacent heap objects. With rate = 0 exactly, the loop is infinite, it will write until the process hits unmapped memory or is killed.
All identified standalone applications which were identified as vulnerable will not be disclosed in this report. GStreamer was not affected as GStreamer's pitch element wraps SoundTouch. The gstpitch.cc plugin bounds pitch/rate/tempo properties via g_param_spec_float to [0.1, 10.0].
Fix
Fixed in commit f738b1132ec1fd56efc90367898244cf52d9e6a5 ("Add sanity check to rate value. Clear buffers when change nr of channels") on 2026-04-19, modifying RateTransposer.cpp to add rate bounds checking.