https://github.com/python/cpython/commit/375e372c6661d818b85d1405c5ba681a06988ebd commit: 375e372c6661d818b85d1405c5ba681a06988ebd branch: main author: Yongtao Huang <[email protected]> committer: vstinner <[email protected]> date: 2026-01-19T15:09:30+01:00 summary:
gh-143689: Fix BufferedReader.read1 leaving object in reentrant state on error (#143690) BufferedReader.read1() could leave the buffered object in a reentrant (locked) state when an exception was raised while allocating the output buffer. This change ensures the internal buffered lock is always released on error, keeping the object in a consistent state after failures. Signed-off-by: Yongtao Huang <[email protected]> Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Cody Maloney <[email protected]> Co-authored-by: sobolevn <[email protected]> files: A Misc/NEWS.d/next/Library/2026-01-11-14-14-19.gh-issue-143689.fzHJ2W.rst M Lib/test/test_io/test_bufferedio.py M Modules/_io/bufferedio.c diff --git a/Lib/test/test_io/test_bufferedio.py b/Lib/test/test_io/test_bufferedio.py index 3278665bdc9dd3..e83dd0d4e28d00 100644 --- a/Lib/test/test_io/test_bufferedio.py +++ b/Lib/test/test_io/test_bufferedio.py @@ -10,7 +10,7 @@ from collections import deque, UserList from itertools import cycle, count from test import support -from test.support import os_helper, threading_helper +from test.support import check_sanitizer, os_helper, threading_helper from .utils import byteslike, CTestCase, PyTestCase @@ -623,6 +623,25 @@ def test_bad_readinto_type(self): bufio.readline() self.assertIsInstance(cm.exception.__cause__, TypeError) + @unittest.skipUnless(sys.maxsize > 2**32, 'requires 64bit platform') + @unittest.skipIf(check_sanitizer(thread=True), + 'ThreadSanitizer aborts on huge allocations (exit code 66).') + def test_read1_error_does_not_cause_reentrant_failure(self): + self.addCleanup(os_helper.unlink, os_helper.TESTFN) + with self.open(os_helper.TESTFN, "wb") as f: + f.write(b"hello") + + with self.open(os_helper.TESTFN, "rb", buffering=0) as raw: + bufio = self.tp(raw, buffer_size=8) + # To request a size that is far too huge to ever be satisfied, + # so that the internal buffer allocation reliably fails with MemoryError. + huge = sys.maxsize // 2 + 1 + with self.assertRaises(MemoryError): + bufio.read1(huge) + + # Used to crash before gh-143689: + self.assertEqual(bufio.read1(1), b"h") + class PyBufferedReaderTest(BufferedReaderTest, PyTestCase): tp = pyio.BufferedReader diff --git a/Misc/NEWS.d/next/Library/2026-01-11-14-14-19.gh-issue-143689.fzHJ2W.rst b/Misc/NEWS.d/next/Library/2026-01-11-14-14-19.gh-issue-143689.fzHJ2W.rst new file mode 100644 index 00000000000000..a423b1b70ad077 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-01-11-14-14-19.gh-issue-143689.fzHJ2W.rst @@ -0,0 +1 @@ +Fix :meth:`io.BufferedReader.read1` state cleanup on buffer allocation failure. diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c index 4602f2b42a6017..6d779abd89ca84 100644 --- a/Modules/_io/bufferedio.c +++ b/Modules/_io/bufferedio.c @@ -1073,6 +1073,7 @@ _io__Buffered_read1_impl(buffered *self, Py_ssize_t n) PyBytesWriter *writer = PyBytesWriter_Create(n); if (writer == NULL) { + LEAVE_BUFFERED(self) return NULL; } _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]
