https://github.com/python/cpython/commit/c554143aa15132c413b231f2c6e41f3b98de7846
commit: c554143aa15132c413b231f2c6e41f3b98de7846
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: vstinner <[email protected]>
date: 2026-09-23T21:24:44Z
summary:

[3.14] gh-157335: Fix out-of-bounds write in mmap.mmap.__setitem__ (GH-157438) 
(#158027)

gh-157335: Fix out-of-bounds write in mmap.mmap.__setitem__ (GH-157438)

Fix out-of-bounds write in mmap.mmap.__setitem__() that could occur
when converting the index or the assigned value (via __index__()
for a single item, or via the buffer protocol for a slice) resized or closed
the mmap object during the assignment.
(cherry picked from commit 09bf4c525871db3ad02c8a1359da5cf0668f27b0)

Co-authored-by: Joseph Kerry <[email protected]>
Co-authored-by: Victor Stinner <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-09-13-15-58-28.gh-issue-157335.efaMah.rst
M Lib/test/test_mmap.py
M Modules/mmapmodule.c

diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py
index 93be6c2830357e..c5858ecca2ff1f 100644
--- a/Lib/test/test_mmap.py
+++ b/Lib/test/test_mmap.py
@@ -72,7 +72,7 @@ def test_basic(self):
 
         # Shouldn't crash on boundary (Issue #5292)
         self.assertRaises(IndexError, m.__getitem__, len(m))
-        self.assertRaises(IndexError, m.__setitem__, len(m), b'\0')
+        self.assertRaises(IndexError, m.__setitem__, len(m), 0)
 
         # Modify the file's content
         m[0] = b'3'[0]
@@ -974,6 +974,55 @@ def test_resize_down_anonymous_mapping(self):
                     with self.assertRaises(ValueError):
                         m.resize(start_size)
 
+    @unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize')
+    def test_setitem_resize_reentrancy(self):
+        """Resizing the mmap from inside __index__ while assigning to a
+        single item must not access memory past the new bounds (gh-157335).
+        """
+        size = 2 * PAGESIZE
+        new_size = PAGESIZE
+
+        class ResizeOnIndex:
+            def __init__(self, m):
+                self.m = m
+            def __index__(self):
+                self.m.resize(new_size)
+                return 0
+
+        with mmap.mmap(-1, size) as m:
+            try:
+                with self.assertRaises(IndexError):
+                    m[size - 1] = ResizeOnIndex(m)
+            except SystemError as exc:
+                self.skipTest(f"resize() is not available: {exc!r}")
+            self.assertEqual(len(m), new_size)
+
+    @unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize')
+    def test_setitem_slice_resize_reentrancy(self):
+        """Resizing the mmap from inside a value's buffer-protocol
+        callback while assigning to a slice must not access memory past
+        the new bounds (gh-157335).
+        """
+        size = 2 * PAGESIZE
+        new_size = PAGESIZE
+
+        class ResizeOnBuffer:
+            def __init__(self, m, data):
+                self.m = m
+                self.data = data
+            def __buffer__(self, flags):
+                self.m.resize(new_size)
+                return memoryview(self.data)
+
+        with mmap.mmap(-1, size) as m:
+            value = ResizeOnBuffer(m, bytes(size))
+            try:
+                with self.assertRaises(IndexError):
+                    m[0:size] = value
+            except SystemError as exc:
+                self.skipTest(f"resize() is not available: {exc!r}")
+            self.assertEqual(len(m), new_size)
+
     @unittest.skipUnless(os.name == 'nt', 'requires Windows')
     def test_resize_fails_if_mapping_held_elsewhere(self):
         """If more than one mapping is held against a named file on Windows, 
neither
diff --git 
a/Misc/NEWS.d/next/Library/2026-09-13-15-58-28.gh-issue-157335.efaMah.rst 
b/Misc/NEWS.d/next/Library/2026-09-13-15-58-28.gh-issue-157335.efaMah.rst
new file mode 100644
index 00000000000000..fb0bcad3060564
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-09-13-15-58-28.gh-issue-157335.efaMah.rst
@@ -0,0 +1,4 @@
+Fix out-of-bounds write in ``mmap.mmap.__setitem__`` that could occur
+when converting the index or the assigned value (via :meth:`~object.__index__`
+for a single item, or via the buffer protocol for a slice) resized or closed 
the mmap
+object during the assignment.
diff --git a/Modules/mmapmodule.c b/Modules/mmapmodule.c
index f7b8ab336fed73..269c87f5236203 100644
--- a/Modules/mmapmodule.c
+++ b/Modules/mmapmodule.c
@@ -1608,24 +1608,15 @@ static int
 mmap_ass_subscript_lock_held(PyObject *op, PyObject *item, PyObject *value)
 {
     mmap_object *self = mmap_object_CAST(op);
-    CHECK_VALID(-1);
 
     if (!is_writable(self))
         return -1;
 
     if (PyIndex_Check(item)) {
         Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError);
-        Py_ssize_t v;
-
         if (i == -1 && PyErr_Occurred())
             return -1;
-        if (i < 0)
-            i += self->size;
-        if (i < 0 || i >= self->size) {
-            PyErr_SetString(PyExc_IndexError,
-                            "mmap index out of range");
-            return -1;
-        }
+
         if (value == NULL) {
             PyErr_SetString(PyExc_TypeError,
                             "mmap doesn't support item deletion");
@@ -1636,7 +1627,7 @@ mmap_ass_subscript_lock_held(PyObject *op, PyObject 
*item, PyObject *value)
                             "mmap item value must be an int");
             return -1;
         }
-        v = PyNumber_AsSsize_t(value, PyExc_TypeError);
+        Py_ssize_t v = PyNumber_AsSsize_t(value, PyExc_TypeError);
         if (v == -1 && PyErr_Occurred())
             return -1;
         if (v < 0 || v > 255) {
@@ -1645,7 +1636,18 @@ mmap_ass_subscript_lock_held(PyObject *op, PyObject 
*item, PyObject *value)
                             "in range(0, 256)");
             return -1;
         }
+
+        /* Converting item or value above may have run arbitrary code
+         * (e.g. __index__) that resized or closed the mmap, so bounds
+         * are only checked now, against the current size. */
         CHECK_VALID(-1);
+        if (i < 0)
+            i += self->size;
+        if (i < 0 || i >= self->size) {
+            PyErr_SetString(PyExc_IndexError,
+                            "mmap index out of range");
+            return -1;
+        }
 
         char v_char = (char) v;
         if (safe_byte_copy(self->data + i, &v_char) < 0) {
@@ -1660,7 +1662,6 @@ mmap_ass_subscript_lock_held(PyObject *op, PyObject 
*item, PyObject *value)
         if (PySlice_Unpack(item, &start, &stop, &step) < 0) {
             return -1;
         }
-        slicelen = PySlice_AdjustIndices(self->size, &start, &stop, step);
         if (value == NULL) {
             PyErr_SetString(PyExc_TypeError,
                 "mmap object doesn't support slice deletion");
@@ -1668,6 +1669,12 @@ mmap_ass_subscript_lock_held(PyObject *op, PyObject 
*item, PyObject *value)
         }
         if (PyObject_GetBuffer(value, &vbuf, PyBUF_SIMPLE) < 0)
             return -1;
+
+        /* Acquiring the buffer above may have run arbitrary code (e.g. a
+         * __buffer__ method) that resized or closed this mmap, so the slice 
bounds
+         * are only computed now, against the current size. */
+        CHECK_VALID_OR_RELEASE(-1, vbuf);
+        slicelen = PySlice_AdjustIndices(self->size, &start, &stop, step);
         if (vbuf.len != slicelen) {
             PyErr_SetString(PyExc_IndexError,
                 "mmap slice assignment is wrong size");
@@ -1675,7 +1682,6 @@ mmap_ass_subscript_lock_held(PyObject *op, PyObject 
*item, PyObject *value)
             return -1;
         }
 
-        CHECK_VALID_OR_RELEASE(-1, vbuf);
         int result = 0;
         if (slicelen == 0) {
         }

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to