Skip to content

Commit 8fbdab5

Browse files
serhiy-storchakaned-deily
authored andcommitted
[3.3] [3.5] bpo-27945: Fixed various segfaults with dict. (GH-1657) (GH-1678) (#2396)
Based on patches by Duane Griffin and Tim Mitchell. (cherry picked from commit 753bca3). (cherry picked from commit 2f7f533)
1 parent 052f9d6 commit 8fbdab5

File tree

4 files changed

+143
-29
lines changed

4 files changed

+143
-29
lines changed

Lib/test/test_dict.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,92 @@ def __eq__(self, o):
921921
d = {X(): 0, 1: 1}
922922
self.assertRaises(RuntimeError, d.update, other)
923923

924+
def test_equal_operator_modifying_operand(self):
925+
# test fix for seg fault reported in issue 27945 part 3.
926+
class X():
927+
def __del__(self):
928+
dict_b.clear()
929+
930+
def __eq__(self, other):
931+
dict_a.clear()
932+
return True
933+
934+
def __hash__(self):
935+
return 13
936+
937+
dict_a = {X(): 0}
938+
dict_b = {X(): X()}
939+
self.assertTrue(dict_a == dict_b)
940+
941+
def test_fromkeys_operator_modifying_dict_operand(self):
942+
# test fix for seg fault reported in issue 27945 part 4a.
943+
class X(int):
944+
def __hash__(self):
945+
return 13
946+
947+
def __eq__(self, other):
948+
if len(d) > 1:
949+
d.clear()
950+
return False
951+
952+
d = {} # this is required to exist so that d can be constructed!
953+
d = {X(1): 1, X(2): 2}
954+
try:
955+
dict.fromkeys(d) # shouldn't crash
956+
except RuntimeError: # implementation defined
957+
pass
958+
959+
def test_fromkeys_operator_modifying_set_operand(self):
960+
# test fix for seg fault reported in issue 27945 part 4b.
961+
class X(int):
962+
def __hash__(self):
963+
return 13
964+
965+
def __eq__(self, other):
966+
if len(d) > 1:
967+
d.clear()
968+
return False
969+
970+
d = {} # this is required to exist so that d can be constructed!
971+
d = {X(1), X(2)}
972+
try:
973+
dict.fromkeys(d) # shouldn't crash
974+
except RuntimeError: # implementation defined
975+
pass
976+
977+
def test_dictitems_contains_use_after_free(self):
978+
class X:
979+
def __eq__(self, other):
980+
d.clear()
981+
return NotImplemented
982+
983+
d = {0: set()}
984+
(0, X()) in d.items()
985+
986+
def test_init_use_after_free(self):
987+
class X:
988+
def __hash__(self):
989+
pair[:] = []
990+
return 13
991+
992+
pair = [X(), 123]
993+
dict([pair])
994+
995+
def test_oob_indexing_dictiter_iternextitem(self):
996+
class X(int):
997+
def __del__(self):
998+
d.clear()
999+
1000+
d = {i: X(i) for i in range(8)}
1001+
1002+
def iter_and_mutate():
1003+
for result in d.items():
1004+
if result[0] == 2:
1005+
d[2] = None # free d[2] --> X(2).__del__ was called
1006+
1007+
self.assertRaises(RuntimeError, iter_and_mutate)
1008+
1009+
9241010
from test import mapping_tests
9251011

9261012
class GeneralMappingTests(mapping_tests.BasicTestMappingProtocol):

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,7 @@ David Goodger
457457
Hans de Graaff
458458
Nathaniel Gray
459459
Eddy De Greef
460+
Duane Griffin
460461
Grant Griffin
461462
Andrea Griffini
462463
Duncan Grisby

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ What's New in Python 3.3.7?
1010
Core and Builtins
1111
-----------------
1212

13+
- bpo-27945: Fixed various segfaults with dict when input collections are
14+
mutated during searching, inserting or comparing. Based on patches by
15+
Duane Griffin and Tim Mitchell.
16+
1317
- Issue #28648: Fixed crash in Py_DecodeLocale() in debug build on Mac OS X
1418
when decode astral characters. Patch by Xiang Zhang.
1519

Objects/dictobject.c

Lines changed: 52 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -810,56 +810,62 @@ insertdict(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value)
810810
PyDictKeyEntry *ep;
811811
assert(key != dummy);
812812

813+
Py_INCREF(key);
814+
Py_INCREF(value);
813815
if (mp->ma_values != NULL && !PyUnicode_CheckExact(key)) {
814816
if (insertion_resize(mp) < 0)
815-
return -1;
817+
goto Fail;
816818
}
817819

818820
ep = mp->ma_keys->dk_lookup(mp, key, hash, &value_addr);
819-
if (ep == NULL) {
820-
return -1;
821-
}
822-
Py_INCREF(value);
821+
if (ep == NULL)
822+
goto Fail;
823+
823824
MAINTAIN_TRACKING(mp, key, value);
824825
old_value = *value_addr;
825826
if (old_value != NULL) {
826827
assert(ep->me_key != NULL && ep->me_key != dummy);
827828
*value_addr = value;
828-
Py_DECREF(old_value); /* which **CAN** re-enter */
829+
Py_DECREF(old_value); /* which **CAN** re-enter (see issue #22653) */
830+
Py_DECREF(key);
829831
}
830832
else {
831833
if (ep->me_key == NULL) {
832-
Py_INCREF(key);
833834
if (mp->ma_keys->dk_usable <= 0) {
834835
/* Need to resize. */
835-
if (insertion_resize(mp) < 0) {
836-
Py_DECREF(key);
837-
Py_DECREF(value);
838-
return -1;
839-
}
836+
if (insertion_resize(mp) < 0)
837+
goto Fail;
840838
ep = find_empty_slot(mp, key, hash, &value_addr);
841839
}
840+
mp->ma_used++;
841+
*value_addr = value;
842842
mp->ma_keys->dk_usable--;
843843
assert(mp->ma_keys->dk_usable >= 0);
844844
ep->me_key = key;
845845
ep->me_hash = hash;
846+
assert(ep->me_key != NULL && ep->me_key != dummy);
846847
}
847848
else {
849+
mp->ma_used++;
850+
*value_addr = value;
848851
if (ep->me_key == dummy) {
849-
Py_INCREF(key);
850852
ep->me_key = key;
851853
ep->me_hash = hash;
852854
Py_DECREF(dummy);
853855
} else {
854856
assert(_PyDict_HasSplitTable(mp));
857+
Py_DECREF(key);
855858
}
856859
}
857-
mp->ma_used++;
858-
*value_addr = value;
859860
}
860861
assert(ep->me_key != NULL && ep->me_key != dummy);
861862
assert(PyUnicode_CheckExact(key) || mp->ma_keys->dk_lookup == lookdict);
862863
return 0;
864+
865+
Fail:
866+
Py_DECREF(value);
867+
Py_DECREF(key);
868+
return -1;
863869
}
864870

865871
/*
@@ -1879,11 +1885,18 @@ PyDict_MergeFromSeq2(PyObject *d, PyObject *seq2, int override)
18791885
/* Update/merge with this (key, value) pair. */
18801886
key = PySequence_Fast_GET_ITEM(fast, 0);
18811887
value = PySequence_Fast_GET_ITEM(fast, 1);
1888+
Py_INCREF(key);
1889+
Py_INCREF(value);
18821890
if (override || PyDict_GetItem(d, key) == NULL) {
18831891
int status = PyDict_SetItem(d, key, value);
1884-
if (status < 0)
1892+
if (status < 0) {
1893+
Py_DECREF(key);
1894+
Py_DECREF(value);
18851895
goto Fail;
1896+
}
18861897
}
1898+
Py_DECREF(key);
1899+
Py_DECREF(value);
18871900
Py_DECREF(fast);
18881901
Py_DECREF(item);
18891902
}
@@ -2137,14 +2150,15 @@ dict_equal(PyDictObject *a, PyDictObject *b)
21372150
/* ditto for key */
21382151
Py_INCREF(key);
21392152
bval = PyDict_GetItemWithError((PyObject *)b, key);
2140-
Py_DECREF(key);
21412153
if (bval == NULL) {
2154+
Py_DECREF(key);
21422155
Py_DECREF(aval);
21432156
if (PyErr_Occurred())
21442157
return -1;
21452158
return 0;
21462159
}
21472160
cmp = PyObject_RichCompareBool(aval, bval, Py_EQ);
2161+
Py_DECREF(key);
21482162
Py_DECREF(aval);
21492163
if (cmp <= 0) /* error or not equal */
21502164
return cmp;
@@ -2970,7 +2984,7 @@ PyTypeObject PyDictIterValue_Type = {
29702984

29712985
static PyObject *dictiter_iternextitem(dictiterobject *di)
29722986
{
2973-
PyObject *key, *value, *result = di->di_result;
2987+
PyObject *key, *value, *result;
29742988
register Py_ssize_t i, mask, offset;
29752989
PyDictObject *d = di->di_dict;
29762990
PyObject **value_ptr;
@@ -3006,22 +3020,27 @@ static PyObject *dictiter_iternextitem(dictiterobject *di)
30063020
if (i > mask)
30073021
goto fail;
30083022

3009-
if (result->ob_refcnt == 1) {
3023+
di->len--;
3024+
key = d->ma_keys->dk_entries[i].me_key;
3025+
value = *value_ptr;
3026+
Py_INCREF(key);
3027+
Py_INCREF(value);
3028+
result = di->di_result;
3029+
if (Py_REFCNT(result) == 1) {
3030+
PyObject *oldkey = PyTuple_GET_ITEM(result, 0);
3031+
PyObject *oldvalue = PyTuple_GET_ITEM(result, 1);
3032+
PyTuple_SET_ITEM(result, 0, key); /* steals reference */
3033+
PyTuple_SET_ITEM(result, 1, value); /* steals reference */
30103034
Py_INCREF(result);
3011-
Py_DECREF(PyTuple_GET_ITEM(result, 0));
3012-
Py_DECREF(PyTuple_GET_ITEM(result, 1));
3035+
Py_DECREF(oldkey);
3036+
Py_DECREF(oldvalue);
30133037
} else {
30143038
result = PyTuple_New(2);
30153039
if (result == NULL)
30163040
return NULL;
3041+
PyTuple_SET_ITEM(result, 0, key); /* steals reference */
3042+
PyTuple_SET_ITEM(result, 1, value); /* steals reference */
30173043
}
3018-
di->len--;
3019-
key = d->ma_keys->dk_entries[i].me_key;
3020-
value = *value_ptr;
3021-
Py_INCREF(key);
3022-
Py_INCREF(value);
3023-
PyTuple_SET_ITEM(result, 0, key);
3024-
PyTuple_SET_ITEM(result, 1, value);
30253044
return result;
30263045

30273046
fail:
@@ -3521,6 +3540,7 @@ dictitems_iter(dictviewobject *dv)
35213540
static int
35223541
dictitems_contains(dictviewobject *dv, PyObject *obj)
35233542
{
3543+
int result;
35243544
PyObject *key, *value, *found;
35253545
if (dv->dv_dict == NULL)
35263546
return 0;
@@ -3534,7 +3554,10 @@ dictitems_contains(dictviewobject *dv, PyObject *obj)
35343554
return -1;
35353555
return 0;
35363556
}
3537-
return PyObject_RichCompareBool(value, found, Py_EQ);
3557+
Py_INCREF(found);
3558+
result = PyObject_RichCompareBool(value, found, Py_EQ);
3559+
Py_DECREF(found);
3560+
return result;
35383561
}
35393562

35403563
static PySequenceMethods dictitems_as_sequence = {

0 commit comments

Comments
 (0)