Skip to content

Commit 2409319

Browse files
committed
gh-143378: Fix use-after-free BytesIO write via re-entrant buffer access
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. Temporarily bump the exports counter while acquiring the input buffer to block re-entrant mutation, and add regression tests to ensure such cases raise BufferError instead of crashing.
1 parent 9609574 commit 2409319

File tree

3 files changed

+38
-0
lines changed

3 files changed

+38
-0
lines changed

Lib/test/test_io/test_memoryio.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,6 +856,37 @@ def test_cow_mutable(self):
856856
memio = self.ioclass(ba)
857857
self.assertEqual(sys.getrefcount(ba), old_rc)
858858

859+
@support.cpython_only
860+
def test_uaf_buffer_write(self):
861+
# Prevent use-after-free when write() triggers a re-entrant call that
862+
# closes or mutates the BytesIO object.
863+
# See: https://github.com/python/cpython/issues/143378
864+
class TBuf:
865+
def __init__(self, bio):
866+
self.bio = bio
867+
def __buffer__(self, flags):
868+
self.bio.close()
869+
return memoryview(b"A")
870+
871+
memio = self.ioclass()
872+
self.assertRaises(BufferError, memio.write, TBuf(memio))
873+
874+
@support.cpython_only
875+
def test_uaf_buffer_writelines(self):
876+
# Prevent use-after-free when writelines() triggers a re-entrant call that
877+
# closes or mutates the BytesIO object.
878+
# See: https://github.com/python/cpython/issues/143378
879+
class TBuf:
880+
def __init__(self, bio):
881+
self.bio = bio
882+
def __buffer__(self, flags):
883+
self.bio.close()
884+
return memoryview(b"A")
885+
886+
memio = self.ioclass()
887+
self.assertRaises(BufferError, memio.writelines, [TBuf(memio)])
888+
889+
859890
class CStringIOTest(PyStringIOTest):
860891
ioclass = io.StringIO
861892
UnsupportedOperation = io.UnsupportedOperation
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix use-after-free crashes when a :class:`_io.BytesIO` object is mutated during
2+
buffer writes via :meth:`write` or :meth:`writelines`.

Modules/_io/bytesio.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,9 +202,14 @@ write_bytes_lock_held(bytesio *self, PyObject *b)
202202
}
203203

204204
Py_buffer buf;
205+
/* Issue #143378: Prevent re-entrant mutation during PyObject_GetBuffer() */
206+
self->exports++;
205207
if (PyObject_GetBuffer(b, &buf, PyBUF_CONTIG_RO) < 0) {
208+
self->exports--;
206209
return -1;
207210
}
211+
self->exports--;
212+
208213
Py_ssize_t len = buf.len;
209214
if (len == 0) {
210215
goto done;

0 commit comments

Comments
 (0)