https://github.com/python/cpython/commit/86d904588e8c84c7fccb8faf84b343f03461970d
commit: 86d904588e8c84c7fccb8faf84b343f03461970d
branch: main
author: kaushal trivedi <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2025-12-25T09:13:39Z
summary:
gh-143004: Fix possible use-after-free in collections.Counter.update()
(GH-143044)
This happened when the Counter was mutated when incrementing
the value for an existing key.
files:
A Misc/NEWS.d/next/Library/2025-12-22-00-00-00.gh-issue-143004.uaf-counter.rst
M Lib/test/test_collections.py
M Modules/_collectionsmodule.c
diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py
index 22595239252814..fad639b20a1801 100644
--- a/Lib/test/test_collections.py
+++ b/Lib/test/test_collections.py
@@ -2135,6 +2135,19 @@ def test_basics(self):
self.assertEqual(c.setdefault('e', 5), 5)
self.assertEqual(c['e'], 5)
+ def test_update_reentrant_add_clears_counter(self):
+ c = Counter()
+ key = object()
+
+ class Evil(int):
+ def __add__(self, other):
+ c.clear()
+ return NotImplemented
+
+ c[key] = Evil()
+ c.update([key])
+ self.assertEqual(c[key], 1)
+
def test_init(self):
self.assertEqual(list(Counter(self=42).items()), [('self', 42)])
self.assertEqual(list(Counter(iterable=42).items()), [('iterable',
42)])
diff --git
a/Misc/NEWS.d/next/Library/2025-12-22-00-00-00.gh-issue-143004.uaf-counter.rst
b/Misc/NEWS.d/next/Library/2025-12-22-00-00-00.gh-issue-143004.uaf-counter.rst
new file mode 100644
index 00000000000000..278066e9b706bf
--- /dev/null
+++
b/Misc/NEWS.d/next/Library/2025-12-22-00-00-00.gh-issue-143004.uaf-counter.rst
@@ -0,0 +1,2 @@
+Fix a potential use-after-free in :meth:`collections.Counter.update` when user
code
+mutates the Counter during an update.
diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c
index 3b14a21fa8428e..45ca63e6d7c77f 100644
--- a/Modules/_collectionsmodule.c
+++ b/Modules/_collectionsmodule.c
@@ -2577,7 +2577,12 @@ _collections__count_elements_impl(PyObject *module,
PyObject *mapping,
if (_PyDict_SetItem_KnownHash(mapping, key, one, hash) < 0)
goto done;
} else {
+ /* oldval is a borrowed reference. Keep it alive across
+ PyNumber_Add(), which can execute arbitrary user code and
+ mutate (or even clear) the underlying dict. */
+ Py_INCREF(oldval);
newval = PyNumber_Add(oldval, one);
+ Py_DECREF(oldval);
if (newval == NULL)
goto done;
if (_PyDict_SetItem_KnownHash(mapping, key, newval, hash) < 0)
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]