diff --git a/Lib/test/test_memoryview.py b/Lib/test/test_memoryview.py index 656318668e6d6e..0203779d994a3d 100644 --- a/Lib/test/test_memoryview.py +++ b/Lib/test/test_memoryview.py @@ -228,6 +228,32 @@ def test_compare(self): self.assertRaises(TypeError, lambda: m >= c) self.assertRaises(TypeError, lambda: c > m) + def test_compare_1d_concurrent_mutation(self): + # Prevent crashes during a mixed format 1-D comparison loop. + # Regression test for https://github.com/python/cpython/issues/142663. + src1 = array.array("d", [1.0, 2.0]) + src2 = array.array("l", [1, 2]) + mv1, mv2 = memoryview(src1), memoryview(src2) + self.do_test_compare_concurrent_mutation(src1, mv1, mv2) + + def test_compare_2d_concurrent_mutation(self): + # Prevent crashes during a mixed format 2-D comparison loop. + # Regression test for https://github.com/python/cpython/issues/142663. + src1 = array.array("d", [1.0, 2.0]) + src2 = array.array("l", [1, 2]) + mv1 = memoryview(src1).cast("B").cast("d", shape=(1, 2)) + mv2 = memoryview(src2).cast("B").cast("l", shape=(1, 2)) + self.do_test_compare_concurrent_mutation(src1, mv1, mv2) + + def do_test_compare_concurrent_mutation(self, src1, mv1, mv2): + class S(struct.Struct): + def unpack_from(self, buf, /, offset=0): + mv1.release() + src1.append(3.14) + return (1,) + with support.swap_attr(struct, "Struct", S): + self.assertRaises(BufferError, mv1.__eq__, mv2) + def check_attributes_with_type(self, tp): m = self._view(tp(self._source)) self.assertEqual(m.format, self.format) diff --git a/Misc/NEWS.d/next/Library/2025-12-30-22-12-27.gh-issue-142663.gq7iIf.rst b/Misc/NEWS.d/next/Library/2025-12-30-22-12-27.gh-issue-142663.gq7iIf.rst new file mode 100644 index 00000000000000..39ee1ef29267a7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-30-22-12-27.gh-issue-142663.gq7iIf.rst @@ -0,0 +1,2 @@ +Fix use-after-free crashes when a :class:`memoryview` is mutated +during a comparison with another object. diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c index f3b7e4a396b4a1..16b8495659c0d8 100644 --- a/Objects/memoryobject.c +++ b/Objects/memoryobject.c @@ -3122,7 +3122,8 @@ memory_richcompare(PyObject *v, PyObject *w, int op) } vv = VIEW_ADDR(v); - if (PyMemoryView_Check(w)) { + int w_is_mv = PyMemoryView_Check(w); + if (w_is_mv) { if (BASE_INACCESSIBLE(w)) { equal = (v == w); goto result; @@ -3165,6 +3166,13 @@ memory_richcompare(PyObject *v, PyObject *w, int op) goto result; } } + /* Prevent memoryview object from being released and its underlying buffer + reshaped during a mixed format comparison loop. */ + // See https://github.com/python/cpython/issues/142663. + ((PyMemoryViewObject *)v)->exports++; + if (w_is_mv) { + ((PyMemoryViewObject *)w)->exports++; + } if (vv->ndim == 0) { equal = unpack_cmp(vv->buf, ww->buf, @@ -3183,6 +3191,11 @@ memory_richcompare(PyObject *v, PyObject *w, int op) vfmt, unpack_v, unpack_w); } + ((PyMemoryViewObject *)v)->exports--; + if (w_is_mv) { + ((PyMemoryViewObject *)w)->exports--; + } + result: if (equal < 0) { if (equal == MV_COMPARE_NOT_IMPL)