From b1556685d80a6ad496a7ff2616b9b75939d838ef Mon Sep 17 00:00:00 2001 From: Duane Griffin Date: Mon, 12 Sep 2016 11:33:49 +1200 Subject: [PATCH 1/3] Issue #27945: fix dictiter_iternextitem use-after-free Objects being freed may mutate the dictionary during the iteration. Check for and handle this case. --- Lib/test/test_dict.py | 14 ++++++++++++++ Objects/dictobject.c | 22 ++++++++++++++++------ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index fb954c8..5ab0a57 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -1029,6 +1029,20 @@ class DictTest(unittest.TestCase): support.check_free_after_iterating(self, lambda d: iter(d.values()), dict) support.check_free_after_iterating(self, lambda d: iter(d.items()), dict) + def test_oob_indexing_dictiter_iternextitem(self): + class X(int): + def __del__(self): + d.clear() + + d = {i: X(i) for i in range(8)} + + def iter_and_mutate(): + for result in d.items(): + if result[0] == 2: + d[2] = None # free d[2] --> X(2).__del__ was called + + self.assertRaises(RuntimeError, iter_and_mutate) + from test import mapping_tests class GeneralMappingTests(mapping_tests.BasicTestMappingProtocol): diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 4bcc3db..9089867 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -3407,12 +3407,8 @@ static PyObject *dictiter_iternextitem(dictiterobject *di) return NULL; assert (PyDict_Check(d)); - if (di->di_used != d->ma_used) { - PyErr_SetString(PyExc_RuntimeError, - "dictionary changed size during iteration"); - di->di_used = -1; /* Make this state sticky */ - return NULL; - } + if (di->di_used != d->ma_used) + goto mutated; i = di->di_pos; if (i < 0) @@ -3438,6 +3434,14 @@ static PyObject *dictiter_iternextitem(dictiterobject *di) Py_INCREF(result); Py_DECREF(PyTuple_GET_ITEM(result, 0)); Py_DECREF(PyTuple_GET_ITEM(result, 1)); + + /* note that above may have mutated the dictionary (see #27945) */ + if (di->di_used != d->ma_used) { + PyTuple_SET_ITEM(result, 0, NULL); + PyTuple_SET_ITEM(result, 1, NULL); + Py_DECREF(result); + goto mutated; + } } else { result = PyTuple_New(2); if (result == NULL) @@ -3452,6 +3456,12 @@ static PyObject *dictiter_iternextitem(dictiterobject *di) PyTuple_SET_ITEM(result, 1, value); /* steals reference */ return result; +mutated: + PyErr_SetString(PyExc_RuntimeError, + "dictionary changed size during iteration"); + di->di_used = -1; /* Make this state sticky */ + return NULL; + fail: di->di_dict = NULL; Py_DECREF(d); -- 2.10.0