Skip to content

Commit 1c4ba11

Browse files
committed
chore: resolve review comment
1 parent e656b34 commit 1c4ba11

File tree

3 files changed

+46
-55
lines changed

3 files changed

+46
-55
lines changed

Lib/test/test_ordered_dict.py

Lines changed: 39 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -971,76 +971,73 @@ def test_weakref_list_is_not_traversed(self):
971971

972972
gc.collect()
973973

974-
def test_getitem_re_entrant_clear_during_copy(self):
975-
class Evil(self.OrderedDict):
974+
def test_copy_concurrent_clear_in__getitem__(self):
975+
class MyOD(self.OrderedDict):
976976
def __getitem__(self, key):
977977
super().clear()
978978
return None
979979

980-
evil_dict = Evil([(i, i) for i in range(4)])
981-
with self.assertRaises(RuntimeError):
982-
result = evil_dict.copy()
980+
od = MyOD([(i, i) for i in range(4)])
981+
self.assertRaises(RuntimeError, od.copy)
983982

984-
def test_getitem_re_entrant_modify_during_copy(self):
985-
class Modifier(self.OrderedDict):
983+
def test_copy_concurrent_insertion_in__getitem__(self):
984+
class MyOD(self.OrderedDict):
986985
def __getitem__(self, key):
987986
self['new_key'] = 'new_value'
988987
return super().__getitem__(key)
989988

990-
original = Modifier([(1, 'one'), (2, 'two')])
991-
with self.assertRaises(RuntimeError):
992-
result = original.copy()
989+
od = MyOD([(1, 'one'), (2, 'two')])
990+
self.assertRaises(RuntimeError, od.copy)
993991

994-
def test_getitem_re_entrant_delete_during_copy(self):
995-
class Deleter(self.OrderedDict):
992+
def test_copy_concurrent_deletion_by_del_in__getitem__(self):
993+
class MyOD(self.OrderedDict):
996994
call_count = 0
997995
def __getitem__(self, key):
998-
Deleter.call_count += 1
999-
if Deleter.call_count == 1:
996+
MyOD.call_count += 1
997+
if MyOD.call_count == 1:
1000998
del self[3]
1001999
return super().__getitem__(key)
10021000

1003-
original = Deleter([(1, 'one'), (2, 'two'), (3, 'three')])
1004-
with self.assertRaises(RuntimeError):
1005-
result = original.copy()
1001+
od = MyOD([(1, 'one'), (2, 'two'), (3, 'three')])
1002+
self.assertRaises(RuntimeError, od.copy)
10061003

1007-
def test_getitem_re_entrant_add_during_copy(self):
1008-
class MultiAdder(self.OrderedDict):
1009-
def __getitem__(self, key):
1010-
self['new_key1'] = 'new_value1'
1011-
return super().__getitem__(key)
1012-
1013-
original = MultiAdder([(1, 'one'), (2, 'two'), (3, 'three')])
1014-
with self.assertRaises(RuntimeError):
1015-
result = original.copy()
1016-
1017-
def test_getitem_re_entrant_pop_during_copy(self):
1018-
class Popper(self.OrderedDict):
1004+
def test_copy_concurrent_deletion_by_pop_in__getitem__(self):
1005+
class MyOD(self.OrderedDict):
10191006
call_count = 0
10201007
def __getitem__(self, key):
1021-
Popper.call_count += 1
1022-
if Popper.call_count == 1:
1008+
MyOD.call_count += 1
1009+
if MyOD.call_count == 1:
10231010
self.pop(3, None)
10241011
return super().__getitem__(key)
10251012

1026-
original = Popper([(1, 'one'), (2, 'two'), (3, 'three')])
1027-
with self.assertRaises(RuntimeError):
1028-
result = original.copy()
1013+
od = MyOD([(1, 'one'), (2, 'two'), (3, 'three')])
1014+
self.assertRaises(RuntimeError, od.copy)
10291015

1030-
def test_getitem_re_entrant_mixed_mutation_during_copy(self):
1031-
class MixedMutator(self.OrderedDict):
1016+
def test_copy_concurrent_deletion_and_set_in__getitem__(self):
1017+
class MyOD(self.OrderedDict):
10321018
call_count = 0
10331019
def __getitem__(self, key):
1034-
MixedMutator.call_count += 1
1035-
if MixedMutator.call_count == 1:
1020+
MyOD.call_count += 1
1021+
if MyOD.call_count == 1:
10361022
del self[3]
1037-
elif MixedMutator.call_count == 2:
1023+
elif MyOD.call_count == 2:
10381024
self['new_key'] = 'new_value'
10391025
return super().__getitem__(key)
10401026

1041-
original = MixedMutator([(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')])
1042-
with self.assertRaises(RuntimeError):
1043-
result = original.copy()
1027+
od = MyOD([(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')])
1028+
self.assertRaises(RuntimeError, od.copy)
1029+
1030+
def test_copy_concurrent_mutation_in__setitem__(self):
1031+
od = None
1032+
1033+
class MyOD(self.OrderedDict):
1034+
def __setitem__(self, key, value):
1035+
if od is not None and len(od) > 1:
1036+
del od[3]
1037+
return super().__setitem__(key, value)
1038+
1039+
od = MyOD([(1, 'one'), (2, 'two'), (3, 'three')])
1040+
self.assertRaises(RuntimeError, od.copy)
10441041

10451042

10461043
class PurePythonOrderedDictSubclassTests(PurePythonOrderedDictTests):
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
fix ordereddict copy heap-use-after-free security issue
1+
:mod:`collections`: fix use-after-free crashes in :meth:`OrderedDict.copy <dict.copy>` when the dictionary to copy is concurrently mutated.

Objects/odictobject.c

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1271,34 +1271,28 @@ OrderedDict_copy_impl(PyObject *od)
12711271
_ODictNode *cur;
12721272

12731273
_odict_FOREACH(od, cur) {
1274-
if (self->od_state != state) {
1275-
PyErr_SetString(PyExc_RuntimeError,
1276-
"OrderedDict mutated during iteration");
1277-
goto fail;
1278-
}
12791274
PyObject *key = Py_NewRef(_odictnode_KEY(cur));
12801275
PyObject *value = PyObject_GetItem(od, key);
12811276
if (value == NULL) {
12821277
Py_DECREF(key);
12831278
goto fail;
12841279
}
1285-
if (self->od_state != state) {
1286-
Py_DECREF(key);
1287-
Py_DECREF(value);
1288-
PyErr_SetString(PyExc_RuntimeError,
1289-
"OrderedDict mutated during iteration");
1290-
goto fail;
1291-
}
12921280
int rc = PyObject_SetItem(od_copy, key, value);
12931281
Py_DECREF(key);
12941282
Py_DECREF(value);
12951283
if (rc != 0) {
12961284
goto fail;
12971285
}
1286+
if (self->od_state != state) {
1287+
goto invalid_state;
1288+
}
12981289
}
12991290
}
13001291
return od_copy;
13011292

1293+
invalid_state:
1294+
PyErr_SetString(PyExc_RuntimeError,
1295+
"OrderedDict mutated during iteration");
13021296
fail:
13031297
Py_DECREF(od_copy);
13041298
return NULL;

0 commit comments

Comments
 (0)