From 3d898fdae98ad8be5e8378d3fae6c64cd03ece4e 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: Recheck BytesIO state after converting truncate size A size object's __index__() can close a BytesIO or export its buffer after truncate() checks the stream. Continuing can dereference a cleared buffer or resize storage behind a live view. Recheck the stream state after size conversion. Backport to 3.15 and 3.14; on 3.14, put the regression test in Lib/test/test_memoryio.py. --- Lib/test/test_io/test_memoryio.py | 22 +++++++++++++++++++ ...-10-21-21-26-10.gh-issue-159739.rVWF2a.rst | 1 + Modules/_io/bytesio.c | 2 ++ 3 files changed, 25 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.rVWF2a.rst diff --git a/Lib/test/test_io/test_memoryio.py b/Lib/test/test_io/test_memoryio.py index 90bdc791787439..999bd5bc007b2e 100644 --- a/Lib/test/test_io/test_memoryio.py +++ b/Lib/test/test_io/test_memoryio.py @@ -962,6 +962,28 @@ class CBytesIOTest(PyBytesIOTest): ioclass = io.BytesIO UnsupportedOperation = io.UnsupportedOperation + def test_truncate_size_changes_stream(self): + class Size: + def __init__(self, action): + self.action = action + + def __index__(self): + self.result = self.action() + return 0 + + stream = self.ioclass(b'abc') + with self.assertRaises(ValueError): + stream.truncate(Size(stream.close)) + + with self.ioclass(b'abc') as stream: + size = Size(stream.getbuffer) + try: + with self.assertRaises(BufferError): + stream.truncate(size) + self.assertEqual(stream.getvalue(), b'abc') + finally: + size.result.release() + def test_readinto_overlapping_buffer(self): with self.ioclass(b'abcd') as stream: with stream.getbuffer()[1:] as target: diff --git a/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.rVWF2a.rst b/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.rVWF2a.rst new file mode 100644 index 00000000000000..3a50e7e799859a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.rVWF2a.rst @@ -0,0 +1 @@ +Fix :meth:`io.BytesIO.truncate` when converting its size argument closes the stream or exports its buffer. diff --git a/Modules/_io/bytesio.c b/Modules/_io/bytesio.c index 2374a43ef74eab..aa74d9b0c721a4 100644 --- a/Modules/_io/bytesio.c +++ b/Modules/_io/bytesio.c @@ -754,6 +754,8 @@ _io_BytesIO_truncate_impl(bytesio *self, PyObject *size) "negative size value %zd", new_size); return NULL; } + CHECK_CLOSED(self); + CHECK_EXPORTS(self); } if (new_size < self->string_size) { From 18f01839b40f2c46d22ade01d24354fea38383e4 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 22 Oct 2026 14:10:49 -0700 Subject: [PATCH 2/2] Apply suggestion from @JelleZijlstra --- .../next/Library/2026-10-21-21-26-10.gh-issue-159739.rVWF2a.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.rVWF2a.rst b/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.rVWF2a.rst index 3a50e7e799859a..f2e13ee926eaa1 100644 --- a/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.rVWF2a.rst +++ b/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.rVWF2a.rst @@ -1 +1 @@ -Fix :meth:`io.BytesIO.truncate` when converting its size argument closes the stream or exports its buffer. +Fix the ``truncate`` method on :class:`io.BytesIO` when converting its size argument closes the stream or exports its buffer.