Skip to content

Commit 5dd00da

Browse files
ashm-devvstinner
andcommitted
gh-157364: Fix use-after-free in io.TextIOWrapper during reentrant detach (#157370)
Co-authored-by: Victor Stinner <vstinner@python.org> (cherry picked from commit e19dc47)
1 parent e1ff17b commit 5dd00da

3 files changed

Lines changed: 51 additions & 9 deletions

File tree

‎Lib/test/test_io/test_textio.py‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1676,6 +1676,36 @@ def make_text(buffer):
16761676
wrapper.write('x')
16771677
self.assertRaisesRegex(ValueError, "detached", wrapper.read)
16781678

1679+
def test_reentrant_detach_during_read(self):
1680+
# gh-157363, gh-157364: The buffer must stay alive until its active
1681+
# read operation returns.
1682+
wrapper = None
1683+
1684+
class DetachOnRead(self.RawIOBase):
1685+
detached = False
1686+
1687+
def readable(self):
1688+
return True
1689+
1690+
def readinto(self, b):
1691+
if self.detached:
1692+
return 0
1693+
self.detached = True
1694+
wrapper.detach()
1695+
b[:3] = b"ab\n"
1696+
return 3
1697+
1698+
for method_name in ("read", "readline"):
1699+
with self.subTest(method_name):
1700+
raw = DetachOnRead()
1701+
wrapper = self.TextIOWrapper(
1702+
self.BufferedReader(raw), encoding="utf-8")
1703+
method = getattr(wrapper, method_name)
1704+
self.assertEqual(method(), "ab\n")
1705+
with self.assertRaisesRegex(ValueError,
1706+
"underlying buffer has been detached"):
1707+
getattr(wrapper, 'buffer')
1708+
16791709

16801710
class PyTextIOWrapperTest(TextIOWrapperTest, PyTestCase):
16811711
shutdown_error = "LookupError: unknown encoding: ascii"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a use-after-free in :class:`io.TextIOWrapper` when a call to the
2+
underlying buffer reentrantly detaches it. Patched by Shamil Abdulaev.

‎Modules/_io/textio.c‎

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -753,10 +753,8 @@ buffer_access_safe(textio *self)
753753
return NULL;
754754
}
755755

756-
/* Returning a borrowed reference is safe since TextIOWrapper methods are
757-
protected by critical sections. */
758756
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
759-
return self->buffer;
757+
return Py_NewRef(self->buffer);
760758
}
761759

762760
static PyObject *
@@ -767,7 +765,9 @@ buffer_getattr(textio *self, PyObject *attr_name)
767765
return NULL;
768766
}
769767

770-
return PyObject_GetAttr(buffer, attr_name);
768+
PyObject *res = PyObject_GetAttr(buffer, attr_name);
769+
Py_DECREF(buffer);
770+
return res;
771771
}
772772

773773
static PyObject *
@@ -778,7 +778,9 @@ buffer_callmethod_noargs(textio *self, PyObject *name)
778778
return NULL;
779779
}
780780

781-
return PyObject_CallMethodNoArgs(buffer, name);
781+
PyObject *res = PyObject_CallMethodNoArgs(buffer, name);
782+
Py_DECREF(buffer);
783+
return res;
782784
}
783785

784786
static PyObject *
@@ -789,7 +791,9 @@ buffer_callmethod_onearg(textio *self, PyObject *name, PyObject *arg)
789791
return NULL;
790792
}
791793

792-
return PyObject_CallMethodOneArg(buffer, name, arg);
794+
PyObject *res = PyObject_CallMethodOneArg(buffer, name, arg);
795+
Py_DECREF(buffer);
796+
return res;
793797
}
794798

795799
static void
@@ -1639,7 +1643,7 @@ _io_TextIOWrapper_detach_impl(textio *self)
16391643
if (buffer == NULL) {
16401644
return NULL;
16411645
}
1642-
self->buffer = NULL;
1646+
Py_CLEAR(self->buffer);
16431647
self->detached = 1;
16441648
return buffer;
16451649
}
@@ -1860,7 +1864,12 @@ _io_TextIOWrapper_write_impl(textio *self, PyObject *text)
18601864

18611865
if (needflush) {
18621866
PyObject *buffer = buffer_access_safe(self);
1863-
if (buffer == NULL || _PyFile_Flush(buffer) < 0) {
1867+
if (buffer == NULL) {
1868+
return NULL;
1869+
}
1870+
int res = _PyFile_Flush(buffer);
1871+
Py_DECREF(buffer);
1872+
if (res < 0) {
18641873
return NULL;
18651874
}
18661875
}
@@ -2679,6 +2688,7 @@ _io_TextIOWrapper_seek_impl(textio *self, PyObject *cookieObj, int whence)
26792688
goto fail;
26802689
}
26812690
res = _PyObject_CallMethod(buf, &_Py_ID(seek), "ii", 0, 2);
2691+
Py_DECREF(buf);
26822692
Py_CLEAR(cookieObj);
26832693
if (res == NULL)
26842694
goto fail;
@@ -3436,7 +3446,7 @@ static PyObject *
34363446
_io_TextIOWrapper_buffer_get_impl(textio *self)
34373447
/*[clinic end generated code: output=d265a34555aa5d4b input=5951cfa148f7350a]*/
34383448
{
3439-
return Py_XNewRef(buffer_access_safe(self));
3449+
return buffer_access_safe(self);
34403450
}
34413451

34423452
static PyMethodDef incrementalnewlinedecoder_methods[] = {

0 commit comments

Comments
 (0)