Skip to content

Commit 1502a52

Browse files
cmaloneyclaude
andcommitted
gh-160781: Fix BytesIO overflow handling for large sizes
BytesIO.write() at a position near sys.maxsize raised SystemError ("Negative size passed to PyBytes_FromStringAndSize") because the overallocated buffer size could exceed PY_SSIZE_T_MAX. Check the computed allocation, not only the requested size. BytesIO.truncate() converted its argument with PyLong_AsLong(), which rejects sizes above 2**31 - 1 on Windows where C long is 32-bit. Use PyNumber_AsSsize_t() like the Py_ssize_t Argument Clinic converter it used before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 4521718 commit 1502a52

4 files changed

Lines changed: 18 additions & 1 deletion

File tree

‎Lib/test/test_io/test_memoryio.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,10 @@ def test_issue141311(self):
591591
self.assertEqual(sys.maxsize, memio.seek(sys.maxsize))
592592
buf = bytearray(2)
593593
self.assertEqual(0, memio.readinto(buf))
594+
# Writing there must not overflow the buffer size.
595+
memio.seek(sys.maxsize - 1)
596+
self.assertRaises((MemoryError, OverflowError), memio.write, b"x")
597+
self.assertEqual(memio.getvalue(), b"")
594598

595599
def test_peek(self):
596600
buf = self.buftype("1234567890")
@@ -984,6 +988,11 @@ def __index__(self):
984988
finally:
985989
size.result.release()
986990

991+
# Sizes above 2**31 - 1 must work where C long is 32-bit.
992+
stream = self.ioclass(b'abc')
993+
self.assertEqual(stream.truncate(2**31), 2**31)
994+
self.assertEqual(stream.getvalue(), b'abc')
995+
987996
def test_readinto_overlapping_buffer(self):
988997
with self.ioclass(b'abcd') as stream:
989998
with stream.getbuffer()[1:] as target:
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :class:`io.BytesIO` raising :exc:`SystemError` instead of
2+
:exc:`OverflowError` when writing at a position close to
3+
:data:`sys.maxsize`.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :meth:`io.BytesIO.truncate` rejecting sizes larger than ``2**31 - 1`` on
2+
Windows.

‎Modules/_io/bytesio.c‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,9 @@ resize_buffer_lock_held(bytesio *self, size_t size)
221221
/* Major upsize; resize up to exact size */
222222
alloc = size + 1;
223223
}
224+
if (alloc > PY_SSIZE_T_MAX) {
225+
goto overflow;
226+
}
224227

225228
if (SHARED_BUF(self)) {
226229
if (unshare_buffer_lock_held(self, alloc) < 0)
@@ -755,7 +758,7 @@ _io_BytesIO_truncate_impl(bytesio *self, PyObject *size)
755758
new_size = self->pos;
756759
}
757760
else {
758-
new_size = PyLong_AsLong(size);
761+
new_size = PyNumber_AsSsize_t(size, PyExc_OverflowError);
759762
if (new_size == -1 && PyErr_Occurred()) {
760763
return NULL;
761764
}

0 commit comments

Comments
 (0)