You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Use-after-free in CallbackResampler.read() for mono 1-D input (samplerate 0.2.4) #41
Since #36 (06e88d1, released in 0.2.4), CallbackResampler.read() can return a NumPy array whose data pointer refers to freed memory. It happens when the callback supplies 1-D (mono) arrays and a read returns fewer frames than requested, which happens routinely at the end of every stream. The array is writeable, so writing to it corrupts whatever the allocator has since put there.
Cause
In read() (src/samplerate.cpp), for _channels == 1 && _buffer_ndim == 1:
output = py::array_t<float, py::array::c_style>(
out_shape, static_cast<float *>(outbuf.ptr)); // copies into a NEW array
...
if (output_frames_gen < frames) {
...
return py::array_t<float, py::array::c_style>(
out_shape, strides, static_cast<float *>(outbuf.ptr), output);
}
The first statement copies the 2-D buffer into a new 1-D array. The returned view then combines the pointer of the original buffer (outbuf.ptr) with the copy (output) as its base. Nothing keeps the original array alive once read() returns and outbuf is released, so the view dangles.
importnumpyasnp, sampleratex=np.arange(1, 168, dtype=np.float32)
chunks=iter([x, None])
withsamplerate.CallbackResampler(lambda: next(chunks), 0.9, "linear") ascb:
y=cb.read(1000) # short read: only ~150 frames availableprint(np.shares_memory(y, y.base)) # False: y's data is not inside its basejunk= [np.full(1000, 7.0, np.float32) for_inrange(50)]
print(y[:4]) # [7. 7. 7. 7.]: freed memory was reusedprint(samplerate.resample(x, 0.9, "linear")[:4]) # [1. 1.111 2.222 3.333]
Impact
Wrong audio data, possible crashes, and heap corruption if the caller writes to the returned array. Any code that reads a mono stream to completion with CallbackResampler is affected. It isn't directly attacker-controlled, but it is memory-unsafe behaviour reachable from ordinary API use.
A deterministic regression test: every truncated output must satisfy y.base is None or np.shares_memory(y, y.base). It fails on 0.2.4 and passes with the fix. We shipped this fix and test in the LedFx fork (samplerate-ledfx): LedFx#14
Disclosure note: that fork PR is public and describes the bug.
Activity