/* * SPDX-License-Identifier: ISC * * Copyright (c) 2019 Robert Manner * * 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 #include static int _sudo_printf_noop(int msg_type, const char *fmt, ...) { (void) msg_type; (void) fmt; return 0; } struct PythonContext py_ctx = { &_sudo_printf_noop, NULL, 0, NULL }; 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_noop); } 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; if (py_ctx.py_traceback_module != NULL) { PyObject *py_traceback_str_list = PyObject_CallMethod(py_ctx.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); } } 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, *py_message, *py_traceback; 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_lineno = 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; py_lineno = PyObject_GetAttrString(py_frame, "f_lineno"); if (py_lineno != NULL) { *line_number = PyLong_AsLong(py_lineno); } py_f_code = PyObject_GetAttrString(py_frame, "f_code"); if (py_f_code != NULL) { py_filename = PyObject_GetAttrString(py_f_code, "co_filename"); if (py_filename != NULL) *file_name = strdup(PyUnicode_AsUTF8(py_filename)); py_function_name = PyObject_GetAttrString(py_f_code, "co_name"); if (py_function_name != NULL) *function_name = strdup(PyUnicode_AsUTF8(py_function_name)); } cleanup: Py_CLEAR(py_frame); Py_CLEAR(py_lineno); 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_noop; } 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; }