Skip to content

Commit abdbe0b

Browse files
gh-142615: disallow multiple initializations of asyncio.Task and asyncio.Future (#142616)
1 parent b538c28 commit abdbe0b

File tree

5 files changed

+26
-78
lines changed

5 files changed

+26
-78
lines changed

Lib/asyncio/futures.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ def __init__(self, *, loop=None):
7979
loop object used by the future. If it's not provided, the future uses
8080
the default event loop.
8181
"""
82+
if self._loop is not None:
83+
raise RuntimeError(f"{self.__class__.__name__} object is already "
84+
"initialized")
85+
8286
if loop is None:
8387
self._loop = events.get_event_loop()
8488
else:

Lib/test/test_asyncio/test_futures.py

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,10 @@ def test_future_cancelled_exception_refcycles(self):
750750
self.assertIsNotNone(exc)
751751
self.assertListEqual(gc.get_referrers(exc), [])
752752

753+
def test_future_disallow_multiple_initialization(self):
754+
f = self._new_future(loop=self.loop)
755+
with self.assertRaises(RuntimeError, msg="is already initialized"):
756+
f.__init__(loop=self.loop)
753757

754758
@unittest.skipUnless(hasattr(futures, '_CFuture'),
755759
'requires the C _asyncio module')
@@ -1091,33 +1095,6 @@ def __getattribute__(self, name):
10911095
fut.add_done_callback(fut_callback_0)
10921096
self.assertRaises(ReachableCode, fut.set_result, "boom")
10931097

1094-
def test_use_after_free_on_fut_context_0_with_evil__getattribute__(self):
1095-
# see: https://github.com/python/cpython/issues/125984
1096-
1097-
class EvilEventLoop(SimpleEvilEventLoop):
1098-
def call_soon(self, *args, **kwargs):
1099-
super().call_soon(*args, **kwargs)
1100-
raise ReachableCode
1101-
1102-
def __getattribute__(self, name):
1103-
if name == 'call_soon':
1104-
# resets the future's event loop
1105-
fut.__init__(loop=SimpleEvilEventLoop())
1106-
return object.__getattribute__(self, name)
1107-
1108-
evil_loop = EvilEventLoop()
1109-
with mock.patch.object(self, 'loop', evil_loop):
1110-
fut = self._new_future()
1111-
self.assertIs(fut.get_loop(), evil_loop)
1112-
1113-
fut_callback_0 = mock.Mock()
1114-
fut_context_0 = mock.Mock()
1115-
fut.add_done_callback(fut_callback_0, context=fut_context_0)
1116-
del fut_context_0
1117-
del fut_callback_0
1118-
self.assertRaises(ReachableCode, fut.set_result, "boom")
1119-
1120-
11211098
@unittest.skipUnless(hasattr(futures, '_CFuture'),
11221099
'requires the C _asyncio module')
11231100
class CFutureDoneCallbackTests(BaseFutureDoneCallbackTests,

Lib/test/test_asyncio/test_tasks.py

Lines changed: 9 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2776,28 +2776,17 @@ def test_get_context(self):
27762776
finally:
27772777
loop.close()
27782778

2779-
def test_proper_refcounts(self):
2780-
# see: https://github.com/python/cpython/issues/126083
2781-
class Break:
2782-
def __str__(self):
2783-
raise RuntimeError("break")
2784-
2785-
obj = object()
2786-
initial_refcount = sys.getrefcount(obj)
2787-
2788-
coro = coroutine_function()
2789-
with contextlib.closing(asyncio.EventLoop()) as loop:
2790-
task = asyncio.Task.__new__(asyncio.Task)
2791-
for _ in range(5):
2792-
with self.assertRaisesRegex(RuntimeError, 'break'):
2793-
task.__init__(coro, loop=loop, context=obj, name=Break())
2794-
2795-
coro.close()
2796-
task._log_destroy_pending = False
2797-
del task
2779+
def test_task_disallow_multiple_initialization(self):
2780+
async def foo():
2781+
pass
27982782

2799-
self.assertEqual(sys.getrefcount(obj), initial_refcount)
2783+
coro = foo()
2784+
self.addCleanup(coro.close)
2785+
task = self.new_task(self.loop, coro)
2786+
task._log_destroy_pending = False
28002787

2788+
with self.assertRaises(RuntimeError, msg="is already initialized"):
2789+
task.__init__(coro, loop=self.loop)
28012790

28022791
def add_subclass_tests(cls):
28032792
BaseTask = cls.Task
@@ -2921,19 +2910,6 @@ class CTask_CFuture_Tests(BaseTaskTests, SetMethodsTest,
29212910
all_tasks = getattr(tasks, '_c_all_tasks', None)
29222911
current_task = staticmethod(getattr(tasks, '_c_current_task', None))
29232912

2924-
@support.refcount_test
2925-
def test_refleaks_in_task___init__(self):
2926-
gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount')
2927-
async def coro():
2928-
pass
2929-
task = self.new_task(self.loop, coro())
2930-
self.loop.run_until_complete(task)
2931-
refs_before = gettotalrefcount()
2932-
for i in range(100):
2933-
task.__init__(coro(), loop=self.loop)
2934-
self.loop.run_until_complete(task)
2935-
self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)
2936-
29372913
def test_del__log_destroy_pending_segfault(self):
29382914
async def coro():
29392915
pass
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix possible crashes when initializing :class:`asyncio.Task` or :class:`asyncio.Future` multiple times.
2+
These classes can now be initialized only once and any subsequent initialization attempt will raise a RuntimeError.
3+
Patch by Kumar Aditya.

Modules/_asynciomodule.c

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -498,21 +498,13 @@ future_schedule_callbacks(asyncio_state *state, FutureObj *fut)
498498
static int
499499
future_init(FutureObj *fut, PyObject *loop)
500500
{
501+
if (fut->fut_loop != NULL) {
502+
PyErr_Format(PyExc_RuntimeError, "%T object is already initialized", fut);
503+
return -1;
504+
}
505+
501506
PyObject *res;
502507
int is_true;
503-
504-
Py_CLEAR(fut->fut_loop);
505-
Py_CLEAR(fut->fut_callback0);
506-
Py_CLEAR(fut->fut_context0);
507-
Py_CLEAR(fut->fut_callbacks);
508-
Py_CLEAR(fut->fut_result);
509-
Py_CLEAR(fut->fut_exception);
510-
Py_CLEAR(fut->fut_exception_tb);
511-
Py_CLEAR(fut->fut_source_tb);
512-
Py_CLEAR(fut->fut_cancel_msg);
513-
Py_CLEAR(fut->fut_cancelled_exc);
514-
Py_CLEAR(fut->fut_awaited_by);
515-
516508
fut->fut_state = STATE_PENDING;
517509
fut->fut_log_tb = 0;
518510
fut->fut_blocking = 0;
@@ -3008,11 +3000,7 @@ task_call_step_soon(asyncio_state *state, TaskObj *task, PyObject *arg)
30083000
return -1;
30093001
}
30103002

3011-
// Beware: An evil call_soon could alter task_context.
3012-
// See: https://github.com/python/cpython/issues/126080.
3013-
PyObject *task_context = Py_NewRef(task->task_context);
3014-
int ret = call_soon(state, task->task_loop, cb, NULL, task_context);
3015-
Py_DECREF(task_context);
3003+
int ret = call_soon(state, task->task_loop, cb, NULL, task->task_context);
30163004
Py_DECREF(cb);
30173005
return ret;
30183006
}

0 commit comments

Comments
 (0)