Skip to content

Commit

Permalink
[2.7] bpo-27945: Fixed various segfaults with dict. (GH-1657) (#1681)
Browse files Browse the repository at this point in the history
Based on patches by Duane Griffin and Tim Mitchell.
(cherry picked from commit 753bca3)
  • Loading branch information
serhiy-storchaka committed May 20, 2017
1 parent e9f9b04 commit e6a0b59
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 14 deletions.
91 changes: 91 additions & 0 deletions Lib/test/test_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,97 @@ def test_free_after_iterating(self):
test_support.check_free_after_iterating(self, lambda d: iter(d.viewvalues()), dict)
test_support.check_free_after_iterating(self, lambda d: iter(d.viewitems()), dict)

def test_equal_operator_modifying_operand(self):
# test fix for seg fault reported in issue 27945 part 3.
class X(object):
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}
try:
dict.fromkeys(d) # shouldn't crash
except RuntimeError: # implementation defined
pass

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), X(2)}
try:
dict.fromkeys(d) # shouldn't crash
except RuntimeError: # implementation defined
pass

def test_dictitems_contains_use_after_free(self):
class X(object):
def __eq__(self, other):
d.clear()
return NotImplemented

__hash__ = object.__hash__ # silence Py3k warning

d = {0: set()}
try:
(0, X()) in d.iteritems() # shouldn't crash
except RuntimeError: # implementation defined
pass

def test_init_use_after_free(self):
class X(object):
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.iteritems():
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):
Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,7 @@ Hans de Graaff
Tim Graham
Nathaniel Gray
Eddy De Greef
Duane Griffin
Grant Griffin
Andrea Griffini
Duncan Grisby
Expand Down
4 changes: 4 additions & 0 deletions Misc/NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ What's New in Python 2.7.14?
Core and Builtins
-----------------

- bpo-27945: Fixed various segfaults with dict when input collections are
mutated during searching, inserting or comparing. Based on patches by
Duane Griffin and Tim Mitchell.

- bpo-25794: Fixed type.__setattr__() and type.__delattr__() for
non-interned or unicode attribute names. Based on patch by Eryk Sun.

Expand Down
45 changes: 31 additions & 14 deletions Objects/dictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1595,11 +1595,18 @@ PyDict_MergeFromSeq2(PyObject *d, PyObject *seq2, int override)
/* 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);
}
Expand Down Expand Up @@ -1938,12 +1945,13 @@ dict_equal(PyDictObject *a, PyDictObject *b)
/* ditto for key */
Py_INCREF(key);
bval = PyDict_GetItem((PyObject *)b, key);
Py_DECREF(key);
if (bval == NULL) {
Py_DECREF(key);
Py_DECREF(aval);
return 0;
}
cmp = PyObject_RichCompareBool(aval, bval, Py_EQ);
Py_DECREF(key);
Py_DECREF(aval);
if (cmp <= 0) /* error or not equal */
return cmp;
Expand Down Expand Up @@ -2743,7 +2751,7 @@ PyTypeObject PyDictIterValue_Type = {

static PyObject *dictiter_iternextitem(dictiterobject *di)
{
PyObject *key, *value, *result = di->di_result;
PyObject *key, *value, *result;
register Py_ssize_t i, mask;
register PyDictEntry *ep;
PyDictObject *d = di->di_dict;
Expand All @@ -2770,22 +2778,27 @@ static PyObject *dictiter_iternextitem(dictiterobject *di)
if (i > mask)
goto fail;

if (result->ob_refcnt == 1) {
di->len--;
key = ep[i].me_key;
value = ep[i].me_value;
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 */
}
di->len--;
key = ep[i].me_key;
value = ep[i].me_value;
Py_INCREF(key);
Py_INCREF(value);
PyTuple_SET_ITEM(result, 0, key);
PyTuple_SET_ITEM(result, 1, value);
return result;

fail:
Expand Down Expand Up @@ -3186,6 +3199,7 @@ dictitems_iter(dictviewobject *dv)
static int
dictitems_contains(dictviewobject *dv, PyObject *obj)
{
int result;
PyObject *key, *value, *found;
if (dv->dv_dict == NULL)
return 0;
Expand All @@ -3199,7 +3213,10 @@ dictitems_contains(dictviewobject *dv, PyObject *obj)
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 = {
Expand Down

0 comments on commit e6a0b59

Please sign in to comment.