]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-143004: Fix possible use-after-free in collections.Counter.update() (GH-143044)
authorkaushal trivedi <155625932+Kaushalt2004@users.noreply.github.com>
Thu, 25 Dec 2025 09:13:39 +0000 (14:43 +0530)
committerGitHub <noreply@github.com>
Thu, 25 Dec 2025 09:13:39 +0000 (09:13 +0000)
This happened when the Counter was mutated when incrementing
the value for an existing key.

Lib/test/test_collections.py
Misc/NEWS.d/next/Library/2025-12-22-00-00-00.gh-issue-143004.uaf-counter.rst [new file with mode: 0644]
Modules/_collectionsmodule.c

index 225952392528143f477fbe88590e2ed95b8ac307..fad639b20a1801a4209a81d598880c9c32387940 100644 (file)
@@ -2135,6 +2135,19 @@ class TestCounter(unittest.TestCase):
         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 (file)
index 0000000..278066e
--- /dev/null
@@ -0,0 +1,2 @@
+Fix a potential use-after-free in :meth:`collections.Counter.update` when user code
+mutates the Counter during an update.
index 3b14a21fa8428ea2b659867d51745db5cec48e7d..45ca63e6d7c77fcce7e16f6088c119942698ef8b 100644 (file)
@@ -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)