diff --git a/Python/context.c b/Python/context.c index 62b582f271ffe5..77b6478460050b 100644 --- a/Python/context.c +++ b/Python/context.c @@ -791,8 +791,16 @@ contextvar_set(PyContextVar *var, PyObject *val) return -1; } + // gh-148891: _PyHamt_Assoc can allocate HAMT nodes, which may trigger + // GC. A weakref callback or finalizer could then re-enter + // contextvar_set, replacing ctx->ctx_vars via Py_SETREF and freeing + // the old HAMT while _PyHamt_Assoc is still traversing it. + // Prevent this by holding a strong reference to the current vars. + PyHamtObject *old_vars = ctx->ctx_vars; + Py_INCREF(old_vars); PyHamtObject *new_vars = _PyHamt_Assoc( - ctx->ctx_vars, (PyObject *)var, val); + old_vars, (PyObject *)var, val); + Py_DECREF(old_vars); if (new_vars == NULL) { return -1; } @@ -819,8 +827,12 @@ contextvar_del(PyContextVar *var) return -1; } + // gh-148891: same borrowed-reference hazard as contextvar_set: + // _PyHamt_Without allocates nodes, which may trigger GC re-entrancy. PyHamtObject *vars = ctx->ctx_vars; + Py_INCREF(vars); PyHamtObject *new_vars = _PyHamt_Without(vars, (PyObject *)var); + Py_DECREF(vars); if (new_vars == NULL) { return -1; }