https://github.com/python/cpython/commit/149ecbb9a9e6e58a60c83783412de2c8dc0c9db9
commit: 149ecbb9a9e6e58a60c83783412de2c8dc0c9db9
branch: 3.13
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-01-15T15:00:44Z
summary:

[3.13] gh-142829: Fix use-after-free in `Context.__eq__` via re-entrant 
`ContextVar.set` (GH-142905) (GH-143871)

(cherry picked from commit a4086d7f89e5d388e4ffcdb13e4fba0255234286)

Co-authored-by: A.Ibrahim <[email protected]>

files:
A Misc/NEWS.d/next/Core and 
Builtins/2025-12-17-19-45-10.gh-issue-142829.ICtLXy.rst
M Lib/test/test_context.py
M Python/hamt.c

diff --git a/Lib/test/test_context.py b/Lib/test/test_context.py
index 4e6b3b7fc608eb..ce313a5bd825b4 100644
--- a/Lib/test/test_context.py
+++ b/Lib/test/test_context.py
@@ -370,6 +370,36 @@ def sub(num):
             tp.shutdown()
         self.assertEqual(results, list(range(10)))
 
+    def test_context_eq_reentrant_contextvar_set(self):
+        var = contextvars.ContextVar("v")
+        ctx1 = contextvars.Context()
+        ctx2 = contextvars.Context()
+
+        class ReentrantEq:
+            def __eq__(self, other):
+                ctx1.run(lambda: var.set(object()))
+                return True
+
+        ctx1.run(var.set, ReentrantEq())
+        ctx2.run(var.set, object())
+        ctx1 == ctx2
+
+    def test_context_eq_reentrant_contextvar_set_in_hash(self):
+        var = contextvars.ContextVar("v")
+        ctx1 = contextvars.Context()
+        ctx2 = contextvars.Context()
+
+        class ReentrantHash:
+            def __hash__(self):
+                ctx1.run(lambda: var.set(object()))
+                return 0
+            def __eq__(self, other):
+                return isinstance(other, ReentrantHash)
+
+        ctx1.run(var.set, ReentrantHash())
+        ctx2.run(var.set, ReentrantHash())
+        ctx1 == ctx2
+
 
 # HAMT Tests
 
diff --git a/Misc/NEWS.d/next/Core and 
Builtins/2025-12-17-19-45-10.gh-issue-142829.ICtLXy.rst b/Misc/NEWS.d/next/Core 
and Builtins/2025-12-17-19-45-10.gh-issue-142829.ICtLXy.rst
new file mode 100644
index 00000000000000..b85003071ac188
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and 
Builtins/2025-12-17-19-45-10.gh-issue-142829.ICtLXy.rst 
@@ -0,0 +1,3 @@
+Fix a use-after-free crash in :class:`contextvars.Context` comparison when a
+custom ``__eq__`` method modifies the context via
+:meth:`~contextvars.ContextVar.set`.
diff --git a/Python/hamt.c b/Python/hamt.c
index 91abaecd3e7287..a9f811f4422e5f 100644
--- a/Python/hamt.c
+++ b/Python/hamt.c
@@ -2368,6 +2368,10 @@ _PyHamt_Eq(PyHamtObject *v, PyHamtObject *w)
         return 0;
     }
 
+    Py_INCREF(v);
+    Py_INCREF(w);
+
+    int res = 1;
     PyHamtIteratorState iter;
     hamt_iter_t iter_res;
     hamt_find_t find_res;
@@ -2383,25 +2387,38 @@ _PyHamt_Eq(PyHamtObject *v, PyHamtObject *w)
             find_res = hamt_find(w, v_key, &w_val);
             switch (find_res) {
                 case F_ERROR:
-                    return -1;
+                    res = -1;
+                    goto done;
 
                 case F_NOT_FOUND:
-                    return 0;
+                    res = 0;
+                    goto done;
 
                 case F_FOUND: {
+                    Py_INCREF(v_key);
+                    Py_INCREF(v_val);
+                    Py_INCREF(w_val);
                     int cmp = PyObject_RichCompareBool(v_val, w_val, Py_EQ);
+                    Py_DECREF(v_key);
+                    Py_DECREF(v_val);
+                    Py_DECREF(w_val);
                     if (cmp < 0) {
-                        return -1;
+                        res = -1;
+                        goto done;
                     }
                     if (cmp == 0) {
-                        return 0;
+                        res = 0;
+                        goto done;
                     }
                 }
             }
         }
     } while (iter_res != I_END);
 
-    return 1;
+done:
+    Py_DECREF(v);
+    Py_DECREF(w);
+    return res;
 }
 
 Py_ssize_t

_______________________________________________
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]

Reply via email to