Skip to content

Commit 453163d

Browse files
committed
lookdict: stop more insane core-dump mutating comparison cases. Should
be possible to provoke unbounded recursion now, but leaving that to someone else to provoke and repair. Bugfix candidate -- although this is getting harder to backstitch, and the cases it's protecting against are mondo contrived.
1 parent 7b5d0af commit 453163d

File tree

2 files changed

+99
-6
lines changed

2 files changed

+99
-6
lines changed

‎Lib/test/test_mutants.py‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,71 @@ def __hash__(self):
215215
f.close()
216216
os.unlink(TESTFN)
217217
del f, dict
218+
219+
220+
##########################################################################
221+
# And another core-dumper from Michael Hudson.
222+
223+
dict = {}
224+
225+
# let's force dict to malloc its table
226+
for i in range(1, 10):
227+
dict[i] = i
228+
229+
class Machiavelli2:
230+
def __eq__(self, other):
231+
dict.clear()
232+
return 1
233+
234+
def __hash__(self):
235+
return 0
236+
237+
dict[Machiavelli2()] = Machiavelli2()
238+
239+
try:
240+
dict[Machiavelli2()]
241+
except KeyError:
242+
pass
243+
244+
del dict
245+
246+
##########################################################################
247+
# And another core-dumper from Michael Hudson.
248+
249+
dict = {}
250+
251+
# let's force dict to malloc its table
252+
for i in range(1, 10):
253+
dict[i] = i
254+
255+
class Machiavelli3:
256+
def __init__(self, id):
257+
self.id = id
258+
259+
def __eq__(self, other):
260+
if self.id == other.id:
261+
dict.clear()
262+
return 1
263+
else:
264+
return 0
265+
266+
def __repr__(self):
267+
return "%s(%s)"%(self.__class__.__name__, self.id)
268+
269+
def __hash__(self):
270+
return 0
271+
272+
dict[Machiavelli3(1)] = Machiavelli3(0)
273+
dict[Machiavelli3(2)] = Machiavelli3(0)
274+
275+
f = open(TESTFN, "w")
276+
try:
277+
try:
278+
print >> f, dict[Machiavelli3(2)]
279+
except KeyError:
280+
pass
281+
finally:
282+
f.close()
283+
os.unlink(TESTFN)
284+
285+
del dict

‎Objects/dictobject.c‎

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ lookdict(dictobject *mp, PyObject *key, register long hash)
255255
register int checked_error;
256256
register int cmp;
257257
PyObject *err_type, *err_value, *err_tb;
258+
PyObject *startkey;
258259

259260
i = hash & mask;
260261
ep = &ep0[i];
@@ -272,11 +273,23 @@ lookdict(dictobject *mp, PyObject *key, register long hash)
272273
restore_error = 1;
273274
PyErr_Fetch(&err_type, &err_value, &err_tb);
274275
}
275-
cmp = PyObject_RichCompareBool(ep->me_key, key, Py_EQ);
276-
if (cmp > 0)
277-
goto Done;
276+
startkey = ep->me_key;
277+
cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);
278278
if (cmp < 0)
279279
PyErr_Clear();
280+
if (ep0 == mp->ma_table && ep->me_key == startkey) {
281+
if (cmp > 0)
282+
goto Done;
283+
}
284+
else {
285+
/* The compare did major nasty stuff to the
286+
* dict: start over.
287+
* XXX A clever adversary could prevent this
288+
* XXX from terminating.
289+
*/
290+
ep = lookdict(mp, key, hash);
291+
goto Done;
292+
}
280293
}
281294
freeslot = NULL;
282295
}
@@ -302,11 +315,23 @@ lookdict(dictobject *mp, PyObject *key, register long hash)
302315
&err_tb);
303316
}
304317
}
305-
cmp = PyObject_RichCompareBool(ep->me_key, key, Py_EQ);
306-
if (cmp > 0)
307-
break;
318+
startkey = ep->me_key;
319+
cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);
308320
if (cmp < 0)
309321
PyErr_Clear();
322+
if (ep0 == mp->ma_table && ep->me_key == startkey) {
323+
if (cmp > 0)
324+
break;
325+
}
326+
else {
327+
/* The compare did major nasty stuff to the
328+
* dict: start over.
329+
* XXX A clever adversary could prevent this
330+
* XXX from terminating.
331+
*/
332+
ep = lookdict(mp, key, hash);
333+
break;
334+
}
310335
}
311336
else if (ep->me_key == dummy && freeslot == NULL)
312337
freeslot = ep;

0 commit comments

Comments
 (0)