]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-143004: Fix possible use-after-free in collections.Counter.update() (GH...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Thu, 25 Dec 2025 11:41:24 +0000 (12:41 +0100)
committerGitHub <noreply@github.com>
Thu, 25 Dec 2025 11:41:24 +0000 (13:41 +0200)
This happened when the Counter was mutated when incrementing
the value for an existing key.
(cherry picked from commit 86d904588e8c84c7fccb8faf84b343f03461970d)

Co-authored-by: kaushal trivedi <155625932+Kaushalt2004@users.noreply.github.com>
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 843f4ffcd25aa49bb5f014d9527fb01b9e3cb864..4b9b47cd740a57ca67e981ce813bbe192ec2e791 100644 (file)
@@ -2119,6 +2119,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 b356c917c1d5efba63d16c97de0415c0db39f939..f5fc0d02a166079f0a899d9a59c8f6f12a1088b4 100644 (file)
@@ -2549,7 +2549,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)