Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-92888: Fix memoryview bad __index__ use after free #92946

Merged
merged 12 commits into from
Jun 17, 2022
8 changes: 3 additions & 5 deletions Lib/test/test_memoryview.py
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,9 @@ def test_pickle(self):
pickle.dumps(m, proto)

def test_use_released_memory(self):
Fidget-Spinner marked this conversation as resolved.
Show resolved Hide resolved
# gh-92888: Previously it was possible to use a memoryview even after
# backing buffer is freed in certain cases. This tests that those
# cases raise an exception.
size = 128
def release():
m.release()
Expand All @@ -564,25 +567,20 @@ def __bool__(self):
release()
return True

ba = None
m = memoryview(bytearray(b'\xff'*size))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my PR, I tried to make the code more generic to test more cases: https://github.com/python/cpython/pull/93127/files#diff-d41c6bb40a1e03fea5a20d15c4077413e0ddde65651147922b625b03a66a2f16R399:

        tp = self.rw_type
        b = self.rw_type(self._source)
        view = self._view(b)

with self.assertRaises(ValueError):
m[MyIndex()]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is very long. Can you try to factorize similar code and use loop with subTest(), and put pack operations in one test method and unpack in another test method?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we will need to duplicate the definitions of internal classes.

The tested code is so different, that it is difficult to use a loop. And I think that the result will be more complicated.


ba = None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is necessary. The old bytearray should be deallocated before creating a new bytearray, so all bytearrays will be allocated at the same place.

m = memoryview(bytearray(b'\xff'*size))
self.assertEqual(list(m[:MyIndex()]), [255] * 4)

ba = None
m = memoryview(bytearray(b'\xff'*size))
self.assertEqual(list(m[MyIndex():8]), [255] * 4)

Fidget-Spinner marked this conversation as resolved.
Show resolved Hide resolved
ba = None
m = memoryview(bytearray(b'\xff'*size)).cast('B', (64, 2))
with self.assertRaisesRegex(ValueError, "operation forbidden"):
m[MyIndex(), 0]

ba = None
m = memoryview(bytearray(b'\xff'*size)).cast('B', (2, 64))
with self.assertRaisesRegex(ValueError, "operation forbidden"):
m[0, MyIndex()]
Expand Down
29 changes: 16 additions & 13 deletions Objects/memoryobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@ PyTypeObject _PyManagedBuffer_Type = {
return -1; \
}

/* See gh-92888. These macros signal that we need to check the memoryview
again due to possible read after frees. */
#define CHECK_RELEASED_AGAIN(mv) CHECK_RELEASED(mv)
#define CHECK_RELEASED_INT_AGAIN(mv) CHECK_RELEASED_INT(mv)

#define CHECK_LIST_OR_TUPLE(v) \
if (!PyList_Check(v) && !PyTuple_Check(v)) { \
PyErr_SetString(PyExc_TypeError, \
Expand Down Expand Up @@ -383,7 +388,7 @@ copy_rec(const Py_ssize_t *shape, Py_ssize_t ndim, Py_ssize_t itemsize,
static int
copy_single(PyMemoryViewObject *self, const Py_buffer *dest, const Py_buffer *src)
{
CHECK_RELEASED_INT(self); /* See gh-92888 for why we need this here */
CHECK_RELEASED_INT_AGAIN(self);
char *mem = NULL;

assert(dest->ndim == 1);
Expand Down Expand Up @@ -1690,7 +1695,7 @@ unpack_single(PyMemoryViewObject *self, const char *ptr, const char *fmt)
unsigned char uc;
void *p;

CHECK_RELEASED(self); /* See gh-92888 for why we need this here */
CHECK_RELEASED_AGAIN(self);

switch (fmt[0]) {

Expand Down Expand Up @@ -1781,15 +1786,13 @@ pack_single(PyMemoryViewObject *self, char *ptr, PyObject *item, const char *fmt
double d;
void *p;

CHECK_RELEASED_INT(self); /* See gh-92888 for why we need this here */

switch (fmt[0]) {
/* signed integers */
case 'b': case 'h': case 'i': case 'l':
ld = pylong_as_ld(item);
if (ld == -1 && PyErr_Occurred())
goto err_occurred;
CHECK_RELEASED_INT(self); /* See gh-92888 for why we need this here */
CHECK_RELEASED_INT_AGAIN(self);
switch (fmt[0]) {
case 'b':
if (ld < SCHAR_MIN || ld > SCHAR_MAX) goto err_range;
Expand All @@ -1810,7 +1813,7 @@ pack_single(PyMemoryViewObject *self, char *ptr, PyObject *item, const char *fmt
lu = pylong_as_lu(item);
if (lu == (unsigned long)-1 && PyErr_Occurred())
goto err_occurred;
CHECK_RELEASED_INT(self); /* See gh-92888 for why we need this here */
CHECK_RELEASED_INT_AGAIN(self);
switch (fmt[0]) {
case 'B':
if (lu > UCHAR_MAX) goto err_range;
Expand All @@ -1831,14 +1834,14 @@ pack_single(PyMemoryViewObject *self, char *ptr, PyObject *item, const char *fmt
lld = pylong_as_lld(item);
if (lld == -1 && PyErr_Occurred())
goto err_occurred;
CHECK_RELEASED_INT(self); /* See gh-92888 for why we need this here */
CHECK_RELEASED_INT_AGAIN(self);
PACK_SINGLE(ptr, lld, long long);
break;
case 'Q':
llu = pylong_as_llu(item);
if (llu == (unsigned long long)-1 && PyErr_Occurred())
goto err_occurred;
CHECK_RELEASED_INT(self); /* See gh-92888 for why we need this here */
CHECK_RELEASED_INT_AGAIN(self);
PACK_SINGLE(ptr, llu, unsigned long long);
break;

Expand All @@ -1847,14 +1850,14 @@ pack_single(PyMemoryViewObject *self, char *ptr, PyObject *item, const char *fmt
zd = pylong_as_zd(item);
if (zd == -1 && PyErr_Occurred())
goto err_occurred;
CHECK_RELEASED_INT(self); /* See gh-92888 for why we need this here */
CHECK_RELEASED_INT_AGAIN(self);
PACK_SINGLE(ptr, zd, Py_ssize_t);
break;
case 'N':
zu = pylong_as_zu(item);
if (zu == (size_t)-1 && PyErr_Occurred())
goto err_occurred;
CHECK_RELEASED_INT(self); /* See gh-92888 for why we need this here */
CHECK_RELEASED_INT_AGAIN(self);
PACK_SINGLE(ptr, zu, size_t);
break;

Expand All @@ -1863,7 +1866,7 @@ pack_single(PyMemoryViewObject *self, char *ptr, PyObject *item, const char *fmt
d = PyFloat_AsDouble(item);
if (d == -1.0 && PyErr_Occurred())
goto err_occurred;
CHECK_RELEASED_INT(self); /* See gh-92888 for why we need this here */
CHECK_RELEASED_INT_AGAIN(self);
if (fmt[0] == 'f') {
PACK_SINGLE(ptr, d, float);
}
Expand All @@ -1877,7 +1880,7 @@ pack_single(PyMemoryViewObject *self, char *ptr, PyObject *item, const char *fmt
ld = PyObject_IsTrue(item);
if (ld < 0)
return -1; /* preserve original error */
CHECK_RELEASED_INT(self); /* See gh-92888 for why we need this here */
CHECK_RELEASED_INT_AGAIN(self);
PACK_SINGLE(ptr, ld, _Bool);
break;

Expand All @@ -1895,7 +1898,7 @@ pack_single(PyMemoryViewObject *self, char *ptr, PyObject *item, const char *fmt
p = PyLong_AsVoidPtr(item);
if (p == NULL && PyErr_Occurred())
goto err_occurred;
CHECK_RELEASED_INT(self); /* See gh-92888 for why we need this here */
CHECK_RELEASED_INT_AGAIN(self);
PACK_SINGLE(ptr, p, void *);
break;

Expand Down