Skip to content

[2.7] bpo-27945: Fixed various segfaults with dict. (GH-1657) #1681

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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