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]

Reply via email to