diff --git a/Lib/test/test_io/test_memoryio.py b/Lib/test/test_io/test_memoryio.py index f53525d77b7dbaf..649b8677d5d1202 100644 --- a/Lib/test/test_io/test_memoryio.py +++ b/Lib/test/test_io/test_memoryio.py @@ -54,6 +54,9 @@ def testSeek(self): self.assertEqual(buf[3:], bytesIo.read()) self.assertRaises(TypeError, bytesIo.seek, 0.0) + # gh-148286: seeking this far past the end and then reading is + # what exercises the overseek path where the pointer arithmetic + # wraps. Under UBSan this is the case that reports, so keep it. self.assertEqual(sys.maxsize, bytesIo.seek(sys.maxsize)) self.assertEqual(self.EOF, bytesIo.read(4)) diff --git a/Misc/NEWS.d/next/Library/2026-07-30-12-05-00.gh-issue-148286.Bv3Nq8.rst b/Misc/NEWS.d/next/Library/2026-07-30-12-05-00.gh-issue-148286.Bv3Nq8.rst new file mode 100644 index 000000000000000..9b2c98a5ba74a6f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-30-12-05-00.gh-issue-148286.Bv3Nq8.rst @@ -0,0 +1,2 @@ +Fix undefined behaviour in :meth:`!io.StringIO.read` when reading from a +position past the end of the buffer. diff --git a/Modules/_io/stringio.c b/Modules/_io/stringio.c index 27605b4c44239e9..ff74acee0bbc8f9 100644 --- a/Modules/_io/stringio.c +++ b/Modules/_io/stringio.c @@ -349,6 +349,17 @@ _io_StringIO_read_impl(stringio *self, Py_ssize_t size) } ENSURE_REALIZED(self); + + /* In case of overseek, return the empty string, as _stringio_readline() + does. `size` is already clamped to 0 here, so nothing would be read + through the pointer -- but forming `self->buf + self->pos` when + `self->pos` is past the end of the buffer is undefined behaviour on + its own, and for a large enough `self->pos` the multiplication by + sizeof(Py_UCS4) wraps and yields a pointer below `self->buf`. */ + if (self->pos >= self->string_size) { + return Py_GetConstant(Py_CONSTANT_EMPTY_STR); + } + output = self->buf + self->pos; self->pos += size; return PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, output, size); diff --git a/Tools/ubsan/suppressions.txt b/Tools/ubsan/suppressions.txt index 2b9ad49ebf1eefb..b22663e47759e9b 100644 --- a/Tools/ubsan/suppressions.txt +++ b/Tools/ubsan/suppressions.txt @@ -14,6 +14,3 @@ shift-base:Modules/_ctypes/cfield.c # Modules/_ctypes/cfield.c:640:1: runtime error: signed integer overflow: -2147483648 - 1 cannot be represented in type 'int' signed-integer-overflow:Modules/_ctypes/cfield.c - -# Modules/_io/stringio.c:350:24: runtime error: addition of unsigned offset to 0x7fd01ec25850 overflowed to 0x7fd01ec2584c -pointer-overflow:Modules/_io/stringio.c