SF bug 482574: audioop.ratecv crashes.
Bugfix candidate.
A numerically naive computation of output buffer size caused crashes
and spurious MemoryErrors for reasonable arguments.
audioop_ratecv(): Avoid spurious overflow by careful reworking of the
buffer size computations, triggering MemoryError if and only if the
final buffer size can't be represented in a C int (although
PyString_FromStringAndSize may legitimately raise MemoryError even if
it does fit in a C int). All reasonable arguments should work as
intended now, and all unreasonable arguments should be cuaght.
diff --git a/Modules/audioop.c b/Modules/audioop.c
index d12642a..014631c 100644
--- a/Modules/audioop.c
+++ b/Modules/audioop.c
@@ -2,6 +2,7 @@
/* audioopmodule - Module to detect peak values in arrays */
#include "Python.h"
+#include <math.h>
#if SIZEOF_INT == 4
typedef int Py_Int32;
@@ -902,6 +903,7 @@
int len, size, nchannels, inrate, outrate, weightA, weightB;
int chan, d, *prev_i, *cur_i, cur_o;
PyObject *state, *samps, *str, *rv = NULL;
+ int size_times_nchannels;
weightA = 1;
weightB = 0;
@@ -936,17 +938,28 @@
prev_i = (int *) malloc(nchannels * sizeof(int));
cur_i = (int *) malloc(nchannels * sizeof(int));
- len /= size * nchannels; /* # of frames */
if (prev_i == NULL || cur_i == NULL) {
(void) PyErr_NoMemory();
goto exit;
}
+ size_times_nchannels = size * nchannels;
+ if (size_times_nchannels / nchannels != size) {
+ /* This overflow test is rigorously correct because
+ both multiplicands are >= 1. Use the argument names
+ from the docs for the error msg. */
+ PyErr_SetString(PyExc_OverflowError,
+ "width * nchannels too big for a C int");
+ goto exit;
+ }
+ len /= size_times_nchannels; /* # of frames */
+
if (state == Py_None) {
d = -outrate;
for (chan = 0; chan < nchannels; chan++)
prev_i[chan] = cur_i[chan] = 0;
- } else {
+ }
+ else {
if (!PyArg_ParseTuple(state,
"iO!;audioop.ratecv: illegal state argument",
&d, &PyTuple_Type, &samps))
@@ -962,10 +975,53 @@
goto exit;
}
}
- str = PyString_FromStringAndSize(
- NULL, size * nchannels * (len * outrate + inrate - 1) / inrate);
- if (str == NULL)
- goto exit;
+
+ /* str <- Space for the output buffer. */
+ {
+ /* There are len input frames, so we need (mathematically)
+ ceiling(len*outrate/inrate) output frames, and each frame
+ requires size_times_nchannels bytes. Computing this
+ without spurious overflow is the challenge. */
+ int ceiling; /* the number of output frames, eventually */
+ int nbytes; /* the number of output bytes needed */
+ int q = len / inrate;
+ int r = len - q * inrate;
+ /* Now len = q * inrate + r exactly, so
+ len*outrate/inrate =
+ (q*inrate+r)*outrate/inrate =
+ (q*inrate*outrate + r*outrate)/inrate =
+ q*outrate + r*outrate/inrate exactly.
+ q*outrate is an exact integer, so the ceiling we're after is
+ q*outrate + ceiling(r*outrate/inrate). */
+ ceiling = q * outrate;
+ if (ceiling / outrate != q) {
+ PyErr_SetString(PyExc_MemoryError,
+ "not enough memory for output buffer");
+ goto exit;
+ }
+ /* Since r = len % inrate, in particular r < inrate. So
+ r * outrate / inrate = (r / inrate) * outrate < outrate,
+ so ceiling(r * outrate / inrate) <= outrate: the final
+ result fits in an int -- it can't overflow. */
+ assert(r < inrate);
+ q = (int)ceil((double)r * (double)outrate / (double)inrate);
+ assert(q <= outrate);
+ ceiling += q;
+ if (ceiling < 0) {
+ PyErr_SetString(PyExc_MemoryError,
+ "not enough memory for output buffer");
+ goto exit;
+ }
+ nbytes = ceiling * size_times_nchannels;
+ if (nbytes / size_times_nchannels != ceiling) {
+ PyErr_SetString(PyExc_MemoryError,
+ "not enough memory for output buffer");
+ goto exit;
+ }
+ str = PyString_FromStringAndSize(NULL, nbytes);
+ if (str == NULL)
+ goto exit;
+ }
ncp = PyString_AsString(str);
for (;;) {