Skip to content

Commit 2eff55c

Browse files
gh-92888: Fix memoryview bad __index__ use after free (GH-92946)
Co-authored-by: chilaxan <35645806+chilaxan@users.noreply.github.com> Co-authored-by: Serhiy Storchaka <3659035+serhiy-storchaka@users.noreply.github.com> (cherry picked from commit 11190c4) Co-authored-by: Ken Jin <kenjin@python.org>
1 parent 6c8eb95 commit 2eff55c

File tree

3 files changed

+139
-19
lines changed

3 files changed

+139
-19
lines changed

Lib/test/test_memoryview.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,107 @@ def test_pickle(self):
545545
with self.assertRaises(TypeError):
546546
pickle.dumps(m, proto)
547547

548+
def test_use_released_memory(self):
549+
# gh-92888: Previously it was possible to use a memoryview even after
550+
# backing buffer is freed in certain cases. This tests that those
551+
# cases raise an exception.
552+
size = 128
553+
def release():
554+
m.release()
555+
nonlocal ba
556+
ba = bytearray(size)
557+
class MyIndex:
558+
def __index__(self):
559+
release()
560+
return 4
561+
class MyFloat:
562+
def __float__(self):
563+
release()
564+
return 4.25
565+
class MyBool:
566+
def __bool__(self):
567+
release()
568+
return True
569+
570+
ba = None
571+
m = memoryview(bytearray(b'\xff'*size))
572+
with self.assertRaises(ValueError):
573+
m[MyIndex()]
574+
575+
ba = None
576+
m = memoryview(bytearray(b'\xff'*size))
577+
self.assertEqual(list(m[:MyIndex()]), [255] * 4)
578+
579+
ba = None
580+
m = memoryview(bytearray(b'\xff'*size))
581+
self.assertEqual(list(m[MyIndex():8]), [255] * 4)
582+
583+
ba = None
584+
m = memoryview(bytearray(b'\xff'*size)).cast('B', (64, 2))
585+
with self.assertRaisesRegex(ValueError, "operation forbidden"):
586+
m[MyIndex(), 0]
587+
588+
ba = None
589+
m = memoryview(bytearray(b'\xff'*size)).cast('B', (2, 64))
590+
with self.assertRaisesRegex(ValueError, "operation forbidden"):
591+
m[0, MyIndex()]
592+
593+
ba = None
594+
m = memoryview(bytearray(b'\xff'*size))
595+
with self.assertRaisesRegex(ValueError, "operation forbidden"):
596+
m[MyIndex()] = 42
597+
self.assertEqual(ba[:8], b'\0'*8)
598+
599+
ba = None
600+
m = memoryview(bytearray(b'\xff'*size))
601+
with self.assertRaisesRegex(ValueError, "operation forbidden"):
602+
m[:MyIndex()] = b'spam'
603+
self.assertEqual(ba[:8], b'\0'*8)
604+
605+
ba = None
606+
m = memoryview(bytearray(b'\xff'*size))
607+
with self.assertRaisesRegex(ValueError, "operation forbidden"):
608+
m[MyIndex():8] = b'spam'
609+
self.assertEqual(ba[:8], b'\0'*8)
610+
611+
ba = None
612+
m = memoryview(bytearray(b'\xff'*size)).cast('B', (64, 2))
613+
with self.assertRaisesRegex(ValueError, "operation forbidden"):
614+
m[MyIndex(), 0] = 42
615+
self.assertEqual(ba[8:16], b'\0'*8)
616+
ba = None
617+
m = memoryview(bytearray(b'\xff'*size)).cast('B', (2, 64))
618+
with self.assertRaisesRegex(ValueError, "operation forbidden"):
619+
m[0, MyIndex()] = 42
620+
self.assertEqual(ba[:8], b'\0'*8)
621+
622+
ba = None
623+
m = memoryview(bytearray(b'\xff'*size))
624+
with self.assertRaisesRegex(ValueError, "operation forbidden"):
625+
m[0] = MyIndex()
626+
self.assertEqual(ba[:8], b'\0'*8)
627+
628+
for fmt in 'bhilqnBHILQN':
629+
with self.subTest(fmt=fmt):
630+
ba = None
631+
m = memoryview(bytearray(b'\xff'*size)).cast(fmt)
632+
with self.assertRaisesRegex(ValueError, "operation forbidden"):
633+
m[0] = MyIndex()
634+
self.assertEqual(ba[:8], b'\0'*8)
635+
636+
for fmt in 'fd':
637+
with self.subTest(fmt=fmt):
638+
ba = None
639+
m = memoryview(bytearray(b'\xff'*size)).cast(fmt)
640+
with self.assertRaisesRegex(ValueError, "operation forbidden"):
641+
m[0] = MyFloat()
642+
self.assertEqual(ba[:8], b'\0'*8)
643+
644+
ba = None
645+
m = memoryview(bytearray(b'\xff'*size)).cast('?')
646+
with self.assertRaisesRegex(ValueError, "operation forbidden"):
647+
m[0] = MyBool()
648+
self.assertEqual(ba[:8], b'\0'*8)
548649

549650
if __name__ == "__main__":
550651
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix ``memoryview`` use after free when accessing the backing buffer in certain cases.
2+

Objects/memoryobject.c

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,11 @@ PyTypeObject _PyManagedBuffer_Type = {
193193
return -1; \
194194
}
195195

196+
/* See gh-92888. These macros signal that we need to check the memoryview
197+
again due to possible read after frees. */
198+
#define CHECK_RELEASED_AGAIN(mv) CHECK_RELEASED(mv)
199+
#define CHECK_RELEASED_INT_AGAIN(mv) CHECK_RELEASED_INT(mv)
200+
196201
#define CHECK_LIST_OR_TUPLE(v) \
197202
if (!PyList_Check(v) && !PyTuple_Check(v)) { \
198203
PyErr_SetString(PyExc_TypeError, \
@@ -381,8 +386,9 @@ copy_rec(const Py_ssize_t *shape, Py_ssize_t ndim, Py_ssize_t itemsize,
381386

382387
/* Faster copying of one-dimensional arrays. */
383388
static int
384-
copy_single(const Py_buffer *dest, const Py_buffer *src)
389+
copy_single(PyMemoryViewObject *self, const Py_buffer *dest, const Py_buffer *src)
385390
{
391+
CHECK_RELEASED_INT_AGAIN(self);
386392
char *mem = NULL;
387393

388394
assert(dest->ndim == 1);
@@ -1677,7 +1683,7 @@ pylong_as_zu(PyObject *item)
16771683
module syntax. This function is very sensitive to small changes. With this
16781684
layout gcc automatically generates a fast jump table. */
16791685
static inline PyObject *
1680-
unpack_single(const char *ptr, const char *fmt)
1686+
unpack_single(PyMemoryViewObject *self, const char *ptr, const char *fmt)
16811687
{
16821688
unsigned long long llu;
16831689
unsigned long lu;
@@ -1689,6 +1695,8 @@ unpack_single(const char *ptr, const char *fmt)
16891695
unsigned char uc;
16901696
void *p;
16911697

1698+
CHECK_RELEASED_AGAIN(self);
1699+
16921700
switch (fmt[0]) {
16931701

16941702
/* signed integers and fast path for 'B' */
@@ -1767,7 +1775,7 @@ unpack_single(const char *ptr, const char *fmt)
17671775
/* Pack a single item. 'fmt' can be any native format character in
17681776
struct module syntax. */
17691777
static int
1770-
pack_single(char *ptr, PyObject *item, const char *fmt)
1778+
pack_single(PyMemoryViewObject *self, char *ptr, PyObject *item, const char *fmt)
17711779
{
17721780
unsigned long long llu;
17731781
unsigned long lu;
@@ -1784,6 +1792,7 @@ pack_single(char *ptr, PyObject *item, const char *fmt)
17841792
ld = pylong_as_ld(item);
17851793
if (ld == -1 && PyErr_Occurred())
17861794
goto err_occurred;
1795+
CHECK_RELEASED_INT_AGAIN(self);
17871796
switch (fmt[0]) {
17881797
case 'b':
17891798
if (ld < SCHAR_MIN || ld > SCHAR_MAX) goto err_range;
@@ -1804,6 +1813,7 @@ pack_single(char *ptr, PyObject *item, const char *fmt)
18041813
lu = pylong_as_lu(item);
18051814
if (lu == (unsigned long)-1 && PyErr_Occurred())
18061815
goto err_occurred;
1816+
CHECK_RELEASED_INT_AGAIN(self);
18071817
switch (fmt[0]) {
18081818
case 'B':
18091819
if (lu > UCHAR_MAX) goto err_range;
@@ -1824,12 +1834,14 @@ pack_single(char *ptr, PyObject *item, const char *fmt)
18241834
lld = pylong_as_lld(item);
18251835
if (lld == -1 && PyErr_Occurred())
18261836
goto err_occurred;
1837+
CHECK_RELEASED_INT_AGAIN(self);
18271838
PACK_SINGLE(ptr, lld, long long);
18281839
break;
18291840
case 'Q':
18301841
llu = pylong_as_llu(item);
18311842
if (llu == (unsigned long long)-1 && PyErr_Occurred())
18321843
goto err_occurred;
1844+
CHECK_RELEASED_INT_AGAIN(self);
18331845
PACK_SINGLE(ptr, llu, unsigned long long);
18341846
break;
18351847

@@ -1838,12 +1850,14 @@ pack_single(char *ptr, PyObject *item, const char *fmt)
18381850
zd = pylong_as_zd(item);
18391851
if (zd == -1 && PyErr_Occurred())
18401852
goto err_occurred;
1853+
CHECK_RELEASED_INT_AGAIN(self);
18411854
PACK_SINGLE(ptr, zd, Py_ssize_t);
18421855
break;
18431856
case 'N':
18441857
zu = pylong_as_zu(item);
18451858
if (zu == (size_t)-1 && PyErr_Occurred())
18461859
goto err_occurred;
1860+
CHECK_RELEASED_INT_AGAIN(self);
18471861
PACK_SINGLE(ptr, zu, size_t);
18481862
break;
18491863

@@ -1852,6 +1866,7 @@ pack_single(char *ptr, PyObject *item, const char *fmt)
18521866
d = PyFloat_AsDouble(item);
18531867
if (d == -1.0 && PyErr_Occurred())
18541868
goto err_occurred;
1869+
CHECK_RELEASED_INT_AGAIN(self);
18551870
if (fmt[0] == 'f') {
18561871
PACK_SINGLE(ptr, d, float);
18571872
}
@@ -1865,6 +1880,7 @@ pack_single(char *ptr, PyObject *item, const char *fmt)
18651880
ld = PyObject_IsTrue(item);
18661881
if (ld < 0)
18671882
return -1; /* preserve original error */
1883+
CHECK_RELEASED_INT_AGAIN(self);
18681884
PACK_SINGLE(ptr, ld, _Bool);
18691885
break;
18701886

@@ -1882,6 +1898,7 @@ pack_single(char *ptr, PyObject *item, const char *fmt)
18821898
p = PyLong_AsVoidPtr(item);
18831899
if (p == NULL && PyErr_Occurred())
18841900
goto err_occurred;
1901+
CHECK_RELEASED_INT_AGAIN(self);
18851902
PACK_SINGLE(ptr, p, void *);
18861903
break;
18871904

@@ -2048,7 +2065,7 @@ adjust_fmt(const Py_buffer *view)
20482065

20492066
/* Base case for multi-dimensional unpacking. Assumption: ndim == 1. */
20502067
static PyObject *
2051-
tolist_base(const char *ptr, const Py_ssize_t *shape,
2068+
tolist_base(PyMemoryViewObject *self, const char *ptr, const Py_ssize_t *shape,
20522069
const Py_ssize_t *strides, const Py_ssize_t *suboffsets,
20532070
const char *fmt)
20542071
{
@@ -2061,7 +2078,7 @@ tolist_base(const char *ptr, const Py_ssize_t *shape,
20612078

20622079
for (i = 0; i < shape[0]; ptr+=strides[0], i++) {
20632080
const char *xptr = ADJUST_PTR(ptr, suboffsets, 0);
2064-
item = unpack_single(xptr, fmt);
2081+
item = unpack_single(self, xptr, fmt);
20652082
if (item == NULL) {
20662083
Py_DECREF(lst);
20672084
return NULL;
@@ -2075,7 +2092,7 @@ tolist_base(const char *ptr, const Py_ssize_t *shape,
20752092
/* Unpack a multi-dimensional array into a nested list.
20762093
Assumption: ndim >= 1. */
20772094
static PyObject *
2078-
tolist_rec(const char *ptr, Py_ssize_t ndim, const Py_ssize_t *shape,
2095+
tolist_rec(PyMemoryViewObject *self, const char *ptr, Py_ssize_t ndim, const Py_ssize_t *shape,
20792096
const Py_ssize_t *strides, const Py_ssize_t *suboffsets,
20802097
const char *fmt)
20812098
{
@@ -2087,15 +2104,15 @@ tolist_rec(const char *ptr, Py_ssize_t ndim, const Py_ssize_t *shape,
20872104
assert(strides != NULL);
20882105

20892106
if (ndim == 1)
2090-
return tolist_base(ptr, shape, strides, suboffsets, fmt);
2107+
return tolist_base(self, ptr, shape, strides, suboffsets, fmt);
20912108

20922109
lst = PyList_New(shape[0]);
20932110
if (lst == NULL)
20942111
return NULL;
20952112

20962113
for (i = 0; i < shape[0]; ptr+=strides[0], i++) {
20972114
const char *xptr = ADJUST_PTR(ptr, suboffsets, 0);
2098-
item = tolist_rec(xptr, ndim-1, shape+1,
2115+
item = tolist_rec(self, xptr, ndim-1, shape+1,
20992116
strides+1, suboffsets ? suboffsets+1 : NULL,
21002117
fmt);
21012118
if (item == NULL) {
@@ -2129,15 +2146,15 @@ memoryview_tolist_impl(PyMemoryViewObject *self)
21292146
if (fmt == NULL)
21302147
return NULL;
21312148
if (view->ndim == 0) {
2132-
return unpack_single(view->buf, fmt);
2149+
return unpack_single(self, view->buf, fmt);
21332150
}
21342151
else if (view->ndim == 1) {
2135-
return tolist_base(view->buf, view->shape,
2152+
return tolist_base(self, view->buf, view->shape,
21362153
view->strides, view->suboffsets,
21372154
fmt);
21382155
}
21392156
else {
2140-
return tolist_rec(view->buf, view->ndim, view->shape,
2157+
return tolist_rec(self, view->buf, view->ndim, view->shape,
21412158
view->strides, view->suboffsets,
21422159
fmt);
21432160
}
@@ -2345,7 +2362,7 @@ memory_item(PyMemoryViewObject *self, Py_ssize_t index)
23452362
char *ptr = ptr_from_index(view, index);
23462363
if (ptr == NULL)
23472364
return NULL;
2348-
return unpack_single(ptr, fmt);
2365+
return unpack_single(self, ptr, fmt);
23492366
}
23502367

23512368
PyErr_SetString(PyExc_NotImplementedError,
@@ -2376,7 +2393,7 @@ memory_item_multi(PyMemoryViewObject *self, PyObject *tup)
23762393
ptr = ptr_from_tuple(view, tup);
23772394
if (ptr == NULL)
23782395
return NULL;
2379-
return unpack_single(ptr, fmt);
2396+
return unpack_single(self, ptr, fmt);
23802397
}
23812398

23822399
static inline int
@@ -2463,7 +2480,7 @@ memory_subscript(PyMemoryViewObject *self, PyObject *key)
24632480
const char *fmt = adjust_fmt(view);
24642481
if (fmt == NULL)
24652482
return NULL;
2466-
return unpack_single(view->buf, fmt);
2483+
return unpack_single(self, view->buf, fmt);
24672484
}
24682485
else if (key == Py_Ellipsis) {
24692486
Py_INCREF(self);
@@ -2538,7 +2555,7 @@ memory_ass_sub(PyMemoryViewObject *self, PyObject *key, PyObject *value)
25382555
if (key == Py_Ellipsis ||
25392556
(PyTuple_Check(key) && PyTuple_GET_SIZE(key)==0)) {
25402557
ptr = (char *)view->buf;
2541-
return pack_single(ptr, value, fmt);
2558+
return pack_single(self, ptr, value, fmt);
25422559
}
25432560
else {
25442561
PyErr_SetString(PyExc_TypeError,
@@ -2560,7 +2577,7 @@ memory_ass_sub(PyMemoryViewObject *self, PyObject *key, PyObject *value)
25602577
ptr = ptr_from_index(view, index);
25612578
if (ptr == NULL)
25622579
return -1;
2563-
return pack_single(ptr, value, fmt);
2580+
return pack_single(self, ptr, value, fmt);
25642581
}
25652582
/* one-dimensional: fast path */
25662583
if (PySlice_Check(key) && view->ndim == 1) {
@@ -2583,7 +2600,7 @@ memory_ass_sub(PyMemoryViewObject *self, PyObject *key, PyObject *value)
25832600
goto end_block;
25842601
dest.len = dest.shape[0] * dest.itemsize;
25852602

2586-
ret = copy_single(&dest, &src);
2603+
ret = copy_single(self, &dest, &src);
25872604

25882605
end_block:
25892606
PyBuffer_Release(&src);
@@ -2599,7 +2616,7 @@ memory_ass_sub(PyMemoryViewObject *self, PyObject *key, PyObject *value)
25992616
ptr = ptr_from_tuple(view, key);
26002617
if (ptr == NULL)
26012618
return -1;
2602-
return pack_single(ptr, value, fmt);
2619+
return pack_single(self, ptr, value, fmt);
26032620
}
26042621
if (PySlice_Check(key) || is_multislice(key)) {
26052622
/* Call memory_subscript() to produce a sliced lvalue, then copy
@@ -3200,7 +3217,7 @@ memoryiter_next(memoryiterobject *it)
32003217
if (ptr == NULL) {
32013218
return NULL;
32023219
}
3203-
return unpack_single(ptr, it->it_fmt);
3220+
return unpack_single(seq, ptr, it->it_fmt);
32043221
}
32053222

32063223
it->it_seq = NULL;

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy