Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork33.7k
gh-125789: fix side-effects inasyncio callback scheduling methods#125833
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes from1 commit
be7cac9ca61908640c799a05cde222b21ccf7b6730f72c393378fe09File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
- Loading branch information
Uh oh!
There was an error while loading.Please reload this page.
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -929,6 +929,47 @@ def __eq__(self, other): | ||
| fut.remove_done_callback(evil()) | ||
| def test_schedule_callbacks_list_mutation_3(self): | ||
| raise NotImplemented | ||
| def _test_schedule_callbacks_list_mutation_3(self, exc_type, exc_text=None): | ||
| # see https://github.com/python/cpython/issues/125789 for details | ||
| fut = self._new_future() | ||
| class evil: | ||
| def __eq__(self, other): | ||
| global mem | ||
| mem = other | ||
| return False | ||
| cb_pad = lambda: ... | ||
| fut.add_done_callback(cb_pad) # sets fut->fut_callback0 | ||
| fut.add_done_callback(evil()) # sets first item in fut->fut_callbacks | ||
| # Consume fut->fut_callback0 callback but checks the remaining callbacks, | ||
| # thereby invoking evil.__eq__(). | ||
| fut.remove_done_callback(cb_pad) | ||
| self.assertIs(mem, cb_pad) | ||
| fake = ( | ||
| (0).to_bytes(8, 'little') + | ||
| id(bytearray).to_bytes(8, 'little') + | ||
| (2 ** 63 - 1).to_bytes(8, 'little') + | ||
| (0).to_bytes(24, 'little') | ||
| ) | ||
| i2f = lambda num: 5e-324 * num | ||
| fut._callbacks[0] = complex(0, i2f(id(fake) + bytes.__basicsize__ - 1)) | ||
| # We want to call once again evil.__eq__() to set 'mem' to our | ||
| # malicious bytearray. However, since we manually modified the | ||
| # callbacks list, we will not be able to by-pass the checks. | ||
| if exc_text is None: | ||
| self.assertRaises(exc_type, fut.remove_done_callback, evil()) | ||
| else: | ||
| self.assertRaisesRegex(exc_type, exc_text, fut.remove_done_callback, evil()) | ||
| self.assertIs(mem, cb_pad) | ||
| @unittest.skipUnless(hasattr(futures, '_CFuture'), | ||
| 'requires the C _asyncio module') | ||
| @@ -938,6 +979,9 @@ class CFutureDoneCallbackTests(BaseFutureDoneCallbackTests, | ||
| def _new_future(self): | ||
| return futures._CFuture(loop=self.loop) | ||
| def test_schedule_callbacks_list_mutation_3(self): | ||
| super()._test_schedule_callbacks_list_mutation_3(RuntimeError, 'corrupted') | ||
| @unittest.skipUnless(hasattr(futures, '_CFuture'), | ||
| 'requires the C _asyncio module') | ||
| @@ -949,13 +993,19 @@ class CSubFuture(futures._CFuture): | ||
| pass | ||
| return CSubFuture(loop=self.loop) | ||
| def test_schedule_callbacks_list_mutation_3(self): | ||
| super()._test_schedule_callbacks_list_mutation_3(RuntimeError, 'corrupted') | ||
picnixz marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| class PyFutureDoneCallbackTests(BaseFutureDoneCallbackTests, | ||
| test_utils.TestCase): | ||
| def _new_future(self): | ||
| return futures._PyFuture(loop=self.loop) | ||
| def test_schedule_callbacks_list_mutation_3(self): | ||
| super()._test_schedule_callbacks_list_mutation_3(TypeError) | ||
| class BaseFutureInheritanceTests: | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -434,15 +434,33 @@ future_schedule_callbacks(asyncio_state *state, FutureObj *fut) | ||
| return 0; | ||
| } | ||
| if (!PyList_Check(fut->fut_callbacks)) { | ||
picnixz marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| PyErr_SetString(PyExc_RuntimeError, "corrupted future state"); | ||
picnixz marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| return -1; | ||
| } | ||
| len = PyList_GET_SIZE(fut->fut_callbacks); | ||
| if (len == 0) { | ||
| /* The list of callbacks was empty; clear it and return. */ | ||
| Py_CLEAR(fut->fut_callbacks); | ||
| return 0; | ||
| } | ||
| // Beware: 'call_soon' below may change fut_callbacks or its items | ||
| // (see https://github.com/python/cpython/issues/125789 for details). | ||
| for (i = 0; fut->fut_callbacks != NULL; i++) { | ||
| if (!PyList_Check(fut->fut_callbacks)) { | ||
picnixz marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| PyErr_SetString(PyExc_RuntimeError, "corrupted future state"); | ||
| return -1; | ||
| } | ||
| if (i >= PyList_GET_SIZE(fut->fut_callbacks)) { | ||
| break; // done | ||
| } | ||
| PyObject *cb_tup = PyList_GET_ITEM(fut->fut_callbacks, i); | ||
| if (!PyTuple_Check(cb_tup) || PyTuple_GET_SIZE(cb_tup) < 2) { | ||
| PyErr_SetString(PyExc_RuntimeError, "corrupted future state"); | ||
| return -1; | ||
| } | ||
| PyObject *cb = PyTuple_GET_ITEM(cb_tup, 0); | ||
| PyObject *ctx = PyTuple_GET_ITEM(cb_tup, 1); | ||
| @@ -1033,6 +1051,11 @@ _asyncio_Future_remove_done_callback_impl(FutureObj *self, PyTypeObject *cls, | ||
| return PyLong_FromSsize_t(cleared_callback0); | ||
| } | ||
| if (!PyList_Check(self->fut_callbacks)) { | ||
picnixz marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| PyErr_SetString(PyExc_RuntimeError, "corrupted future state"); | ||
| return NULL; | ||
| } | ||
| len = PyList_GET_SIZE(self->fut_callbacks); | ||
| if (len == 0) { | ||
| Py_CLEAR(self->fut_callbacks); | ||
| @@ -1041,8 +1064,15 @@ _asyncio_Future_remove_done_callback_impl(FutureObj *self, PyTypeObject *cls, | ||
| if (len == 1) { | ||
| PyObject *cb_tup = PyList_GET_ITEM(self->fut_callbacks, 0); | ||
| // Beware: PyObject_RichCompareBool below may change fut_callbacks or | ||
picnixz marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| // its items (see https://github.com/python/cpython/issues/97592 and | ||
| // https://github.com/python/cpython/issues/125789 for details). | ||
| if (!PyTuple_Check(cb_tup) || PyTuple_GET_SIZE(cb_tup) < 1) { | ||
| PyErr_SetString(PyExc_RuntimeError, "corrupted future state"); | ||
picnixz marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| return NULL; | ||
| } | ||
| PyObject *cb = PyTuple_GET_ITEM(cb_tup, 0); | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. A little nitpicky, but the incref should be on | ||
| int cmp = PyObject_RichCompareBool(cb, fn, Py_EQ); | ||
| if (cmp == -1) { | ||
| return NULL; | ||
| } | ||
| @@ -1060,24 +1090,34 @@ _asyncio_Future_remove_done_callback_impl(FutureObj *self, PyTypeObject *cls, | ||
| return NULL; | ||
| } | ||
| // Beware: PyObject_RichCompareBool below may change fut_callbacks or | ||
picnixz marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| // its items (see https://github.com/python/cpython/issues/97592 and | ||
| // https://github.com/python/cpython/issues/125789 for details). | ||
| for (i = 0; self->fut_callbacks != NULL; i++) { | ||
| if (!PyList_Check(self->fut_callbacks)) { | ||
picnixz marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| PyErr_SetString(PyExc_RuntimeError, "corrupted future state"); | ||
| goto fail; | ||
| } | ||
| if (i >= PyList_GET_SIZE(self->fut_callbacks)) { | ||
| break; // done | ||
| } | ||
| PyObject *cb_tup = PyList_GET_ITEM(self->fut_callbacks, i); | ||
| if (!PyTuple_Check(cb_tup) || PyTuple_GET_SIZE(cb_tup) < 1) { | ||
picnixz marked this conversation as resolved. OutdatedShow resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| PyErr_SetString(PyExc_RuntimeError, "corrupted future state"); | ||
| goto fail; | ||
| } | ||
| Py_INCREF(cb_tup); | ||
| PyObject *cb = PyTuple_GET_ITEM(cb_tup, 0); | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Same thing here, I don't think it's possible to delete | ||
| int ret = PyObject_RichCompareBool(cb, fn, Py_EQ); | ||
| if (ret == 0) { | ||
| if (j < len) { | ||
| PyList_SET_ITEM(newlist, j,cb_tup); | ||
| j++; | ||
| continue; | ||
| } | ||
| ret = PyList_Append(newlist,cb_tup); | ||
| } | ||
| Py_DECREF(cb_tup); | ||
| if (ret < 0) { | ||
| goto fail; | ||
| } | ||