cpython icon indicating copy to clipboard operation
cpython copied to clipboard

gh-142734: fix UAF in `OrderedDict.copy` with concurrent mutations by `__getitem__`

Open fatelei opened this issue 3 weeks ago • 3 comments

fix OrderedDict copy heap-use-after-free security issue

  • Issue: gh-142734

fatelei avatar Dec 22 '25 07:12 fatelei

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

bedevere-app[bot] avatar Dec 27 '25 17:12 bedevere-app[bot]

This implementation is not efficient. I think it's better that we just bail during the copy (that is, raise) if we detect a state change (if possible). We had a similar patch in __eq__.

update

        PyODictObject *self = _PyODictObject_CAST(od);
        size_t state = self->od_state;
        _ODictNode *cur = _odict_FIRST(od);

        while (cur != NULL) {
            if (self->od_state != state) {
                PyErr_SetString(PyExc_RuntimeError,
                                "OrderedDict mutated during iteration");
                goto fail;
            }
            PyObject *key = Py_NewRef(_odictnode_KEY(cur));
            PyObject *value = PyObject_GetItem((PyObject *)od, key);
            if (value == NULL) {
                Py_DECREF(key);
                goto fail;
            }
            if (self->od_state != state) {
                Py_DECREF(key);
                Py_DECREF(value);
                PyErr_SetString(PyExc_RuntimeError,
                                "OrderedDict mutated during iteration");
                goto fail;
            }
            if (PyObject_SetItem((PyObject *)od_copy, key, value) != 0) {
                Py_DECREF(key);
                Py_DECREF(value);
                goto fail;
            }
            Py_DECREF(key);
            Py_DECREF(value);

            cur = _odictnode_NEXT(cur);
        }

fatelei avatar Dec 28 '25 08:12 fatelei

Please add tests when the dictionary is shrinked or grows during the iteration (not just cleared)

test update

fatelei avatar Dec 28 '25 14:12 fatelei