Skip to content

Commit 241b9de

Browse files
msullivanVanshAgarwal24036vstinner
committed
gh-146065: Fix NULL dereference in FutureIter_am_send
This is based on #146304, but fixes a concurrency issue where `future` is accessed unsafely. Co-authored-by: VanshAgarwal24036 <148854295+VanshAgarwal24036@users.noreply.github.com> Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent a5b03fa commit 241b9de

3 files changed

Lines changed: 40 additions & 4 deletions

File tree

‎Lib/test/test_asyncio/test_futures.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,24 @@ def test_future_disallow_multiple_initialization(self):
763763
with self.assertRaises(RuntimeError, msg="is already initialized"):
764764
f.__init__(loop=self.loop)
765765

766+
def test_futureiter_send_after_throw_no_crash(self):
767+
fut = self._new_future(loop=self.loop)
768+
it = fut.__await__()
769+
next(it)
770+
with self.assertRaises(RuntimeError):
771+
it.throw(RuntimeError)
772+
with self.assertRaises(StopIteration):
773+
it.send(None)
774+
775+
def test_futureiter_send_after_close_no_crash(self):
776+
fut = self._new_future(loop=self.loop)
777+
it = fut.__await__()
778+
next(it)
779+
it.close()
780+
with self.assertRaises(StopIteration):
781+
it.send(None)
782+
783+
766784
@unittest.skipUnless(hasattr(futures, '_CFuture'),
767785
'requires the C _asyncio module')
768786
class CFutureTests(BaseFutureTests, test_utils.TestCase):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a crash in asyncio.Future iterator when send() is
2+
called after throw() or close().

‎Modules/_asynciomodule.c‎

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1795,10 +1795,9 @@ FutureIter_dealloc(PyObject *it)
17951795
}
17961796

17971797
static PySendResult
1798-
FutureIter_am_send_lock_held(futureiterobject *it, PyObject **result)
1798+
FutureIter_am_send_lock_held(FutureObj *fut, PyObject **result)
17991799
{
18001800
PyObject *res;
1801-
FutureObj *fut = it->future;
18021801
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(fut);
18031802

18041803
*result = NULL;
@@ -1829,11 +1828,24 @@ FutureIter_am_send(PyObject *op,
18291828
PyObject **result)
18301829
{
18311830
futureiterobject *it = (futureiterobject*)op;
1831+
1832+
FutureObj *fut;
1833+
Py_BEGIN_CRITICAL_SECTION(op);
1834+
fut = (FutureObj*)Py_XNewRef(it->future);
1835+
Py_END_CRITICAL_SECTION();
1836+
1837+
if (fut == NULL) {
1838+
PyErr_SetNone(PyExc_StopIteration);
1839+
*result = NULL;
1840+
return PYGEN_ERROR;
1841+
}
1842+
18321843
/* arg is unused, see the comment on FutureIter_send for clarification */
18331844
PySendResult res;
1834-
Py_BEGIN_CRITICAL_SECTION(it->future);
1835-
res = FutureIter_am_send_lock_held(it, result);
1845+
Py_BEGIN_CRITICAL_SECTION(fut);
1846+
res = FutureIter_am_send_lock_held(fut, result);
18361847
Py_END_CRITICAL_SECTION();
1848+
Py_DECREF(fut);
18371849
return res;
18381850
}
18391851

@@ -1927,7 +1939,9 @@ FutureIter_throw(PyObject *op, PyObject *const *args, Py_ssize_t nargs)
19271939
goto fail;
19281940
}
19291941

1942+
Py_BEGIN_CRITICAL_SECTION(self);
19301943
Py_CLEAR(self->future);
1944+
Py_END_CRITICAL_SECTION();
19311945

19321946
PyErr_Restore(type, val, tb);
19331947

@@ -1944,7 +1958,9 @@ static int
19441958
FutureIter_clear(PyObject *op)
19451959
{
19461960
futureiterobject *it = (futureiterobject*)op;
1961+
Py_BEGIN_CRITICAL_SECTION(op);
19471962
Py_CLEAR(it->future);
1963+
Py_END_CRITICAL_SECTION();
19481964
return 0;
19491965
}
19501966

0 commit comments

Comments
 (0)