gh-142829: Fix use-after-free in Context.__eq__ via re-entrant ContextVar.set (#142905)

This commit is contained in:
A.Ibrahim 2026-01-09 13:27:34 +01:00 committed by GitHub
parent a9ca49d9c6
commit a4086d7f89
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 55 additions and 5 deletions

View 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

View File

@ -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`.

View 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