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 23:33:00 2016 +0200 @@ -1055,6 +1055,87 @@ 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_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_dictitems_contains_use_after_free(self): + class X: + def __eq__(self, other): + d.clear() + return NotImplemented + + d = {0: set()} + (0, X()) in d.items() + + 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) + class CAPITest(unittest.TestCase): 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 23:33:00 2016 +0200 @@ -1115,18 +1115,23 @@ insertdict(PyDictObject *mp, PyObject *k PyDictKeyEntry *ep, *ep0; Py_ssize_t hashpos, ix; + Py_INCREF(key); + Py_INCREF(value); if (mp->ma_values != NULL && !PyUnicode_CheckExact(key)) { - if (insertion_resize(mp) < 0) + if (insertion_resize(mp) < 0) { + Py_DECREF(value); + Py_DECREF(key); return -1; + } } - ix = mp->ma_keys->dk_lookup(mp, key, hash, &value_addr, &hashpos); if (ix == DKIX_ERROR) { + Py_DECREF(value); + Py_DECREF(key); return -1; } assert(PyUnicode_CheckExact(key) || mp->ma_keys->dk_lookup == lookdict); - Py_INCREF(value); MAINTAIN_TRACKING(mp, key, value); /* When insertion order is different from shared key, we can't share @@ -1137,6 +1142,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 +1155,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 +1179,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 +1192,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 +1203,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 +1847,7 @@ PyObject * if (PyDict_CheckExact(iterable)) { PyDictObject *mp = (PyDictObject *)d; PyObject *oldvalue; - Py_ssize_t pos = 0; + Py_ssize_t pos = 0, it_size; PyObject *key; Py_hash_t hash; @@ -1846,17 +1856,24 @@ PyObject * return NULL; } + it_size = ((PyDictObject *)iterable)->ma_used; while (_PyDict_Next(iterable, &pos, &key, &oldvalue, &hash)) { if (insertdict(mp, key, hash, value)) { 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; PyObject *key; Py_hash_t hash; @@ -1865,11 +1882,18 @@ PyObject * return NULL; } + it_size = ((PySetObject *)iterable)->used; while (_PySet_NextEntry(iterable, &pos, &key, &hash)) { if (insertdict(mp, key, hash, value)) { Py_DECREF(d); return NULL; } + if (((PySetObject *)iterable)->used != it_size) { + Py_DECREF(d); + PyErr_SetString(PyExc_RuntimeError, + "set changed size during iteration"); + return NULL; + } } return d; } @@ -2344,11 +2368,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 +2680,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 +2689,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; } @@ -3539,7 +3571,7 @@ PyTypeObject PyDictIterValue_Type = { static PyObject * dictiter_iternextitem(dictiterobject *di) { - PyObject *key, *value, *result = di->di_result; + PyObject *key, *value, *result; Py_ssize_t i; PyDictObject *d = di->di_dict; @@ -3577,20 +3609,25 @@ dictiter_iternextitem(dictiterobject *di } di->di_pos = i+1; di->len--; - if (result->ob_refcnt == 1) { + Py_INCREF(key); + Py_INCREF(value); + result = di->di_result; + if (Py_REFCNT(result) == 1) { + PyObject *oldkey = PyTuple_GET_ITEM(result, 0); + PyObject *oldvalue = PyTuple_GET_ITEM(result, 1); + PyTuple_SET_ITEM(result, 0, key); /* steals reference */ + PyTuple_SET_ITEM(result, 1, value); /* steals reference */ Py_INCREF(result); - Py_DECREF(PyTuple_GET_ITEM(result, 0)); - Py_DECREF(PyTuple_GET_ITEM(result, 1)); + Py_DECREF(oldkey); + Py_DECREF(oldvalue); } else { result = PyTuple_New(2); if (result == NULL) return NULL; + PyTuple_SET_ITEM(result, 0, key); /* steals reference */ + PyTuple_SET_ITEM(result, 1, value); /* steals reference */ } - Py_INCREF(key); - Py_INCREF(value); - PyTuple_SET_ITEM(result, 0, key); /* steals reference */ - PyTuple_SET_ITEM(result, 1, value); /* steals reference */ return result; fail: @@ -4083,6 +4120,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 +4134,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 = {