diff --git a/include/pybind11/detail/class.h b/include/pybind11/detail/class.h index 480c369aa..5d2432d5b 100644 --- a/include/pybind11/detail/class.h +++ b/include/pybind11/detail/class.h @@ -251,6 +251,15 @@ extern "C" inline void pybind11_meta_dealloc(PyObject *obj) { } }); + // Release the references to the internals capsules that were acquired in make_new_python_type. + // See the comment there for details on preventing use-after-free during interpreter shutdown. + if (PyObject *capsule = get_internals_capsule()) { + Py_DECREF(capsule); + } + if (PyObject *capsule = get_local_internals_capsule()) { + Py_DECREF(capsule); + } + PyType_Type.tp_dealloc(obj); } @@ -829,6 +838,19 @@ inline PyObject *make_new_python_type(const type_record &rec) { PYBIND11_SET_OLDPY_QUALNAME(type, qualname); + // Prevent use-after-free during interpreter shutdown. GC order is not guaranteed, so the + // internals capsule may be destroyed (resetting internals via internals_shutdown) before all + // pybind11 types are destroyed. If a type's tp_traverse/tp_clear then calls py::cast, it + // would recreate an empty internals and fail because the type registry is gone. By holding + // references to the capsules, we ensure they outlive all pybind11 types. The decref happens + // in pybind11_meta_dealloc. + if (PyObject *capsule = get_internals_capsule()) { + Py_INCREF(capsule); + } + if (PyObject *capsule = get_local_internals_capsule()) { + Py_INCREF(capsule); + } + return reinterpret_cast(type); } diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index e2dee77ec..619481a5b 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -777,7 +777,7 @@ private: on_fetch_function *on_fetch_ = nullptr; // Pointer-to-pointer to the singleton internals for the first seen interpreter (may not be the // main interpreter) - std::unique_ptr *internals_singleton_pp_; + std::unique_ptr *internals_singleton_pp_ = nullptr; }; // If We loaded the internals through `state_dict`, our `error_already_set` @@ -829,6 +829,35 @@ PYBIND11_NOINLINE internals &get_internals() { return *internals_ptr; } +/// Return the PyObject* for the internals capsule (borrowed reference). +/// Returns nullptr if the capsule doesn't exist yet. +/// This is used to prevent use-after-free during interpreter shutdown by allowing pybind11 types +/// to hold a reference to the capsule (see make_new_python_type in class.h). +inline PyObject *get_internals_capsule() { + auto state_dict = reinterpret_borrow(get_python_state_dict()); + return dict_getitemstring(state_dict.ptr(), PYBIND11_INTERNALS_ID); +} + +/// Return the key used for local_internals in the state dict. +/// This function ensures a consistent key is used across all call sites within the same +/// compilation unit. The key includes the address of a static variable to make it unique per +/// module (DSO), matching the behavior of get_local_internals_pp_manager(). +inline const std::string &get_local_internals_key() { + static const std::string key + = PYBIND11_MODULE_LOCAL_ID + std::to_string(reinterpret_cast(&key)); + return key; +} + +/// Return the PyObject* for the local_internals capsule (borrowed reference). +/// Returns nullptr if the capsule doesn't exist yet. +/// This is used to prevent use-after-free during interpreter shutdown by allowing pybind11 types +/// to hold a reference to the capsule (see make_new_python_type in class.h). +inline PyObject *get_local_internals_capsule() { + const auto &key = get_local_internals_key(); + auto state_dict = reinterpret_borrow(get_python_state_dict()); + return dict_getitemstring(state_dict.ptr(), key.c_str()); +} + inline void ensure_internals() { pybind11::detail::get_internals_pp_manager().unref(); #ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT @@ -840,12 +869,10 @@ inline void ensure_internals() { } inline internals_pp_manager &get_local_internals_pp_manager() { - // Use the address of this static itself as part of the key, so that the value is uniquely tied + // Use the address of a static variable as part of the key, so that the value is uniquely tied // to where the module is loaded in memory - static const std::string this_module_idstr - = PYBIND11_MODULE_LOCAL_ID - + std::to_string(reinterpret_cast(&this_module_idstr)); - return internals_pp_manager::get_instance(this_module_idstr.c_str(), nullptr); + return internals_pp_manager::get_instance(get_local_internals_key().c_str(), + nullptr); } /// Works like `get_internals`, but for things which are locally registered.