diff -r 3248782c3176 Lib/test/test_dict.py --- a/Lib/test/test_dict.py Sat Nov 19 16:20:31 2016 +0100 +++ b/Lib/test/test_dict.py Sat Nov 19 20:48:25 2016 +0200 @@ -1049,12 +1049,70 @@ class DictTest(unittest.TestCase): d = {X(): 0, 1: 1} self.assertRaises(RuntimeError, d.update, other) + def test_equal_operator_modifying_operand(self): + # test fix for seg fault reported in issue 27945 part 3. + class X(): + def __del__(self): + dict_b.clear() + + def __eq__(self, other): + dict_a.clear() + return True + + def __hash__(self): + return 13 + + dict_a = {X(): 0} + dict_b = {X(): X()} + self.assertTrue(dict_a == dict_b) + + def test_fromkeys_operator_modifying_dict_operand(self): + # test fix for seg fault reported in issue 27945 part 4a. + class X(int): + def __hash__(self): + return 13 + + def __eq__(self, other): + if len(d) > 1: + d.clear() + return False + + d = {} # this is required to exist so that d can be constructed! + d = {X(1): 1, X(2): 2} + with self.assertRaises(RuntimeError): + dict.fromkeys(d) + + def test_fromkeys_operator_modifying_set_operand(self): + # test fix for seg fault reported in issue 27945 part 4b. + class X(int): + def __hash__(self): + return 13 + + def __eq__(self, other): + if len(d) > 1: + d.clear() + return False + + d = {} # this is required to exist so that d can be constructed! + d = {X(1): 1, X(2): 2} + with self.assertRaises(RuntimeError): + dict.fromkeys(d) + def test_free_after_iterating(self): support.check_free_after_iterating(self, iter, dict) support.check_free_after_iterating(self, lambda d: iter(d.keys()), dict) 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_dictitems_contains_use_after_free(self): + class X: + def __eq__(self, other): + d.clear() + return NotImplemented + + d = {0: set()} + (0, X()) in d.items() + class CAPITest(unittest.TestCase): @@ -1086,6 +1144,29 @@ class CAPITest(unittest.TestCase): self.assertRaises(Exc, dict_getitem_knownhash, d, k2, hash(k2)) + def test_init_use_after_free(self): + class X: + def __hash__(self): + pair[:] = [] + return 13 + + pair = [X(), 123] + dict([pair]) + + 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 -r 3248782c3176 Objects/dictobject.c --- a/Objects/dictobject.c Sat Nov 19 16:20:31 2016 +0100 +++ b/Objects/dictobject.c Sat Nov 19 20:48:25 2016 +0200 @@ -1119,9 +1119,10 @@ insertdict(PyDictObject *mp, PyObject *k if (insertion_resize(mp) < 0) return -1; } - + Py_INCREF(key); ix = mp->ma_keys->dk_lookup(mp, key, hash, &value_addr, &hashpos); if (ix == DKIX_ERROR) { + Py_DECREF(key); return -1; } @@ -1137,6 +1138,7 @@ insertdict(PyDictObject *mp, PyObject *k (ix == DKIX_EMPTY && mp->ma_used != mp->ma_keys->dk_nentries))) { if (insertion_resize(mp) < 0) { Py_DECREF(value); + Py_DECREF(key); return -1; } find_empty_slot(mp, key, hash, &value_addr, &hashpos); @@ -1149,6 +1151,7 @@ insertdict(PyDictObject *mp, PyObject *k /* Need to resize. */ if (insertion_resize(mp) < 0) { Py_DECREF(value); + Py_DECREF(key); return -1; } find_empty_slot(mp, key, hash, &value_addr, &hashpos); @@ -1172,6 +1175,7 @@ insertdict(PyDictObject *mp, PyObject *k mp->ma_keys->dk_nentries++; assert(mp->ma_keys->dk_usable >= 0); assert(_PyDict_CheckConsistency(mp)); + Py_DECREF(key); return 0; } @@ -1184,6 +1188,7 @@ insertdict(PyDictObject *mp, PyObject *k assert(_PyDict_CheckConsistency(mp)); Py_DECREF(old_value); /* which **CAN** re-enter (see issue #22653) */ + Py_DECREF(key); return 0; } @@ -1194,6 +1199,7 @@ insertdict(PyDictObject *mp, PyObject *k mp->ma_used++; mp->ma_version_tag = DICT_NEXT_VERSION(); assert(_PyDict_CheckConsistency(mp)); + Py_DECREF(key); return 0; } @@ -1837,7 +1843,7 @@ PyObject * if (PyDict_CheckExact(iterable)) { PyDictObject *mp = (PyDictObject *)d; PyObject *oldvalue; - Py_ssize_t pos = 0; + Py_ssize_t pos = 0, it_size = ((PyDictObject *)iterable)->ma_used; PyObject *key; Py_hash_t hash; @@ -1851,12 +1857,18 @@ PyObject * Py_DECREF(d); return NULL; } + if (((PyDictObject *)iterable)->ma_used != it_size) { + Py_DECREF(d); + PyErr_SetString(PyExc_RuntimeError, + "dictionary changed size during iteration"); + return NULL; + } } return d; } if (PyAnySet_CheckExact(iterable)) { PyDictObject *mp = (PyDictObject *)d; - Py_ssize_t pos = 0; + Py_ssize_t pos = 0, it_size = ((PyDictObject *)iterable)->ma_used; PyObject *key; Py_hash_t hash; @@ -1870,6 +1882,12 @@ PyObject * Py_DECREF(d); return NULL; } + if (((PyDictObject *)iterable)->ma_used != it_size) { + Py_DECREF(d); + PyErr_SetString(PyExc_RuntimeError, + "set changed size during iteration"); + return NULL; + } } return d; } @@ -2344,11 +2362,18 @@ PyDict_MergeFromSeq2(PyObject *d, PyObje /* Update/merge with this (key, value) pair. */ key = PySequence_Fast_GET_ITEM(fast, 0); value = PySequence_Fast_GET_ITEM(fast, 1); + Py_INCREF(key); + Py_INCREF(value); if (override || PyDict_GetItem(d, key) == NULL) { int status = PyDict_SetItem(d, key, value); - if (status < 0) + if (status < 0) { + Py_DECREF(key); + Py_DECREF(value); goto Fail; + } } + Py_DECREF(key); + Py_DECREF(value); Py_DECREF(fast); Py_DECREF(item); } @@ -2649,8 +2674,8 @@ dict_equal(PyDictObject *a, PyDictObject bval = NULL; else bval = *vaddr; - Py_DECREF(key); if (bval == NULL) { + Py_DECREF(key); Py_DECREF(aval); if (PyErr_Occurred()) return -1; @@ -2658,6 +2683,7 @@ dict_equal(PyDictObject *a, PyDictObject } cmp = PyObject_RichCompareBool(aval, bval, Py_EQ); Py_DECREF(aval); + Py_DECREF(key); if (cmp <= 0) /* error or not equal */ return cmp; } @@ -3547,12 +3573,8 @@ 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; assert(i >= 0); @@ -3581,6 +3603,14 @@ 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); @@ -3593,6 +3623,12 @@ 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); @@ -4083,6 +4119,7 @@ dictitems_iter(_PyDictViewObject *dv) static int dictitems_contains(_PyDictViewObject *dv, PyObject *obj) { + int result; PyObject *key, *value, *found; if (dv->dv_dict == NULL) return 0; @@ -4096,7 +4133,10 @@ dictitems_contains(_PyDictViewObject *dv return -1; return 0; } - return PyObject_RichCompareBool(value, found, Py_EQ); + Py_INCREF(found); + result = PyObject_RichCompareBool(value, found, Py_EQ); + Py_DECREF(found); + return result; } static PySequenceMethods dictitems_as_sequence = {