https://github.com/python/cpython/commit/6d54b6ac7d5744e1f59d784c8e020d632d2959a3
commit: 6d54b6ac7d5744e1f59d784c8e020d632d2959a3
branch: main
author: zhong <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-01-09T13:50:56+02:00
summary:

gh-143378: Fix use-after-free when BytesIO is concurrently mutated during write 
operations (GH-143408)

PyObject_GetBuffer() can execute user code (e.g. via __buffer__), which may
close or otherwise mutate a BytesIO object while write() or writelines()
is in progress. This could invalidate the internal buffer and lead to a
use-after-free.

Ensure that PyObject_GetBuffer() is called before validation checks.

files:
A Misc/NEWS.d/next/Library/2026-01-03-19-41-36.gh-issue-143378.29AvE7.rst
M Lib/_pyio.py
M Lib/test/test_io/test_memoryio.py
M Modules/_io/bytesio.c

diff --git a/Lib/_pyio.py b/Lib/_pyio.py
index 69a088df8fc987..77c44addabf225 100644
--- a/Lib/_pyio.py
+++ b/Lib/_pyio.py
@@ -949,12 +949,12 @@ def read1(self, size=-1):
         return self.read(size)
 
     def write(self, b):
-        if self.closed:
-            raise ValueError("write to closed file")
         if isinstance(b, str):
             raise TypeError("can't write str to binary stream")
         with memoryview(b) as view:
             n = view.nbytes  # Size of any bytes-like object
+            if self.closed:
+                raise ValueError("write to closed file")
         if n == 0:
             return 0
 
diff --git a/Lib/test/test_io/test_memoryio.py 
b/Lib/test/test_io/test_memoryio.py
index bb023735e21398..f730e38a5d6485 100644
--- a/Lib/test/test_io/test_memoryio.py
+++ b/Lib/test/test_io/test_memoryio.py
@@ -587,6 +587,48 @@ def test_issue5449(self):
         self.ioclass(initial_bytes=buf)
         self.assertRaises(TypeError, self.ioclass, buf, foo=None)
 
+    def test_write_concurrent_close(self):
+        class B:
+            def __buffer__(self, flags):
+                memio.close()
+                return memoryview(b"A")
+
+        memio = self.ioclass()
+        self.assertRaises(ValueError, memio.write, B())
+
+    # Prevent crashes when memio.write() or memio.writelines()
+    # concurrently mutates (e.g., closes or exports) 'memio'.
+    # See: https://github.com/python/cpython/issues/143378.
+
+    def test_writelines_concurrent_close(self):
+        class B:
+            def __buffer__(self, flags):
+                memio.close()
+                return memoryview(b"A")
+
+        memio = self.ioclass()
+        self.assertRaises(ValueError, memio.writelines, [B()])
+
+    def test_write_concurrent_export(self):
+        class B:
+            buf = None
+            def __buffer__(self, flags):
+                self.buf = memio.getbuffer()
+                return memoryview(b"A")
+
+        memio = self.ioclass()
+        self.assertRaises(BufferError, memio.write, B())
+
+    def test_writelines_concurrent_export(self):
+        class B:
+            buf = None
+            def __buffer__(self, flags):
+                self.buf = memio.getbuffer()
+                return memoryview(b"A")
+
+        memio = self.ioclass()
+        self.assertRaises(BufferError, memio.writelines, [B()])
+
 
 class TextIOTestMixin:
 
diff --git 
a/Misc/NEWS.d/next/Library/2026-01-03-19-41-36.gh-issue-143378.29AvE7.rst 
b/Misc/NEWS.d/next/Library/2026-01-03-19-41-36.gh-issue-143378.29AvE7.rst
new file mode 100644
index 00000000000000..57bbb4d0a1399c
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-01-03-19-41-36.gh-issue-143378.29AvE7.rst
@@ -0,0 +1 @@
+Fix use-after-free crashes when a :class:`~io.BytesIO` object is concurrently 
mutated during :meth:`~io.RawIOBase.write` or :meth:`~io.IOBase.writelines`.
diff --git a/Modules/_io/bytesio.c b/Modules/_io/bytesio.c
index 96611823ab6b45..d088bb0efac797 100644
--- a/Modules/_io/bytesio.c
+++ b/Modules/_io/bytesio.c
@@ -194,18 +194,18 @@ write_bytes_lock_held(bytesio *self, PyObject *b)
 {
     _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
 
-    if (check_closed(self)) {
-        return -1;
-    }
-    if (check_exports(self)) {
-        return -1;
-    }
-
     Py_buffer buf;
+    Py_ssize_t len;
     if (PyObject_GetBuffer(b, &buf, PyBUF_CONTIG_RO) < 0) {
         return -1;
     }
-    Py_ssize_t len = buf.len;
+
+    if (check_closed(self) || check_exports(self)) {
+        len = -1;
+        goto done;
+    }
+
+    len = buf.len;
     if (len == 0) {
         goto done;
     }

_______________________________________________
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