Skip to content

Commit 9763043

Browse files
committed
fix(io): keep BufferedReader alive via readinto memoryview
1 parent c3755e4 commit 9763043

3 files changed

Lines changed: 43 additions & 6 deletions

File tree

‎Lib/test/test_io/test_bufferedio.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,27 @@ def test_args_error(self):
607607
with self.assertRaisesRegex(TypeError, "BufferedReader"):
608608
self.tp(self.BytesIO(), 1024, 1024, 1024)
609609

610+
def test_readinto_buffer_keeps_reader_alive(self):
611+
# gh-157364: raw.readinto() may store the memoryview it is given, so
612+
# the memoryview must keep the BufferedReader owning its memory alive.
613+
stored = []
614+
615+
class StoringRawIO(self.MockRawIO):
616+
def readinto(self, b):
617+
stored.append(b)
618+
return super().readinto(b)
619+
620+
bufio = self.tp(StoringRawIO([b"abc"]))
621+
self.assertEqual(bufio.read(3), b"abc")
622+
self.assertIs(stored[0].obj, bufio)
623+
wr = weakref.ref(bufio)
624+
del bufio
625+
support.gc_collect()
626+
self.assertIsNotNone(wr())
627+
stored.clear()
628+
support.gc_collect()
629+
self.assertIsNone(wr())
630+
610631
def test_bad_readinto_value(self):
611632
rawio = self.tp(self.BytesIO(b"12"))
612633
rawio.readinto = lambda buf: -1
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
Fix a use-after-free in :class:`io.TextIOWrapper` when a call to the
2-
underlying buffer reentrantly detaches it. Patched by Shamil Abdulaev.
2+
underlying buffer reentrantly detaches it. The :class:`memoryview` that
3+
:class:`io.BufferedReader` passes to the raw stream's ``readinto()`` now keeps
4+
the reader alive. Patched by Shamil Abdulaev.

‎Modules/_io/bufferedio.c‎

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "Python.h"
1111
#include "pycore_call.h" // _PyObject_CallNoArgs()
1212
#include "pycore_fileutils.h" // _PyFile_Flush
13+
#include "pycore_memoryobject.h" // _PyMemoryView_FromBufferProc()
1314
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
1415
#include "pycore_pyerrors.h" // _Py_FatalErrorFormat()
1516
#include "pycore_pylifecycle.h" // _Py_IsInterpreterFinalizing()
@@ -254,6 +255,10 @@ typedef struct {
254255
isn't ready for writing. */
255256
Py_off_t write_end;
256257

258+
/* Region handed to the raw stream by _bufferedreader_raw_read(). */
259+
char *raw_read_start;
260+
Py_ssize_t raw_read_len;
261+
257262
PyThread_type_lock lock;
258263
volatile unsigned long owner;
259264

@@ -1619,16 +1624,25 @@ _io_BufferedReader___init___impl(buffered *self, PyObject *raw,
16191624
return 0;
16201625
}
16211626

1627+
/* The memoryview passed to raw.readinto() owns a reference to the buffered
1628+
object so that storing it in Python code cannot outlive its memory. */
1629+
static int
1630+
_bufferedreader_raw_read_getbuffer(PyObject *op, Py_buffer *view, int flags)
1631+
{
1632+
buffered *self = buffered_CAST(op);
1633+
return PyBuffer_FillInfo(view, op, self->raw_read_start,
1634+
self->raw_read_len, 0, flags);
1635+
}
1636+
16221637
static Py_ssize_t
16231638
_bufferedreader_raw_read(buffered *self, char *start, Py_ssize_t len)
16241639
{
1625-
Py_buffer buf;
16261640
PyObject *memobj, *res;
16271641
Py_ssize_t n;
1628-
/* NOTE: the buffer needn't be released as its object is NULL. */
1629-
if (PyBuffer_FillInfo(&buf, NULL, start, len, 0, PyBUF_CONTIG) == -1)
1630-
return -1;
1631-
memobj = PyMemoryView_FromBuffer(&buf);
1642+
self->raw_read_start = start;
1643+
self->raw_read_len = len;
1644+
memobj = _PyMemoryView_FromBufferProc((PyObject *)self, PyBUF_CONTIG,
1645+
_bufferedreader_raw_read_getbuffer);
16321646
if (memobj == NULL)
16331647
return -1;
16341648
/* NOTE: PyErr_SetFromErrno() calls PyErr_CheckSignals() when EINTR

0 commit comments

Comments
 (0)