Skip to content

Commit 4a455fe

Browse files
[3.14] gh-159739: Recheck BytesIO state after converting truncate size (GH-160190) (#160385)
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. (cherry picked from commit 8bfda55) Co-authored-by: Jelle Zijlstra <jelle@openai.com>
1 parent ef844c8 commit 4a455fe

3 files changed

Lines changed: 25 additions & 0 deletions

File tree

‎Lib/test/test_memoryio.py‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,28 @@ class CBytesIOTest(PyBytesIOTest):
860860
ioclass = io.BytesIO
861861
UnsupportedOperation = io.UnsupportedOperation
862862

863+
def test_truncate_size_changes_stream(self):
864+
class Size:
865+
def __init__(self, action):
866+
self.action = action
867+
868+
def __index__(self):
869+
self.result = self.action()
870+
return 0
871+
872+
stream = self.ioclass(b'abc')
873+
with self.assertRaises(ValueError):
874+
stream.truncate(Size(stream.close))
875+
876+
with self.ioclass(b'abc') as stream:
877+
size = Size(stream.getbuffer)
878+
try:
879+
with self.assertRaises(BufferError):
880+
stream.truncate(size)
881+
self.assertEqual(stream.getvalue(), b'abc')
882+
finally:
883+
size.result.release()
884+
863885
def test_readinto_overlapping_buffer(self):
864886
with self.ioclass(b'abcd') as stream:
865887
with stream.getbuffer()[1:] as target:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix the ``truncate`` method on :class:`io.BytesIO` when converting its size argument closes the stream or exports its buffer.

‎Modules/_io/bytesio.c‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,8 @@ _io_BytesIO_truncate_impl(bytesio *self, PyObject *size)
710710
"negative size value %zd", new_size);
711711
return NULL;
712712
}
713+
CHECK_CLOSED(self);
714+
CHECK_EXPORTS(self);
713715
}
714716

715717
if (new_size < self->string_size) {

0 commit comments

Comments
 (0)