
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.
548 lines
15 KiB
C
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);
|
|
}
|