Files
sudo/plugins/python/pyhelpers.c
Robert Manner 27de7dd24d plugins/python: only deinit interpreters when sudo unlinks the plugin
This only happens when sudo unloads the last python plugin.
The reason doing so is because there are some python modules which
does not support importing them again after destroying the interpreter
which has imported them previously.

Another solution would be to just leak the interpreters (let the kernel
free up), but then there might be some python resources like open files
would not get cleaned up correctly if the plugin is badly written.

Tests are meant to test the scenario sudo does, so I have modified them
to generally do not unlink but only a few times (~per plugin type) so it
does not use 48 interpreters (one gets started on every plugin->open) and
it is visible at least which type of plugin fails deinit if there is an
error.
2020-02-19 11:48:16 -07:00

548 lines
15 KiB
C

/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2019 Robert Manner <robert.manner@oneidentity.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* This is an open source non-commercial project. Dear PVS-Studio, please check it.
* PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
*/
#include "pyhelpers.h"
#include <pwd.h>
#include <signal.h>
#include "pathnames.h"
static int
_sudo_printf_default(int msg_type, const char *fmt, ...)
{
FILE *fp = stdout;
FILE *ttyfp = NULL;
va_list ap;
int len;
if (ISSET(msg_type, SUDO_CONV_PREFER_TTY)) {
/* Try writing to /dev/tty first. */
ttyfp = fopen(_PATH_TTY, "w");
}
switch (msg_type & 0xff) {
case SUDO_CONV_ERROR_MSG:
fp = stderr;
/* FALLTHROUGH */
case SUDO_CONV_INFO_MSG:
va_start(ap, fmt);
len = vfprintf(ttyfp ? ttyfp : fp, fmt, ap);
va_end(ap);
break;
default:
len = -1;
errno = EINVAL;
break;
}
if (ttyfp != NULL)
fclose(ttyfp);
return len;
}
struct PythonContext py_ctx = {
.sudo_log = &_sudo_printf_default,
};
int
py_is_sudo_log_available(void)
{
debug_decl(py_is_sudo_log_available, PYTHON_DEBUG_INTERNAL);
debug_return_int(py_ctx.sudo_log != &_sudo_printf_default);
}
char *
py_join_str_list(PyObject *py_str_list, const char *separator)
{
debug_decl(py_join_str_list, PYTHON_DEBUG_INTERNAL);
char *result = NULL;
PyObject *py_separator = NULL;
PyObject *py_str = NULL;
py_separator = PyUnicode_FromString(separator);
if (py_separator == NULL)
goto cleanup;
py_str = PyObject_CallMethod(py_separator, "join", "(O)", py_str_list);
if (py_str == NULL) {
goto cleanup;
}
const char *str = PyUnicode_AsUTF8(py_str);
if (str != NULL) {
result = strdup(str);
}
cleanup:
Py_XDECREF(py_str);
Py_XDECREF(py_separator);
debug_return_str(result);
}
char *
py_create_traceback_string(PyObject *py_traceback)
{
debug_decl(py_create_traceback_string, PYTHON_DEBUG_INTERNAL);
if (py_traceback == NULL)
debug_return_str(strdup(""));
char* traceback = NULL;
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(""));
}
void
py_log_last_error(const char *context_message)
{
debug_decl(py_log_last_error, PYTHON_DEBUG_INTERNAL);
if (!PyErr_Occurred()) {
py_sudo_log(SUDO_CONV_ERROR_MSG, "%s\n", context_message);
debug_return;
}
PyObject *py_type = NULL, *py_message = NULL, *py_traceback = NULL;
PyErr_Fetch(&py_type, &py_message, &py_traceback);
char *message = py_message ? py_create_string_rep(py_message) : strdup("(NULL)");
if (message == NULL)
message = strdup("?");
py_sudo_log(SUDO_CONV_ERROR_MSG, "%s%s(%s) %s\n",
context_message ? context_message : "",
context_message && *context_message ? ": " : "",
py_type ? ((PyTypeObject *)py_type)->tp_name : "None",
message);
free(message);
if (py_traceback != NULL) {
char *traceback = py_create_traceback_string(py_traceback);
py_sudo_log(SUDO_CONV_INFO_MSG, "Traceback:\n%s\n", traceback);
free(traceback);
}
Py_XDECREF(py_type);
Py_XDECREF(py_message);
Py_XDECREF(py_traceback);
debug_return;
}
PyObject *
py_str_array_to_tuple_with_count(Py_ssize_t count, char * const strings[])
{
debug_decl(py_str_array_to_tuple_with_count, PYTHON_DEBUG_INTERNAL);
PyObject *py_argv = PyTuple_New(count);
if (py_argv == NULL)
debug_return_ptr(NULL);
for (int i = 0; i < count; ++i) {
PyObject *py_arg = PyUnicode_FromString(strings[i]);
if (py_arg == NULL || PyTuple_SetItem(py_argv, i, py_arg) != 0) {
Py_CLEAR(py_argv);
break;
}
}
debug_return_ptr(py_argv);
}
PyObject *
py_str_array_to_tuple(char * const strings[])
{
debug_decl(py_str_array_to_tuple, PYTHON_DEBUG_INTERNAL);
// find the item count ("strings" ends with NULL terminator):
Py_ssize_t count = 0;
if (strings != NULL) {
while (strings[count] != NULL)
++count;
}
debug_return_ptr(py_str_array_to_tuple_with_count(count, strings));
}
char **
py_str_array_from_tuple(PyObject *py_tuple)
{
debug_decl(py_str_array_from_tuple, PYTHON_DEBUG_INTERNAL);
if (!PyTuple_Check(py_tuple)) {
PyErr_Format(PyExc_ValueError, "%s: value error, argument should be a tuple but it is '%s'",
__PRETTY_FUNCTION__, Py_TYPENAME(py_tuple));
debug_return_ptr(NULL);
}
Py_ssize_t tuple_size = PyTuple_Size(py_tuple);
// we need an extra 0 at the end
char **result = calloc(Py_SSIZE2SIZE(tuple_size) + 1, sizeof(char*));
for (int i = 0; i < tuple_size; ++i) {
PyObject *py_value = PyTuple_GetItem(py_tuple, i);
if (py_value == NULL) {
str_array_free(&result);
debug_return_ptr(NULL);
}
// Note that it can be an "int" or something else as well
char *value = py_create_string_rep(py_value);
if (value == NULL) {
// conversion error is already set
str_array_free(&result);
debug_return_ptr(NULL);
}
result[i] = value;
}
debug_return_ptr(result);
}
PyObject *
py_tuple_get(PyObject *py_tuple, Py_ssize_t index, PyTypeObject *expected_type)
{
debug_decl(py_tuple_get, PYTHON_DEBUG_INTERNAL);
PyObject *py_item = PyTuple_GetItem(py_tuple, index);
if (py_item == NULL) {
debug_return_ptr(NULL);
}
if (!PyObject_TypeCheck(py_item, expected_type)) {
PyErr_Format(PyExc_ValueError, "Value error: tuple element %d should "
"be a '%s' (but it is '%s')",
index, expected_type->tp_name, Py_TYPENAME(py_item));
debug_return_ptr(NULL);
}
debug_return_ptr(py_item);
}
PyObject *
py_create_version(unsigned int version)
{
debug_decl(py_create_version, PYTHON_DEBUG_INTERNAL);
debug_return_ptr(PyUnicode_FromFormat("%d.%d", SUDO_API_VERSION_GET_MAJOR(version),
SUDO_API_VERSION_GET_MINOR(version)));
}
PyObject *
py_from_passwd(const struct passwd *pwd)
{
debug_decl(py_from_passwd, PYTHON_DEBUG_INTERNAL);
if (pwd == NULL) {
debug_return_ptr_pynone;
}
// Create a tuple similar and convertible to python "struct_passwd" of "pwd" module
debug_return_ptr(
Py_BuildValue("(zziizzz)", pwd->pw_name, pwd->pw_passwd,
pwd->pw_uid, pwd->pw_gid, pwd->pw_gecos,
pwd->pw_dir, pwd->pw_shell)
);
}
char *
py_create_string_rep(PyObject *py_object)
{
debug_decl(py_create_string_rep, PYTHON_DEBUG_INTERNAL);
char *result = NULL;
if (py_object == NULL)
debug_return_ptr(NULL);
PyObject *py_string = PyObject_Str(py_object);
if (py_string != NULL) {
const char *bytes = PyUnicode_AsUTF8(py_string);
if (bytes != NULL) {
result = strdup(bytes);
}
}
Py_XDECREF(py_string);
debug_return_ptr(result);
}
static void
_py_debug_python_function(const char *class_name, const char *function_name, const char *message,
PyObject *py_args, PyObject *py_kwargs, int subsystem_id)
{
debug_decl_vars(_py_debug_python_function, subsystem_id);
if (sudo_debug_needed(SUDO_DEBUG_DIAG)) {
char *args_str = NULL;
char *kwargs_str = NULL;
if (py_args != NULL)
args_str = py_create_string_rep(py_args);
if (py_kwargs != NULL) {
kwargs_str = py_create_string_rep(py_kwargs);
}
if (args_str == NULL)
args_str = strdup("()");
if (kwargs_str == NULL)
kwargs_str = strdup("");
sudo_debug_printf(SUDO_DEBUG_DIAG, "%s.%s %s: %s %s\n", class_name,
function_name, message, args_str, kwargs_str);
free(args_str);
free(kwargs_str);
}
}
void
py_debug_python_call(const char *class_name, const char *function_name,
PyObject *py_args, PyObject *py_kwargs, int subsystem_id)
{
debug_decl_vars(_py_debug_python_function, subsystem_id);
if (subsystem_id == PYTHON_DEBUG_C_CALLS && sudo_debug_needed(SUDO_DEBUG_INFO)) {
// at this level we also output the callee python script
char *callee_func_name = NULL, *callee_file_name = NULL;
long callee_line_number = -1;
if (py_get_current_execution_frame(&callee_file_name, &callee_line_number, &callee_func_name) == SUDO_RC_OK) {
sudo_debug_printf(SUDO_DEBUG_INFO, "%s @ %s:%ld calls C function:\n",
callee_func_name, callee_file_name, callee_line_number);
}
free(callee_func_name);
free(callee_file_name);
}
_py_debug_python_function(class_name, function_name, "was called with arguments",
py_args, py_kwargs, subsystem_id);
}
void
py_debug_python_result(const char *class_name, const char *function_name,
PyObject *py_result, int subsystem_id)
{
if (py_result == NULL) {
debug_decl_vars(py_debug_python_result, subsystem_id);
sudo_debug_printf(SUDO_CONV_ERROR_MSG, "%s.%s call failed\n",
class_name, function_name);
} else {
_py_debug_python_function(class_name, function_name, "returned result",
py_result, NULL, subsystem_id);
}
}
void
str_array_free(char ***array)
{
debug_decl(str_array_free, PYTHON_DEBUG_INTERNAL);
if (*array == NULL)
debug_return;
for (char **item_ptr = *array; *item_ptr != NULL; ++item_ptr)
free(*item_ptr);
free(*array);
*array = NULL;
debug_return;
}
int
py_get_current_execution_frame(char **file_name, long *line_number, char **function_name)
{
*file_name = NULL;
*line_number = (long)-1;
*function_name = NULL;
PyObject *py_err_type = NULL, *py_err_value = NULL, *py_err_traceback = NULL;
PyErr_Fetch(&py_err_type, &py_err_value, &py_err_traceback);
PyObject *py_frame = NULL, *py_f_code = NULL,
*py_filename = NULL, *py_function_name = NULL;
PyObject *py_getframe = PySys_GetObject("_getframe");
if (py_getframe == NULL)
goto cleanup;
py_frame = PyObject_CallFunction(py_getframe, "i", 0);
if (py_frame == NULL)
goto cleanup;
*line_number = py_object_get_optional_attr_number(py_frame, "f_lineno");
py_f_code = py_object_get_optional_attr(py_frame, "f_code", NULL);
if (py_f_code != NULL) {
py_filename = py_object_get_optional_attr(py_f_code, "co_filename", NULL);
if (py_filename != NULL)
*file_name = strdup(PyUnicode_AsUTF8(py_filename));
py_function_name = py_object_get_optional_attr(py_f_code, "co_name", NULL);
if (py_function_name != NULL)
*function_name = strdup(PyUnicode_AsUTF8(py_function_name));
}
cleanup:
Py_CLEAR(py_frame);
Py_CLEAR(py_f_code);
Py_CLEAR(py_filename);
Py_CLEAR(py_function_name);
// we hide every error happening inside this function
PyErr_Restore(py_err_type, py_err_value, py_err_traceback);
return (*file_name && *function_name && (*line_number >= 0)) ?
SUDO_RC_OK : SUDO_RC_ERROR;
}
void
py_ctx_reset()
{
memset(&py_ctx, 0, sizeof(py_ctx));
py_ctx.sudo_log = &_sudo_printf_default;
}
int
py_sudo_conv(int num_msgs, const struct sudo_conv_message msgs[],
struct sudo_conv_reply replies[], struct sudo_conv_callback *callback)
{
/* Enable suspend during password entry. */
struct sigaction sa, saved_sigtstp;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = SIG_DFL;
(void) sigaction(SIGTSTP, &sa, &saved_sigtstp);
int rc = SUDO_RC_ERROR;
if (py_ctx.sudo_conv != NULL)
rc = py_ctx.sudo_conv((int)num_msgs, msgs, replies, callback);
/* Restore signal handlers and signal mask. */
(void) sigaction(SIGTSTP, &saved_sigtstp, NULL);
return rc;
}
PyObject *
py_object_get_optional_attr(PyObject *py_object, const char *attr, PyObject *py_default)
{
if (PyObject_HasAttrString(py_object, attr)) {
return PyObject_GetAttrString(py_object, attr);
}
Py_XINCREF(py_default); // whatever we return will have its refcount incremented
return py_default;
}
const char *
py_object_get_optional_attr_string(PyObject *py_object, const char *attr_name)
{
PyObject *py_value = py_object_get_optional_attr(py_object, attr_name, NULL);
if (py_value == NULL)
return NULL;
const char *value = PyUnicode_AsUTF8(py_value);
Py_CLEAR(py_value); // Note, the object still has reference to the attribute
return value;
}
long long
py_object_get_optional_attr_number(PyObject *py_object, const char *attr_name)
{
PyObject *py_value = py_object_get_optional_attr(py_object, attr_name, NULL);
if (py_value == NULL)
return -1;
long long value = PyLong_AsLongLong(py_value);
Py_CLEAR(py_value);
return value;
}
void
py_object_set_attr_number(PyObject *py_object, const char *attr_name, long long number)
{
PyObject *py_number = PyLong_FromLong(number);
if (py_number == NULL)
return;
PyObject_SetAttrString(py_object, attr_name, py_number);
Py_CLEAR(py_number);
}
void
py_object_set_attr_string(PyObject *py_object, const char *attr_name, const char *value)
{
PyObject *py_value = PyUnicode_FromString(value);
if (py_value == NULL)
return;
PyObject_SetAttrString(py_object, attr_name, py_value);
Py_CLEAR(py_value);
}
PyObject *
py_dict_create_string_int(size_t count, struct key_value_str_int *key_values)
{
debug_decl(py_dict_create_string_int, PYTHON_DEBUG_INTERNAL);
PyObject *py_value = NULL;
PyObject *py_dict = PyDict_New();
if (py_dict == NULL)
goto cleanup;
for (size_t i = 0; i < count; ++i) {
py_value = PyLong_FromLong(key_values[i].value);
if (py_value == NULL)
goto cleanup;
if (PyDict_SetItemString(py_dict, key_values[i].key, py_value) < 0)
goto cleanup;
Py_CLEAR(py_value);
}
cleanup:
if (PyErr_Occurred()) {
Py_CLEAR(py_dict);
}
Py_CLEAR(py_value);
debug_return_ptr(py_dict);
}