From 22c64f58c06166e7ed7a46a6282ae09c5813a5a2 Mon Sep 17 00:00:00 2001 From: Robert Manner Date: Fri, 24 Jan 2020 13:52:48 +0100 Subject: [PATCH] plugins/python: use separate python interpreter for each plugin On each plugin initialization we create a separate python interpreter which gets stored in the plugin_ctx. The main interpreter is stored in py_ctx and is used for creating more interpreters (if more plugins get loaded) and final python deinitialization. The "traceback" module import and the ImportBlocker initialization was moved, because it has to happen inside the plugin specific interpreters. --- plugins/python/pyhelpers.c | 10 ++++++-- plugins/python/pyhelpers.h | 3 +-- plugins/python/python_plugin_common.c | 37 +++++++++++++++++++-------- plugins/python/python_plugin_common.h | 1 + plugins/python/python_plugin_group.c | 3 +++ plugins/python/python_plugin_io.c | 9 +++++++ plugins/python/python_plugin_policy.c | 9 +++++++ 7 files changed, 57 insertions(+), 15 deletions(-) diff --git a/plugins/python/pyhelpers.c b/plugins/python/pyhelpers.c index d03f06bd6..894309ab2 100644 --- a/plugins/python/pyhelpers.c +++ b/plugins/python/pyhelpers.c @@ -116,13 +116,19 @@ py_create_traceback_string(PyObject *py_traceback) char* traceback = NULL; - if (py_ctx.py_traceback_module != NULL) { - PyObject *py_traceback_str_list = PyObject_CallMethod(py_ctx.py_traceback_module, "format_tb", "(O)", py_traceback); + + PyObject *py_traceback_module = PyImport_ImportModule("traceback"); + if (py_traceback_module == NULL) { + PyErr_Clear(); // do not care, we just won't show backtrace + } else { + PyObject *py_traceback_str_list = PyObject_CallMethod(py_traceback_module, "format_tb", "(O)", py_traceback); if (py_traceback_str_list != NULL) { traceback = py_join_str_list(py_traceback_str_list, ""); Py_DECREF(py_traceback_str_list); } + + Py_CLEAR(py_traceback_module); } debug_return_str(traceback ? traceback : strdup("")); diff --git a/plugins/python/pyhelpers.h b/plugins/python/pyhelpers.h index d8b2dd122..471218797 100644 --- a/plugins/python/pyhelpers.h +++ b/plugins/python/pyhelpers.h @@ -43,8 +43,7 @@ struct PythonContext sudo_printf_t sudo_log; sudo_conv_t sudo_conv; int open_plugin_count; - - PyObject *py_traceback_module; + PyThreadState *py_main_interpreter; }; extern struct PythonContext py_ctx; diff --git a/plugins/python/python_plugin_common.c b/plugins/python/python_plugin_common.c index 060ce0ce5..60566237f 100644 --- a/plugins/python/python_plugin_common.c +++ b/plugins/python/python_plugin_common.c @@ -242,15 +242,9 @@ _python_plugin_register_plugin_in_py_ctx(void) PyImport_AppendInittab("sudo", sudo_module_init); Py_InitializeEx(0); - - if (!sudo_conf_developer_mode() && sudo_module_register_importblocker() < 0) { - py_log_last_error(NULL); - debug_return_int(SUDO_RC_ERROR); - } - - py_ctx.py_traceback_module = PyImport_ImportModule("traceback"); - // if getting the traceback module fails, we just don't show tracebacks - PyErr_Clear(); + py_ctx.py_main_interpreter = PyThreadState_Get(); + } else { + PyThreadState_Swap(py_ctx.py_main_interpreter); } ++py_ctx.open_plugin_count; @@ -267,6 +261,17 @@ python_plugin_init(struct PluginContext *plugin_ctx, char * const plugin_options if (_python_plugin_register_plugin_in_py_ctx() != SUDO_RC_OK) goto cleanup; + plugin_ctx->py_interpreter = Py_NewInterpreter(); + if (plugin_ctx->py_interpreter == NULL) { + goto cleanup; + } + PyThreadState_Swap(plugin_ctx->py_interpreter); + + if (!sudo_conf_developer_mode() && sudo_module_register_importblocker() < 0) { + py_log_last_error(NULL); + debug_return_int(SUDO_RC_ERROR); + } + const char *module_path = _lookup_value(plugin_options, "ModulePath"); if (module_path == NULL) { py_sudo_log(SUDO_CONV_ERROR_MSG, "No python module path is specified. " @@ -321,13 +326,21 @@ python_plugin_deinit(struct PluginContext *plugin_ctx) Py_CLEAR(plugin_ctx->py_instance); Py_CLEAR(plugin_ctx->py_class); Py_CLEAR(plugin_ctx->py_module); + + if (plugin_ctx->py_interpreter != NULL) { + sudo_debug_printf(SUDO_DEBUG_TRACE, "deinit python interpreter for plugin\n"); + Py_EndInterpreter(plugin_ctx->py_interpreter); + } + memset(plugin_ctx, 0, sizeof(*plugin_ctx)); if (py_ctx.open_plugin_count <= 0) { - Py_CLEAR(py_ctx.py_traceback_module); - if (Py_IsInitialized()) { sudo_debug_printf(SUDO_DEBUG_NOTICE, "Closing: deinit python interpreter\n"); + + // we need to call finalize from the main interpreter + PyThreadState_Swap(py_ctx.py_main_interpreter); + Py_Finalize(); } @@ -413,6 +426,8 @@ python_plugin_close(struct PluginContext *plugin_ctx, const char *python_callbac { debug_decl(python_plugin_close, PYTHON_DEBUG_CALLBACKS); + PyThreadState_Swap(plugin_ctx->py_interpreter); + if (!plugin_ctx->call_close) { sudo_debug_printf(SUDO_DEBUG_INFO, "Skipping close call, because there was no command run\n"); diff --git a/plugins/python/python_plugin_common.h b/plugins/python/python_plugin_common.h index 14984f5b7..e10b3c14d 100644 --- a/plugins/python/python_plugin_common.h +++ b/plugins/python/python_plugin_common.h @@ -22,6 +22,7 @@ #include "pyhelpers.h" struct PluginContext { + PyThreadState *py_interpreter; PyObject *py_module; PyObject *py_class; PyObject *py_instance; diff --git a/plugins/python/python_plugin_group.c b/plugins/python/python_plugin_group.c index 1295cebf2..87704e06d 100644 --- a/plugins/python/python_plugin_group.c +++ b/plugins/python/python_plugin_group.c @@ -83,6 +83,7 @@ void python_plugin_group_cleanup(void) { debug_decl(python_plugin_group_cleanup, PYTHON_DEBUG_CALLBACKS); + PyThreadState_Swap(plugin_ctx.py_interpreter); python_plugin_deinit(&plugin_ctx); } @@ -91,6 +92,8 @@ python_plugin_group_query(const char *user, const char *group, const struct pass { debug_decl(python_plugin_group_query, PYTHON_DEBUG_CALLBACKS); + PyThreadState_Swap(plugin_ctx.py_interpreter); + PyObject *py_pwd = py_from_passwd(pwd); if (py_pwd == NULL) { debug_return_int(SUDO_RC_ERROR); diff --git a/plugins/python/python_plugin_io.c b/plugins/python/python_plugin_io.c index 50de682c1..2db8b3690 100644 --- a/plugins/python/python_plugin_io.c +++ b/plugins/python/python_plugin_io.c @@ -134,6 +134,8 @@ python_plugin_io_show_version(struct IOPluginContext *io_ctx, int verbose) { debug_decl(python_plugin_io_show_version, PYTHON_DEBUG_CALLBACKS); + PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter); + if (verbose) { py_sudo_log(SUDO_CONV_INFO_MSG, "Python io plugin API version %d.%d\n", "%d.%d", SUDO_API_VERSION_GET_MAJOR(PY_IO_PLUGIN_VERSION), @@ -147,6 +149,7 @@ int python_plugin_io_log_ttyin(struct IOPluginContext *io_ctx, const char *buf, unsigned int len) { debug_decl(python_plugin_io_log_ttyin, PYTHON_DEBUG_CALLBACKS); + PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter); debug_return_int(python_plugin_api_rc_call(BASE_CTX(io_ctx), CALLBACK_PYNAME(log_ttyin), Py_BuildValue("(s#)", buf, len))); } @@ -155,6 +158,7 @@ int python_plugin_io_log_ttyout(struct IOPluginContext *io_ctx, const char *buf, unsigned int len) { debug_decl(python_plugin_io_log_ttyout, PYTHON_DEBUG_CALLBACKS); + PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter); debug_return_int(python_plugin_api_rc_call(BASE_CTX(io_ctx), CALLBACK_PYNAME(log_ttyout), Py_BuildValue("(s#)", buf, len))); } @@ -163,6 +167,7 @@ int python_plugin_io_log_stdin(struct IOPluginContext *io_ctx, const char *buf, unsigned int len) { debug_decl(python_plugin_io_log_stdin, PYTHON_DEBUG_CALLBACKS); + PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter); debug_return_int(python_plugin_api_rc_call(BASE_CTX(io_ctx), CALLBACK_PYNAME(log_stdin), Py_BuildValue("(s#)", buf, len))); } @@ -171,6 +176,7 @@ int python_plugin_io_log_stdout(struct IOPluginContext *io_ctx, const char *buf, unsigned int len) { debug_decl(python_plugin_io_log_stdout, PYTHON_DEBUG_CALLBACKS); + PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter); debug_return_int(python_plugin_api_rc_call(BASE_CTX(io_ctx), CALLBACK_PYNAME(log_stdout), Py_BuildValue("(s#)", buf, len))); } @@ -179,6 +185,7 @@ int python_plugin_io_log_stderr(struct IOPluginContext *io_ctx, const char *buf, unsigned int len) { debug_decl(python_plugin_io_log_stderr, PYTHON_DEBUG_CALLBACKS); + PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter); debug_return_int(python_plugin_api_rc_call(BASE_CTX(io_ctx), CALLBACK_PYNAME(log_stderr), Py_BuildValue("(s#)", buf, len))); } @@ -187,6 +194,7 @@ int python_plugin_io_change_winsize(struct IOPluginContext *io_ctx, unsigned int line, unsigned int cols) { debug_decl(python_plugin_io_change_winsize, PYTHON_DEBUG_CALLBACKS); + PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter); debug_return_int(python_plugin_api_rc_call(BASE_CTX(io_ctx), CALLBACK_PYNAME(change_winsize), Py_BuildValue("(ii)", line, cols))); } @@ -195,6 +203,7 @@ int python_plugin_io_log_suspend(struct IOPluginContext *io_ctx, int signo) { debug_decl(python_plugin_io_log_suspend, PYTHON_DEBUG_CALLBACKS); + PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter); debug_return_int(python_plugin_api_rc_call(BASE_CTX(io_ctx), CALLBACK_PYNAME(log_suspend), Py_BuildValue("(i)", signo))); } diff --git a/plugins/python/python_plugin_policy.c b/plugins/python/python_plugin_policy.c index caf63cef7..b041e5b49 100644 --- a/plugins/python/python_plugin_policy.c +++ b/plugins/python/python_plugin_policy.c @@ -96,6 +96,8 @@ python_plugin_policy_check(int argc, char * const argv[], debug_decl(python_plugin_policy_check, PYTHON_DEBUG_CALLBACKS); int rc = SUDO_RC_ERROR; + PyThreadState_Swap(plugin_ctx.py_interpreter); + *command_info_out = *argv_out = *user_env_out = NULL; PyObject *py_argv = py_str_array_to_tuple_with_count(argc, argv); @@ -169,6 +171,8 @@ python_plugin_policy_list(int argc, char * const argv[], int verbose, const char { debug_decl(python_plugin_policy_list, PYTHON_DEBUG_CALLBACKS); + PyThreadState_Swap(plugin_ctx.py_interpreter); + PyObject *py_argv = py_str_array_to_tuple_with_count(argc, argv); if (py_argv == NULL) { sudo_debug_printf(SUDO_DEBUG_ERROR, "%s: Failed to create argv argument for the python call\n", __PRETTY_FUNCTION__); @@ -187,6 +191,8 @@ python_plugin_policy_version(int verbose) { debug_decl(python_plugin_policy_version, PYTHON_DEBUG_CALLBACKS); + PyThreadState_Swap(plugin_ctx.py_interpreter); + if (verbose) { py_sudo_log(SUDO_CONV_INFO_MSG, "Python policy plugin API version %d.%d\n", "%d.%d", SUDO_API_VERSION_GET_MAJOR(PY_POLICY_PLUGIN_VERSION), @@ -200,6 +206,7 @@ int python_plugin_policy_validate(void) { debug_decl(python_plugin_policy_validate, PYTHON_DEBUG_CALLBACKS); + PyThreadState_Swap(plugin_ctx.py_interpreter); debug_return_int(python_plugin_api_rc_call(&plugin_ctx, CALLBACK_PYNAME(validate), NULL)); } @@ -207,6 +214,7 @@ void python_plugin_policy_invalidate(int remove) { debug_decl(python_plugin_policy_invalidate, PYTHON_DEBUG_CALLBACKS); + PyThreadState_Swap(plugin_ctx.py_interpreter); python_plugin_api_rc_call(&plugin_ctx, CALLBACK_PYNAME(invalidate), Py_BuildValue("(i)", remove)); debug_return; @@ -217,6 +225,7 @@ python_plugin_policy_init_session(struct passwd *pwd, char **user_env[]) { debug_decl(python_plugin_policy_init_session, PYTHON_DEBUG_CALLBACKS); int rc = SUDO_RC_ERROR; + PyThreadState_Swap(plugin_ctx.py_interpreter); PyObject *py_pwd = NULL, *py_user_env = NULL, *py_result = NULL; py_pwd = py_from_passwd(pwd);