Skip to content

Commit 7e1eedd

Browse files
committed
fix(io): scope UAF patch to TextIOWrapper
1 parent 9763043 commit 7e1eedd

4 files changed

Lines changed: 9 additions & 43 deletions

File tree

‎Lib/test/test_io/test_bufferedio.py‎

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -607,27 +607,6 @@ 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-
631610
def test_bad_readinto_value(self):
632611
rawio = self.tp(self.BytesIO(b"12"))
633612
rawio.readinto = lambda buf: -1

‎Lib/test/test_io/test_textio.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1659,6 +1659,9 @@ def readinto(self, b):
16591659
self.BufferedReader(raw), encoding="utf-8")
16601660
method = getattr(wrapper, method_name)
16611661
self.assertEqual(method(), "ab\n")
1662+
with self.assertRaisesRegex(ValueError,
1663+
"underlying buffer has been detached"):
1664+
wrapper.buffer
16621665

16631666
def test_reentrant_seek_during_tell(self):
16641667
# gh-153539: reading short of _CHUNK_SIZE leaves residual bytes in the
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,2 @@
11
Fix a use-after-free in :class:`io.TextIOWrapper` when a call to the
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.
2+
underlying buffer reentrantly detaches it. Patched by Shamil Abdulaev.

‎Modules/_io/bufferedio.c‎

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
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()
1413
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
1514
#include "pycore_pyerrors.h" // _Py_FatalErrorFormat()
1615
#include "pycore_pylifecycle.h" // _Py_IsInterpreterFinalizing()
@@ -255,10 +254,6 @@ typedef struct {
255254
isn't ready for writing. */
256255
Py_off_t write_end;
257256

258-
/* Region handed to the raw stream by _bufferedreader_raw_read(). */
259-
char *raw_read_start;
260-
Py_ssize_t raw_read_len;
261-
262257
PyThread_type_lock lock;
263258
volatile unsigned long owner;
264259

@@ -1624,25 +1619,16 @@ _io_BufferedReader___init___impl(buffered *self, PyObject *raw,
16241619
return 0;
16251620
}
16261621

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-
16371622
static Py_ssize_t
16381623
_bufferedreader_raw_read(buffered *self, char *start, Py_ssize_t len)
16391624
{
1625+
Py_buffer buf;
16401626
PyObject *memobj, *res;
16411627
Py_ssize_t n;
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);
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);
16461632
if (memobj == NULL)
16471633
return -1;
16481634
/* NOTE: PyErr_SetFromErrno() calls PyErr_CheckSignals() when EINTR

0 commit comments

Comments
 (0)