mirror of https://github.com/python/cpython
gh-125969: fix OOB in `future_schedule_callbacks` due to an evil `call_soon` (#125970)
Co-authored-by: Andrew Svetlov <andrew.svetlov@gmail.com>
This commit is contained in:
parent
1384409460
commit
c5b99f5c2c
|
@ -947,6 +947,39 @@ class BaseFutureDoneCallbackTests():
|
||||||
|
|
||||||
fut.remove_done_callback(evil())
|
fut.remove_done_callback(evil())
|
||||||
|
|
||||||
|
def test_evil_call_soon_list_mutation(self):
|
||||||
|
called_on_fut_callback0 = False
|
||||||
|
|
||||||
|
pad = lambda: ...
|
||||||
|
|
||||||
|
def evil_call_soon(*args, **kwargs):
|
||||||
|
nonlocal called_on_fut_callback0
|
||||||
|
if called_on_fut_callback0:
|
||||||
|
# Called when handling fut->fut_callbacks[0]
|
||||||
|
# and mutates the length fut->fut_callbacks.
|
||||||
|
fut.remove_done_callback(int)
|
||||||
|
fut.remove_done_callback(pad)
|
||||||
|
else:
|
||||||
|
called_on_fut_callback0 = True
|
||||||
|
|
||||||
|
fake_event_loop = lambda: ...
|
||||||
|
fake_event_loop.call_soon = evil_call_soon
|
||||||
|
fake_event_loop.get_debug = lambda: False # suppress traceback
|
||||||
|
|
||||||
|
with mock.patch.object(self, 'loop', fake_event_loop):
|
||||||
|
fut = self._new_future()
|
||||||
|
self.assertIs(fut.get_loop(), fake_event_loop)
|
||||||
|
|
||||||
|
fut.add_done_callback(str) # sets fut->fut_callback0
|
||||||
|
fut.add_done_callback(int) # sets fut->fut_callbacks[0]
|
||||||
|
fut.add_done_callback(pad) # sets fut->fut_callbacks[1]
|
||||||
|
fut.add_done_callback(pad) # sets fut->fut_callbacks[2]
|
||||||
|
fut.set_result("boom")
|
||||||
|
|
||||||
|
# When there are no more callbacks, the Python implementation
|
||||||
|
# returns an empty list but the C implementation returns None.
|
||||||
|
self.assertIn(fut._callbacks, (None, []))
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(hasattr(futures, '_CFuture'),
|
@unittest.skipUnless(hasattr(futures, '_CFuture'),
|
||||||
'requires the C _asyncio module')
|
'requires the C _asyncio module')
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Fix an out-of-bounds crash when an evil :meth:`asyncio.loop.call_soon`
|
||||||
|
mutates the length of the internal callbacks list. Patch by Bénédikt Tran.
|
|
@ -406,9 +406,6 @@ future_ensure_alive(FutureObj *fut)
|
||||||
static int
|
static int
|
||||||
future_schedule_callbacks(asyncio_state *state, FutureObj *fut)
|
future_schedule_callbacks(asyncio_state *state, FutureObj *fut)
|
||||||
{
|
{
|
||||||
Py_ssize_t len;
|
|
||||||
Py_ssize_t i;
|
|
||||||
|
|
||||||
if (fut->fut_callback0 != NULL) {
|
if (fut->fut_callback0 != NULL) {
|
||||||
/* There's a 1st callback */
|
/* There's a 1st callback */
|
||||||
|
|
||||||
|
@ -434,27 +431,25 @@ future_schedule_callbacks(asyncio_state *state, FutureObj *fut)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
len = PyList_GET_SIZE(fut->fut_callbacks);
|
// Beware: An evil call_soon could change fut->fut_callbacks.
|
||||||
if (len == 0) {
|
// The idea is to transfer the ownership of the callbacks list
|
||||||
/* The list of callbacks was empty; clear it and return. */
|
// so that external code is not able to mutate the list during
|
||||||
Py_CLEAR(fut->fut_callbacks);
|
// the iteration.
|
||||||
return 0;
|
PyObject *callbacks = fut->fut_callbacks;
|
||||||
}
|
fut->fut_callbacks = NULL;
|
||||||
|
Py_ssize_t n = PyList_GET_SIZE(callbacks);
|
||||||
for (i = 0; i < len; i++) {
|
for (Py_ssize_t i = 0; i < n; i++) {
|
||||||
PyObject *cb_tup = PyList_GET_ITEM(fut->fut_callbacks, i);
|
assert(PyList_GET_SIZE(callbacks) == n);
|
||||||
|
PyObject *cb_tup = PyList_GET_ITEM(callbacks, i);
|
||||||
PyObject *cb = PyTuple_GET_ITEM(cb_tup, 0);
|
PyObject *cb = PyTuple_GET_ITEM(cb_tup, 0);
|
||||||
PyObject *ctx = PyTuple_GET_ITEM(cb_tup, 1);
|
PyObject *ctx = PyTuple_GET_ITEM(cb_tup, 1);
|
||||||
|
|
||||||
if (call_soon(state, fut->fut_loop, cb, (PyObject *)fut, ctx)) {
|
if (call_soon(state, fut->fut_loop, cb, (PyObject *)fut, ctx)) {
|
||||||
/* If an error occurs in pure-Python implementation,
|
Py_DECREF(callbacks);
|
||||||
all callbacks are cleared. */
|
|
||||||
Py_CLEAR(fut->fut_callbacks);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Py_DECREF(callbacks);
|
||||||
Py_CLEAR(fut->fut_callbacks);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue