From 0505ac8974138726fbeae329bd38e696222124ab Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 6 Feb 2026 10:51:02 +0100 Subject: [PATCH] Raise an error when importing modules compiled for an older Python This is a downstream workaround "implementing" https://github.com/python/cpython/pull/137212 - the mechanism for the check exists in Python 3.15+, where it needs to be added to the standard library modules. In Fedora, we need it also in previous Python versions, as we experience segmentation fault when importing stdlib modules after update while Python is running. _tkinter, _tracemalloc and readline are not calling PyModuleDef_Init, which is modified with this patch, hence they need a direct call to the check function. --- Include/moduleobject.h | 42 ++++++++++++++++++++++++++++++++++++++++++ Makefile.pre.in | 3 +++ Modules/_tkinter.c | 4 ++++ Modules/_tracemalloc.c | 4 ++++ Modules/readline.c | 4 ++++ Objects/moduleobject.c | 1 + 6 files changed, 58 insertions(+) diff --git a/Include/moduleobject.h b/Include/moduleobject.h index 2a17c891dda811..d8c0618f54daee 100644 --- a/Include/moduleobject.h +++ b/Include/moduleobject.h @@ -116,6 +116,48 @@ struct PyModuleDef { freefunc m_free; }; +#if defined(_PyHack_check_version_on_modinit) && defined(Py_BUILD_CORE) +/* The mechanism for the check has been implemented on Python 3.15+: + * https://github.com/python/cpython/pull/137212 + * In Fedora, we need this in older Pythons too: + * if somebody attempts to import a module compiled for a different Python version, + * instead of segmentation fault a meaningful error is raised. + */ +PyAPI_DATA(const unsigned long) Py_Version; + +static inline int +_PyHack_CheckInternalAPIVersion(const char *mod_name) +{ + if (PY_VERSION_HEX != Py_Version) { + PyErr_Format( + PyExc_ImportError, + "internal Python C API version mismatch: " + "module %s compiled with %lu.%lu.%lu; runtime version is %lu.%lu.%lu", + mod_name, + (const unsigned long)((PY_VERSION_HEX >> 24) & 0xFF), + (const unsigned long)((PY_VERSION_HEX >> 16) & 0xFF), + (const unsigned long)((PY_VERSION_HEX >> 8) & 0xFF), + (const unsigned long)((Py_Version >> 24) & 0xFF), + (const unsigned long)((Py_Version >> 16) & 0xFF), + (const unsigned long)((Py_Version >> 8) & 0xFF) + ); + return -1; + } + return 0; +} + +static inline PyObject * +PyModuleDef_Init_with_check(PyModuleDef *def) +{ + if (_PyHack_CheckInternalAPIVersion(def->m_name) < 0) { + return NULL; + } + return PyModuleDef_Init(def); +} + +#define PyModuleDef_Init PyModuleDef_Init_with_check +#endif + #ifdef __cplusplus } #endif diff --git a/Makefile.pre.in b/Makefile.pre.in index 38a355a23f2aab..f2d909c8c9d5ce 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -3415,3 +3415,6 @@ MODULE__MULTIBYTECODEC_DEPS=$(srcdir)/Modules/cjkcodecs/multibytecodec.h # Local Variables: # mode: makefile # End: + +# Fedora-specific, downstream only +CFLAGS += -D_PyHack_check_version_on_modinit=1 diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 2216de509e939c..e5438aa0b298a7 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -3489,6 +3489,10 @@ static struct PyModuleDef _tkintermodule = { PyMODINIT_FUNC PyInit__tkinter(void) { + if (_PyHack_CheckInternalAPIVersion("_tkinter") < 0) { + return NULL; + } + PyObject *m, *uexe, *cexe; tcl_lock = PyThread_allocate_lock(); diff --git a/Modules/_tracemalloc.c b/Modules/_tracemalloc.c index be71fc9fc9c341..5cb33c5b26804e 100644 --- a/Modules/_tracemalloc.c +++ b/Modules/_tracemalloc.c @@ -215,6 +215,10 @@ static struct PyModuleDef module_def = { PyMODINIT_FUNC PyInit__tracemalloc(void) { + if (_PyHack_CheckInternalAPIVersion("_tracemalloc") < 0) { + return NULL; + } + PyObject *mod = PyModule_Create(&module_def); if (mod == NULL) { return NULL; diff --git a/Modules/readline.c b/Modules/readline.c index 8475846eefc905..bce782995b4888 100644 --- a/Modules/readline.c +++ b/Modules/readline.c @@ -1604,6 +1604,10 @@ static struct PyModuleDef readlinemodule = { PyMODINIT_FUNC PyInit_readline(void) { + if (_PyHack_CheckInternalAPIVersion("readline") < 0) { + return NULL; + } + const char *backend = "readline"; PyObject *m; readlinestate *mod_state; diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index b68584b5dd571d..cbf95dc92acfdc 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -50,6 +50,7 @@ _PyModule_IsExtension(PyObject *obj) } +#undef PyModuleDef_Init PyObject* PyModuleDef_Init(PyModuleDef* def) {