Skip to content

Commit 564398a

Browse files
[3.6] bpo-27945: Fixed various segfaults with dict. (GH-1657) (#1677)
Based on patches by Duane Griffin and Tim Mitchell. (cherry picked from commit 753bca3)
1 parent 193f7e0 commit 564398a

File tree

4 files changed

+134
-25
lines changed

4 files changed

+134
-25
lines changed

Lib/test/test_dict.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,6 +1085,91 @@ def test_free_after_iterating(self):
10851085
support.check_free_after_iterating(self, lambda d: iter(d.values()), dict)
10861086
support.check_free_after_iterating(self, lambda d: iter(d.items()), dict)
10871087

1088+
def test_equal_operator_modifying_operand(self):
1089+
# test fix for seg fault reported in issue 27945 part 3.
1090+
class X():
1091+
def __del__(self):
1092+
dict_b.clear()
1093+
1094+
def __eq__(self, other):
1095+
dict_a.clear()
1096+
return True
1097+
1098+
def __hash__(self):
1099+
return 13
1100+
1101+
dict_a = {X(): 0}
1102+
dict_b = {X(): X()}
1103+
self.assertTrue(dict_a == dict_b)
1104+
1105+
def test_fromkeys_operator_modifying_dict_operand(self):
1106+
# test fix for seg fault reported in issue 27945 part 4a.
1107+
class X(int):
1108+
def __hash__(self):
1109+
return 13
1110+
1111+
def __eq__(self, other):
1112+
if len(d) > 1:
1113+
d.clear()
1114+
return False
1115+
1116+
d = {} # this is required to exist so that d can be constructed!
1117+
d = {X(1): 1, X(2): 2}
1118+
try:
1119+
dict.fromkeys(d) # shouldn't crash
1120+
except RuntimeError: # implementation defined
1121+
pass
1122+
1123+
def test_fromkeys_operator_modifying_set_operand(self):
1124+
# test fix for seg fault reported in issue 27945 part 4b.
1125+
class X(int):
1126+
def __hash__(self):
1127+
return 13
1128+
1129+
def __eq__(self, other):
1130+
if len(d) > 1:
1131+
d.clear()
1132+
return False
1133+
1134+
d = {} # this is required to exist so that d can be constructed!
1135+
d = {X(1), X(2)}
1136+
try:
1137+
dict.fromkeys(d) # shouldn't crash
1138+
except RuntimeError: # implementation defined
1139+
pass
1140+
1141+
def test_dictitems_contains_use_after_free(self):
1142+
class X:
1143+
def __eq__(self, other):
1144+
d.clear()
1145+
return NotImplemented
1146+
1147+
d = {0: set()}
1148+
(0, X()) in d.items()
1149+
1150+
def test_init_use_after_free(self):
1151+
class X:
1152+
def __hash__(self):
1153+
pair[:] = []
1154+
return 13
1155+
1156+
pair = [X(), 123]
1157+
dict([pair])
1158+
1159+
def test_oob_indexing_dictiter_iternextitem(self):
1160+
class X(int):
1161+
def __del__(self):
1162+
d.clear()
1163+
1164+
d = {i: X(i) for i in range(8)}
1165+
1166+
def iter_and_mutate():
1167+
for result in d.items():
1168+
if result[0] == 2:
1169+
d[2] = None # free d[2] --> X(2).__del__ was called
1170+
1171+
self.assertRaises(RuntimeError, iter_and_mutate)
1172+
10881173

10891174
class CAPITest(unittest.TestCase):
10901175

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,7 @@ Tim Graham
542542
Kim Gräsman
543543
Nathaniel Gray
544544
Eddy De Greef
545+
Duane Griffin
545546
Grant Griffin
546547
Andrea Griffini
547548
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.6.2 release candidate 1?
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
- bpo-25794: Fixed type.__setattr__() and type.__delattr__() for
1418
non-interned attribute names. Based on patch by Eryk Sun.
1519

Objects/dictobject.c

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,18 +1115,18 @@ insertdict(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value)
11151115
PyDictKeyEntry *ep, *ep0;
11161116
Py_ssize_t hashpos, ix;
11171117

1118+
Py_INCREF(key);
1119+
Py_INCREF(value);
11181120
if (mp->ma_values != NULL && !PyUnicode_CheckExact(key)) {
11191121
if (insertion_resize(mp) < 0)
1120-
return -1;
1122+
goto Fail;
11211123
}
11221124

11231125
ix = mp->ma_keys->dk_lookup(mp, key, hash, &value_addr, &hashpos);
1124-
if (ix == DKIX_ERROR) {
1125-
return -1;
1126-
}
1126+
if (ix == DKIX_ERROR)
1127+
goto Fail;
11271128

11281129
assert(PyUnicode_CheckExact(key) || mp->ma_keys->dk_lookup == lookdict);
1129-
Py_INCREF(value);
11301130
MAINTAIN_TRACKING(mp, key, value);
11311131

11321132
/* When insertion order is different from shared key, we can't share
@@ -1135,10 +1135,8 @@ insertdict(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value)
11351135
if (_PyDict_HasSplitTable(mp) &&
11361136
((ix >= 0 && *value_addr == NULL && mp->ma_used != ix) ||
11371137
(ix == DKIX_EMPTY && mp->ma_used != mp->ma_keys->dk_nentries))) {
1138-
if (insertion_resize(mp) < 0) {
1139-
Py_DECREF(value);
1140-
return -1;
1141-
}
1138+
if (insertion_resize(mp) < 0)
1139+
goto Fail;
11421140
find_empty_slot(mp, key, hash, &value_addr, &hashpos);
11431141
ix = DKIX_EMPTY;
11441142
}
@@ -1147,16 +1145,13 @@ insertdict(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value)
11471145
/* Insert into new slot. */
11481146
if (mp->ma_keys->dk_usable <= 0) {
11491147
/* Need to resize. */
1150-
if (insertion_resize(mp) < 0) {
1151-
Py_DECREF(value);
1152-
return -1;
1153-
}
1148+
if (insertion_resize(mp) < 0)
1149+
goto Fail;
11541150
find_empty_slot(mp, key, hash, &value_addr, &hashpos);
11551151
}
11561152
ep0 = DK_ENTRIES(mp->ma_keys);
11571153
ep = &ep0[mp->ma_keys->dk_nentries];
11581154
dk_set_index(mp->ma_keys, hashpos, mp->ma_keys->dk_nentries);
1159-
Py_INCREF(key);
11601155
ep->me_key = key;
11611156
ep->me_hash = hash;
11621157
if (mp->ma_values) {
@@ -1184,6 +1179,7 @@ insertdict(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value)
11841179
assert(_PyDict_CheckConsistency(mp));
11851180

11861181
Py_DECREF(old_value); /* which **CAN** re-enter (see issue #22653) */
1182+
Py_DECREF(key);
11871183
return 0;
11881184
}
11891185

@@ -1194,7 +1190,13 @@ insertdict(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value)
11941190
mp->ma_used++;
11951191
mp->ma_version_tag = DICT_NEXT_VERSION();
11961192
assert(_PyDict_CheckConsistency(mp));
1193+
Py_DECREF(key);
11971194
return 0;
1195+
1196+
Fail:
1197+
Py_DECREF(value);
1198+
Py_DECREF(key);
1199+
return -1;
11981200
}
11991201

12001202
/*
@@ -2432,11 +2434,18 @@ PyDict_MergeFromSeq2(PyObject *d, PyObject *seq2, int override)
24322434
/* Update/merge with this (key, value) pair. */
24332435
key = PySequence_Fast_GET_ITEM(fast, 0);
24342436
value = PySequence_Fast_GET_ITEM(fast, 1);
2437+
Py_INCREF(key);
2438+
Py_INCREF(value);
24352439
if (override || PyDict_GetItem(d, key) == NULL) {
24362440
int status = PyDict_SetItem(d, key, value);
2437-
if (status < 0)
2441+
if (status < 0) {
2442+
Py_DECREF(key);
2443+
Py_DECREF(value);
24382444
goto Fail;
2445+
}
24392446
}
2447+
Py_DECREF(key);
2448+
Py_DECREF(value);
24402449
Py_DECREF(fast);
24412450
Py_DECREF(item);
24422451
}
@@ -2737,14 +2746,15 @@ dict_equal(PyDictObject *a, PyDictObject *b)
27372746
bval = NULL;
27382747
else
27392748
bval = *vaddr;
2740-
Py_DECREF(key);
27412749
if (bval == NULL) {
2750+
Py_DECREF(key);
27422751
Py_DECREF(aval);
27432752
if (PyErr_Occurred())
27442753
return -1;
27452754
return 0;
27462755
}
27472756
cmp = PyObject_RichCompareBool(aval, bval, Py_EQ);
2757+
Py_DECREF(key);
27482758
Py_DECREF(aval);
27492759
if (cmp <= 0) /* error or not equal */
27502760
return cmp;
@@ -3633,7 +3643,7 @@ PyTypeObject PyDictIterValue_Type = {
36333643
static PyObject *
36343644
dictiter_iternextitem(dictiterobject *di)
36353645
{
3636-
PyObject *key, *value, *result = di->di_result;
3646+
PyObject *key, *value, *result;
36373647
Py_ssize_t i, n;
36383648
PyDictObject *d = di->di_dict;
36393649

@@ -3674,20 +3684,25 @@ dictiter_iternextitem(dictiterobject *di)
36743684
}
36753685
di->di_pos = i+1;
36763686
di->len--;
3677-
if (result->ob_refcnt == 1) {
3687+
Py_INCREF(key);
3688+
Py_INCREF(value);
3689+
result = di->di_result;
3690+
if (Py_REFCNT(result) == 1) {
3691+
PyObject *oldkey = PyTuple_GET_ITEM(result, 0);
3692+
PyObject *oldvalue = PyTuple_GET_ITEM(result, 1);
3693+
PyTuple_SET_ITEM(result, 0, key); /* steals reference */
3694+
PyTuple_SET_ITEM(result, 1, value); /* steals reference */
36783695
Py_INCREF(result);
3679-
Py_DECREF(PyTuple_GET_ITEM(result, 0));
3680-
Py_DECREF(PyTuple_GET_ITEM(result, 1));
3696+
Py_DECREF(oldkey);
3697+
Py_DECREF(oldvalue);
36813698
}
36823699
else {
36833700
result = PyTuple_New(2);
36843701
if (result == NULL)
36853702
return NULL;
3703+
PyTuple_SET_ITEM(result, 0, key); /* steals reference */
3704+
PyTuple_SET_ITEM(result, 1, value); /* steals reference */
36863705
}
3687-
Py_INCREF(key);
3688-
Py_INCREF(value);
3689-
PyTuple_SET_ITEM(result, 0, key); /* steals reference */
3690-
PyTuple_SET_ITEM(result, 1, value); /* steals reference */
36913706
return result;
36923707

36933708
fail:
@@ -4180,6 +4195,7 @@ dictitems_iter(_PyDictViewObject *dv)
41804195
static int
41814196
dictitems_contains(_PyDictViewObject *dv, PyObject *obj)
41824197
{
4198+
int result;
41834199
PyObject *key, *value, *found;
41844200
if (dv->dv_dict == NULL)
41854201
return 0;
@@ -4193,7 +4209,10 @@ dictitems_contains(_PyDictViewObject *dv, PyObject *obj)
41934209
return -1;
41944210
return 0;
41954211
}
4196-
return PyObject_RichCompareBool(value, found, Py_EQ);
4212+
Py_INCREF(found);
4213+
result = PyObject_RichCompareBool(value, found, Py_EQ);
4214+
Py_DECREF(found);
4215+
return result;
41974216
}
41984217

41994218
static PySequenceMethods dictitems_as_sequence = {

0 commit comments

Comments
 (0)