]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-142829: Fix use-after-free in `Context.__eq__` via re-entrant `ContextVar...
authorSerhiy Storchaka <storchaka@gmail.com>
Thu, 15 Jan 2026 15:00:44 +0000 (17:00 +0200)
committerGitHub <noreply@github.com>
Thu, 15 Jan 2026 15:00:44 +0000 (15:00 +0000)
(cherry picked from commit a4086d7f89e5d388e4ffcdb13e4fba0255234286)

Co-authored-by: A.Ibrahim <abdulrasheedibrahim47@gmail.com>
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 4e6b3b7fc608ebc168029111763082a990df42ba..ce313a5bd825b46c2a5f48cd89e81dad4d563d92 100644 (file)
@@ -370,6 +370,36 @@ class ContextTest(unittest.TestCase):
             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 (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 91abaecd3e72878202fa3681c2fef427c9c0b938..a9f811f4422e5f358e03132c4a5fdd025c7b5513 100644 (file)
@@ -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