From e2449e710e68ec67f34727d1b101e6d7cb2e5244 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 21 Oct 2026 21:29:32 -0700 Subject: [PATCH 1/2] gh-159739: Handle overlapping mmap writes Writing a memoryview of an mmap back into an overlapping region, through write() or contiguous slice assignment, used memcpy(), which has undefined behavior for overlapping ranges and can corrupt the mapped data. Use overlap-safe copying for both operations. Backport to 3.15 and 3.14. --- Lib/test/test_mmap.py | 12 ++++++++++++ .../2026-10-21-21-26-10.gh-issue-159739.yuOQX5.rst | 1 + Modules/mmapmodule.c | 4 ++-- 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.yuOQX5.rst diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index 59be17455a4e4e9..894415df0d81ec8 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -795,6 +795,18 @@ def test_weakref(self): gc_collect() self.assertIs(wr(), None) + def test_write_overlapping_memoryview(self): + data = bytes(range(256)) * 16 + expected = data[:1] + data[:-1] + with mmap.mmap(-1, len(data)) as mm, memoryview(mm) as view: + mm[:] = data + mm.seek(1) + self.assertEqual(mm.write(view[:-1]), len(data) - 1) + self.assertEqual(mm[:], expected) + mm[:] = data + mm[1:] = view[:-1] + self.assertEqual(mm[:], expected) + def test_write_returning_the_number_of_bytes_written(self): mm = mmap.mmap(-1, 16) self.assertEqual(mm.write(b""), 0) diff --git a/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.yuOQX5.rst b/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.yuOQX5.rst new file mode 100644 index 000000000000000..bfa3aecea4a9e67 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.yuOQX5.rst @@ -0,0 +1 @@ +Fix overlapping writes and contiguous slice assignments to :class:`mmap.mmap` objects. diff --git a/Modules/mmapmodule.c b/Modules/mmapmodule.c index ce5c59cb0212707..ea73fc9ab0228fb 100644 --- a/Modules/mmapmodule.c +++ b/Modules/mmapmodule.c @@ -752,7 +752,7 @@ mmap_mmap_write_impl(mmap_object *self, Py_buffer *data) CHECK_VALID(NULL); PyObject *result; - if (safe_memcpy(self->data + self->pos, data->buf, data->len) < 0) { + if (safe_memmove(self->data + self->pos, data->buf, data->len) < 0) { result = NULL; } else { @@ -1722,7 +1722,7 @@ mmap_ass_subscript_impl(mmap_object *self, PyObject *item, PyObject *value) if (slicelen == 0) { } else if (step == 1) { - if (safe_memcpy(self->data + start, vbuf.buf, slicelen) < 0) { + if (safe_memmove(self->data + start, vbuf.buf, slicelen) < 0) { result = -1; } } From 1353f4a96c015d942dae63055ac13f833d08335f Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 22 Oct 2026 11:38:36 -0700 Subject: [PATCH 2/2] gh-159739: Preserve overlapping mmap extended slice sources --- Lib/test/test_mmap.py | 14 ++++++++++++++ ...26-10-21-21-26-10.gh-issue-159739.yuOQX5.rst | 2 +- Modules/mmapmodule.c | 17 ++++++++++++++--- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index 894415df0d81ec8..e7b6ff39fdb9a1e 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -807,6 +807,20 @@ def test_write_overlapping_memoryview(self): mm[1:] = view[:-1] self.assertEqual(mm[:], expected) + def test_extended_slice_overlapping_memoryview(self): + data = b'abcdefghijkl' + for step in (-3, -2, -1, 2, 3): + target = slice(None, None, step) + size = len(data[target]) + for offset in (0, len(data) - size): + with self.subTest(step=step, offset=offset): + expected = bytearray(data) + expected[target] = data[offset:offset + size] + with mmap.mmap(-1, len(data)) as mm, memoryview(mm) as view: + mm[:] = data + mm[target] = view[offset:offset + size] + self.assertEqual(mm[:], expected) + def test_write_returning_the_number_of_bytes_written(self): mm = mmap.mmap(-1, 16) self.assertEqual(mm.write(b""), 0) diff --git a/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.yuOQX5.rst b/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.yuOQX5.rst index bfa3aecea4a9e67..71e75d277cc5543 100644 --- a/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.yuOQX5.rst +++ b/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.yuOQX5.rst @@ -1 +1 @@ -Fix overlapping writes and contiguous slice assignments to :class:`mmap.mmap` objects. +Fix overlapping writes and slice assignments to :class:`mmap.mmap` objects. diff --git a/Modules/mmapmodule.c b/Modules/mmapmodule.c index ea73fc9ab0228fb..7c9fae2ba96042f 100644 --- a/Modules/mmapmodule.c +++ b/Modules/mmapmodule.c @@ -1727,11 +1727,22 @@ mmap_ass_subscript_impl(mmap_object *self, PyObject *item, PyObject *value) } } else { - if (safe_copy_from_slice(self->data, (char *)vbuf.buf, start, step, - slicelen) < 0) - { + /* The source may overlap the destination, so copy it before + assigning to the extended slice. */ + char *source = PyMem_Malloc(slicelen); + if (source == NULL) { + PyErr_NoMemory(); result = -1; } + else { + if (safe_memcpy(source, vbuf.buf, slicelen) < 0 || + safe_copy_from_slice(self->data, source, start, step, + slicelen) < 0) + { + result = -1; + } + PyMem_Free(source); + } } PyBuffer_Release(&vbuf); return result;