From 0184c0212f3cf4a2362db6bd1d59ba39b6982d9b Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sun, 29 Mar 2026 22:03:41 +0700 Subject: [PATCH] Harden `PYBIND11_MODULE_PYINIT` and `get_internals()` against crashes during module init (#6018) * Wrap ensure_internals() in try-catch in PYBIND11_MODULE_PYINIT Previously, ensure_internals() was called without exception handling in the PyInit_* function (PYBIND11_MODULE_PYINIT), while the same call in PYBIND11_MODULE_EXEC was already wrapped in try-catch. On MSVC, a C++ exception propagating through the extern "C" PyInit_* boundary is undefined behavior, which can manifest as an access violation instead of a clean error message. This is a potential contributor to crashes like gh-5993. Wrap the entire PyInit body in try/catch using the existing PYBIND11_CATCH_INIT_EXCEPTIONS pattern. Made-with: Cursor * Add nullptr guards in get_internals() for better crash diagnostics Add explicit null checks after get_pp() and create_pp_content_once() in get_internals(), calling pybind11_fail() with descriptive messages. These guards convert potential null-pointer dereferences (which produce unhelpful access-violation crashes, especially on Windows) into clear runtime_error messages that can be caught and reported as ImportError by the try-catch added in the previous commit. Made-with: Cursor --- include/pybind11/detail/common.h | 31 +++++++++++++++++------------ include/pybind11/detail/internals.h | 9 ++++++++- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index c0e4b7f66..29188155d 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -449,19 +449,24 @@ PyModuleDef_Init should be treated like any other PyObject (so not shared across static int PYBIND11_CONCAT(pybind11_exec_, name)(PyObject *); \ PYBIND11_PLUGIN_IMPL(name) { \ PYBIND11_CHECK_PYTHON_VERSION \ - pybind11::detail::ensure_internals(); \ - static ::pybind11::detail::slots_array mod_def_slots = ::pybind11::detail::init_slots( \ - &PYBIND11_CONCAT(pybind11_exec_, name), ##__VA_ARGS__); \ - static PyModuleDef def{/* m_base */ PyModuleDef_HEAD_INIT, \ - /* m_name */ PYBIND11_TOSTRING(name), \ - /* m_doc */ nullptr, \ - /* m_size */ 0, \ - /* m_methods */ nullptr, \ - /* m_slots */ mod_def_slots.data(), \ - /* m_traverse */ nullptr, \ - /* m_clear */ nullptr, \ - /* m_free */ nullptr}; \ - return PyModuleDef_Init(&def); \ + try { \ + pybind11::detail::ensure_internals(); \ + static ::pybind11::detail::slots_array mod_def_slots \ + = ::pybind11::detail::init_slots(&PYBIND11_CONCAT(pybind11_exec_, name), \ + ##__VA_ARGS__); \ + static PyModuleDef def{/* m_base */ PyModuleDef_HEAD_INIT, \ + /* m_name */ PYBIND11_TOSTRING(name), \ + /* m_doc */ nullptr, \ + /* m_size */ 0, \ + /* m_methods */ nullptr, \ + /* m_slots */ mod_def_slots.data(), \ + /* m_traverse */ nullptr, \ + /* m_clear */ nullptr, \ + /* m_free */ nullptr}; \ + return PyModuleDef_Init(&def); \ + } \ + PYBIND11_CATCH_INIT_EXCEPTIONS \ + return nullptr; \ } #define PYBIND11_MODULE_EXEC(name, variable) \ diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index b68152932..a38839ec0 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -864,7 +864,11 @@ inline internals_pp_manager &get_internals_pp_manager() { /// Return a reference to the current `internals` data PYBIND11_NOINLINE internals &get_internals() { auto &ppmgr = get_internals_pp_manager(); - auto &internals_ptr = *ppmgr.get_pp(); + auto *pp = ppmgr.get_pp(); + if (!pp) { + pybind11_fail("get_internals: get_pp() returned nullptr"); + } + auto &internals_ptr = *pp; if (!internals_ptr) { // Slow path, something needs fetched from the state dict or created gil_scoped_acquire_simple gil; @@ -872,6 +876,9 @@ PYBIND11_NOINLINE internals &get_internals() { ppmgr.create_pp_content_once(&internals_ptr); + if (!internals_ptr) { + pybind11_fail("get_internals: create_pp_content_once() produced nullptr"); + } if (!internals_ptr->instance_base) { // This calls get_internals, so cannot be called from within the internals constructor // called above because internals_ptr must be set before get_internals is called again