]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-142829: Fix use-after-free in `Context.__eq__` via re-entrant `ContextVar.set...
authorA.Ibrahim <abdulrasheedibrahim47@gmail.com>
Fri, 9 Jan 2026 12:27:34 +0000 (13:27 +0100)
committerGitHub <noreply@github.com>
Fri, 9 Jan 2026 12:27:34 +0000 (17:57 +0530)
Lib/test/test_context.py
Misc/NEWS.d/next/Core_and_Builtins/2025-12-17-19-45-10.gh-issue-142829.ICtLXy.rst [new file with mode: 0644]
Python/hamt.c

index a08038b5dbd4071146612a43427a2c4d7586365c..ef20495dcc01ea9579ff0fa4aece5da4ce4d8bab 100644 (file)
@@ -556,6 +556,36 @@ class ContextTest(unittest.TestCase):
 
         ctx.run(fun)
 
+    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 (file)
index 0000000..b850030
--- /dev/null
@@ -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`.
index e372b1a1b4c18b1c188ca293e6eb53705716667e..881290a0e60db87b0815a68fb87a534dbd332b48 100644 (file)
@@ -2328,6 +2328,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;
@@ -2343,25 +2347,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