Skip to content

Commit 375e372

Browse files
hyongtao-codeblurb-it[bot]cmaloneysobolevn
authored
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 <yongtaoh2022@gmail.com> Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Cody Maloney <cmaloney@users.noreply.github.com> Co-authored-by: sobolevn <mail@sobolevn.me>
1 parent 72bacb0 commit 375e372

File tree

3 files changed

+22
-1
lines changed

3 files changed

+22
-1
lines changed

Lib/test/test_io/test_bufferedio.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from collections import deque, UserList
1111
from itertools import cycle, count
1212
from test import support
13-
from test.support import os_helper, threading_helper
13+
from test.support import check_sanitizer, os_helper, threading_helper
1414
from .utils import byteslike, CTestCase, PyTestCase
1515

1616

@@ -623,6 +623,25 @@ def test_bad_readinto_type(self):
623623
bufio.readline()
624624
self.assertIsInstance(cm.exception.__cause__, TypeError)
625625

626+
@unittest.skipUnless(sys.maxsize > 2**32, 'requires 64bit platform')
627+
@unittest.skipIf(check_sanitizer(thread=True),
628+
'ThreadSanitizer aborts on huge allocations (exit code 66).')
629+
def test_read1_error_does_not_cause_reentrant_failure(self):
630+
self.addCleanup(os_helper.unlink, os_helper.TESTFN)
631+
with self.open(os_helper.TESTFN, "wb") as f:
632+
f.write(b"hello")
633+
634+
with self.open(os_helper.TESTFN, "rb", buffering=0) as raw:
635+
bufio = self.tp(raw, buffer_size=8)
636+
# To request a size that is far too huge to ever be satisfied,
637+
# so that the internal buffer allocation reliably fails with MemoryError.
638+
huge = sys.maxsize // 2 + 1
639+
with self.assertRaises(MemoryError):
640+
bufio.read1(huge)
641+
642+
# Used to crash before gh-143689:
643+
self.assertEqual(bufio.read1(1), b"h")
644+
626645

627646
class PyBufferedReaderTest(BufferedReaderTest, PyTestCase):
628647
tp = pyio.BufferedReader
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix :meth:`io.BufferedReader.read1` state cleanup on buffer allocation failure.

Modules/_io/bufferedio.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,6 +1073,7 @@ _io__Buffered_read1_impl(buffered *self, Py_ssize_t n)
10731073

10741074
PyBytesWriter *writer = PyBytesWriter_Create(n);
10751075
if (writer == NULL) {
1076+
LEAVE_BUFFERED(self)
10761077
return NULL;
10771078
}
10781079

0 commit comments

Comments
 (0)