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

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.
(cherry picked from commit 6d54b6ac7d)

Co-authored-by: zhong <60600792+superboy-zjc@users.noreply.github.com>
This commit is contained in:
Miss Islington (bot) 2026-01-09 13:16:51 +01:00 committed by GitHub
parent a299c1b185
commit f264f103d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 53 additions and 10 deletions

View file

@ -935,12 +935,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
pos = self._pos

View file

@ -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:

View file

@ -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`.

View file

@ -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;
}