From f6d8284f5ac856954149d9302e349e9e52a9f1b0 Mon Sep 17 00:00:00 2001 From: Bhuvansh Kataria Date: Sun, 26 Jul 2026 09:02:23 +0000 Subject: [PATCH] gh-154709: Fix out-of-bounds access in dict reverse iterator --- Lib/test/test_dict.py | 15 +++++++++++++++ ...2026-07-26-09-07-41.gh-issue-154709.M2uZ76.rst | 2 ++ Objects/dictobject.c | 10 ++++++++++ 3 files changed, 27 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-07-26-09-07-41.gh-issue-154709.M2uZ76.rst diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index dc31d403b837adb..065f5672681c6ae 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -1403,6 +1403,21 @@ def __init__(self, x, y): self.assertEqual(list(reversed(A(1, 0).__dict__)), ['x']) self.assertEqual(list(reversed(A(0, 1).__dict__)), ['y']) + def test_reversed_dict_after_clear_and_restore(self): + d = {} + for i in range(1000): + d[f"k{i}"] = i + + for i in range(1, 1000): + del d[f"k{i}"] + + it = reversed(d) + + d.clear() + d["k0"] = 0 + + self.assertEqual(list(it), []) + def test_dict_copy_order(self): # bpo-34320 od = collections.OrderedDict([('a', 1), ('b', 2)]) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-26-09-07-41.gh-issue-154709.M2uZ76.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-26-09-07-41.gh-issue-154709.M2uZ76.rst new file mode 100644 index 000000000000000..6eb1ce6a8b6970b --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-26-09-07-41.gh-issue-154709.M2uZ76.rst @@ -0,0 +1,2 @@ +Fix an out-of-bounds access in reverse dictionary iterators when the +underlying dictionary is cleared and modified after the iterator is created. diff --git a/Objects/dictobject.c b/Objects/dictobject.c index c650aa456d2cc9d..8bb564a499be403 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -6272,13 +6272,23 @@ dictreviter_iter_lock_held(PyDictObject *d, PyObject *self) if (i < 0) { goto fail; } + if (_PyDict_HasSplitTable(d)) { + if (i >= d->ma_used) { + goto fail; + } + int index = get_index_from_order(d, i); key = LOAD_SHARED_KEY(DK_UNICODE_ENTRIES(k)[index].me_key); value = d->ma_values->values[index]; assert (value != NULL); } else { + Py_ssize_t n = k->dk_nentries; + if (i >= n) { + goto fail; + } + if (DK_IS_UNICODE(k)) { PyDictUnicodeEntry *entry_ptr = &DK_UNICODE_ENTRIES(k)[i]; while (entry_ptr->me_value == NULL) {