diff --git a/MANIFEST b/MANIFEST index 73a3fedaa..b4f0f9ef1 100644 --- a/MANIFEST +++ b/MANIFEST @@ -316,6 +316,7 @@ plugins/python/pyhelpers_cpychecker.h plugins/python/python_baseplugin.c plugins/python/python_convmessage.c plugins/python/python_importblocker.c +plugins/python/python_plugin_audit.c plugins/python/python_plugin_common.c plugins/python/python_plugin_common.h plugins/python/python_plugin_group.c diff --git a/plugins/python/Makefile.in b/plugins/python/Makefile.in index 55f16f114..ffcc55d96 100644 --- a/plugins/python/Makefile.in +++ b/plugins/python/Makefile.in @@ -118,7 +118,7 @@ EXAMPLES = example_conversation.py example_debugging.py example_group_plugin.p OBJS = python_plugin_common.lo python_plugin_policy.lo python_plugin_io.lo python_plugin_group.lo pyhelpers.lo \ python_importblocker.lo python_convmessage.lo sudo_python_module.lo sudo_python_debug.lo \ - python_baseplugin.lo + python_baseplugin.lo python_plugin_audit.lo IOBJS = $(OBJS:.lo=.i) @@ -265,6 +265,12 @@ python_importblocker.i: $(srcdir)/python_importblocker.c \ $(CC) -E -o $@ $(CPPFLAGS) $< python_importblocker.plog: python_importblocker.i rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/python_importblocker.c --i-file $< --output-file $@ +python_plugin_audit.lo: $(srcdir)/python_plugin_audit.c + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/python_plugin_audit.c +python_plugin_audit.i: $(srcdir)/python_plugin_audit.c + $(CC) -E -o $@ $(CPPFLAGS) $< +python_plugin_audit.plog: python_plugin_audit.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/python_plugin_audit.c --i-file $< --output-file $@ python_plugin_common.lo: $(srcdir)/python_plugin_common.c \ $(incdir)/sudo_conf.h $(incdir)/sudo_queue.h $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/python_plugin_common.c diff --git a/plugins/python/python_plugin_audit.c b/plugins/python/python_plugin_audit.c new file mode 100644 index 000000000..8676e3e38 --- /dev/null +++ b/plugins/python/python_plugin_audit.c @@ -0,0 +1,298 @@ +/* + * 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 "python_plugin_common.h" + +struct AuditPluginContext +{ + struct PluginContext base_ctx; + struct audit_plugin *plugin; +}; + +#define BASE_CTX(audit_ctx) (&(audit_ctx->base_ctx)) + +#define PY_AUDIT_PLUGIN_VERSION SUDO_API_MKVERSION(1, 0) + +#define CALLBACK_PLUGINFUNC(func_name) audit_ctx->plugin->func_name + +// This also verifies compile time that the name matches the sudo plugin API. +#define CALLBACK_PYNAME(func_name) ((void)CALLBACK_PLUGINFUNC(func_name), #func_name) + +#define MARK_CALLBACK_OPTIONAL(function_name) \ + do { \ + python_plugin_mark_callback_optional(plugin_ctx, CALLBACK_PYNAME(function_name), \ + (void **)&CALLBACK_PLUGINFUNC(function_name)); \ + } while(0) + +#define CB_SET_ERROR(errstr) \ + do { \ + const char *cb_error = audit_ctx->base_ctx.callback_error; \ + if (cb_error != NULL && errstr != NULL) { \ + *errstr = cb_error; \ + } \ + } while(0) + +static int +_call_plugin_open(struct AuditPluginContext *audit_ctx, int submit_optind, char * const submit_argv[]) +{ + debug_decl(_call_plugin_open, PYTHON_DEBUG_CALLBACKS); + + struct PluginContext *plugin_ctx = BASE_CTX(audit_ctx); + if (!PyObject_HasAttrString(plugin_ctx->py_instance, CALLBACK_PYNAME(open))) { + debug_return_int(SUDO_RC_OK); + } + + int rc = SUDO_RC_ERROR; + PyObject *py_submit_argv = py_str_array_to_tuple(submit_argv); + + if (py_submit_argv != NULL) { + rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(open), + Py_BuildValue("(iO)", submit_optind, py_submit_argv)); + } else { + rc = SUDO_RC_ERROR; + } + + Py_XDECREF(py_submit_argv); + debug_return_int(rc); +} + +static int +python_plugin_audit_open(struct AuditPluginContext *audit_ctx, + unsigned int version, sudo_conv_t conversation, + sudo_printf_t sudo_printf, char * const settings[], + char * const user_info[], int submit_optind, + char * const submit_argv[], char * const submit_envp[], + char * const plugin_options[], const char **errstr) +{ + debug_decl(python_plugin_audit_open, PYTHON_DEBUG_CALLBACKS); + (void) version; + + int rc = python_plugin_register_logging(conversation, sudo_printf, settings); + if (rc != SUDO_RC_OK) { + debug_return_int(rc); + } + + struct PluginContext *plugin_ctx = BASE_CTX(audit_ctx); + + rc = python_plugin_init(plugin_ctx, plugin_options, version); + if (rc != SUDO_RC_OK) { + debug_return_int(rc); + } + + rc = python_plugin_construct(plugin_ctx, PY_AUDIT_PLUGIN_VERSION, settings, + user_info, submit_envp, plugin_options); + if (rc != SUDO_RC_OK) { + CB_SET_ERROR(errstr); + debug_return_int(rc); + } + + // skip plugin callbacks which are not mandatory + MARK_CALLBACK_OPTIONAL(accept); + MARK_CALLBACK_OPTIONAL(reject); + MARK_CALLBACK_OPTIONAL(error); + MARK_CALLBACK_OPTIONAL(show_version); + + plugin_ctx->call_close = 1; + rc = _call_plugin_open(audit_ctx, submit_optind, submit_argv); + + CB_SET_ERROR(errstr); + + if (PyErr_Occurred()) { + py_log_last_error("Error during calling audit open"); + } + + debug_return_int(rc); +} + +static void +python_plugin_audit_close(struct AuditPluginContext *audit_ctx, int status_type, int status) +{ + debug_decl(python_plugin_audit_close, PYTHON_DEBUG_CALLBACKS); + + python_plugin_close(BASE_CTX(audit_ctx), CALLBACK_PYNAME(close), + Py_BuildValue("(ii)", status_type, status)); + + debug_return; +} + +int +python_plugin_audit_accept(struct AuditPluginContext *audit_ctx, + const char *plugin_name, unsigned int plugin_type, + char * const command_info[], char * const run_argv[], + char * const run_envp[], const char **errstr) +{ + debug_decl(python_plugin_audit_accept, PYTHON_DEBUG_CALLBACKS); + + struct PluginContext *plugin_ctx = BASE_CTX(audit_ctx); + PyThreadState_Swap(plugin_ctx->py_interpreter); + + PyObject *py_command_info = NULL, *py_run_argv = NULL, *py_run_envp = NULL; + int rc = SUDO_RC_ERROR; + + py_run_argv = py_str_array_to_tuple(run_argv); + if (py_run_argv == NULL) + goto cleanup; + + py_command_info = py_str_array_to_tuple(command_info); + if (py_command_info == NULL) + goto cleanup; + + py_run_envp = py_str_array_to_tuple(run_envp); + if (py_run_envp == NULL) + goto cleanup; + + PyObject *py_args = Py_BuildValue("(ziOOO)", plugin_name, plugin_type, py_command_info, py_run_argv, py_run_envp); + rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(accept), py_args); + + CB_SET_ERROR(errstr); + +cleanup: + Py_CLEAR(py_command_info); + Py_CLEAR(py_run_argv); + Py_CLEAR(py_run_envp); + + debug_return_int(rc); +} + +int +python_plugin_audit_reject(struct AuditPluginContext *audit_ctx, + const char *plugin_name, unsigned int plugin_type, + const char *audit_msg, char * const command_info[], const char **errstr) +{ + debug_decl(python_plugin_audit_reject, PYTHON_DEBUG_CALLBACKS); + + struct PluginContext *plugin_ctx = BASE_CTX(audit_ctx); + PyThreadState_Swap(plugin_ctx->py_interpreter); + + PyObject *py_command_info = NULL; + int rc = SUDO_RC_ERROR; + + py_command_info = py_str_array_to_tuple(command_info); + if (PyErr_Occurred()) + goto cleanup; + + PyObject *py_args = Py_BuildValue("(zizO)", plugin_name, plugin_type, audit_msg, py_command_info); + rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(reject), py_args); + + CB_SET_ERROR(errstr); + +cleanup: + Py_CLEAR(py_command_info); + if (PyErr_Occurred()) + py_log_last_error("Error during calling audit reject"); + + debug_return_int(rc); +} + +int +python_plugin_audit_error(struct AuditPluginContext *audit_ctx, + const char *plugin_name, unsigned int plugin_type, + const char *audit_msg, char * const command_info[], const char **errstr) +{ + debug_decl(python_plugin_audit_error, PYTHON_DEBUG_CALLBACKS); + + struct PluginContext *plugin_ctx = BASE_CTX(audit_ctx); + PyThreadState_Swap(plugin_ctx->py_interpreter); + + PyObject *py_command_info = NULL; + int rc = SUDO_RC_ERROR; + + py_command_info = py_str_array_to_tuple(command_info); + if (PyErr_Occurred()) + goto cleanup; + + PyObject *py_args = Py_BuildValue("(zizO)", plugin_name, plugin_type, audit_msg, py_command_info); + rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(error), py_args); + CB_SET_ERROR(errstr); + +cleanup: + Py_CLEAR(py_command_info); + + debug_return_int(rc); +} + +int +python_plugin_audit_show_version(struct AuditPluginContext *audit_ctx, int verbose) +{ + debug_decl(python_plugin_audit_show_version, PYTHON_DEBUG_CALLBACKS); + + struct PluginContext *plugin_ctx = BASE_CTX(audit_ctx); + PyThreadState_Swap(plugin_ctx->py_interpreter); + + if (verbose) { + py_sudo_log(SUDO_CONV_INFO_MSG, "Python audit plugin API version %d.%d\n", + SUDO_API_VERSION_GET_MAJOR(PY_AUDIT_PLUGIN_VERSION), + SUDO_API_VERSION_GET_MINOR(PY_AUDIT_PLUGIN_VERSION)); + } + + debug_return_int(python_plugin_show_version(plugin_ctx, + CALLBACK_PYNAME(show_version), verbose)); +} + +__dso_public struct audit_plugin python_audit; + +// generate symbols for loading multiple audit plugins: +#define AUDIT_SYMBOL_NAME(symbol) symbol +#include "python_plugin_audit_multi.inc" +#define AUDIT_SYMBOL_NAME(symbol) symbol##1 +#include "python_plugin_audit_multi.inc" +#define AUDIT_SYMBOL_NAME(symbol) symbol##2 +#include "python_plugin_audit_multi.inc" +#define AUDIT_SYMBOL_NAME(symbol) symbol##3 +#include "python_plugin_audit_multi.inc" +#define AUDIT_SYMBOL_NAME(symbol) symbol##4 +#include "python_plugin_audit_multi.inc" +#define AUDIT_SYMBOL_NAME(symbol) symbol##5 +#include "python_plugin_audit_multi.inc" +#define AUDIT_SYMBOL_NAME(symbol) symbol##6 +#include "python_plugin_audit_multi.inc" +#define AUDIT_SYMBOL_NAME(symbol) symbol##7 +#include "python_plugin_audit_multi.inc" + +static struct audit_plugin *extra_audit_plugins[] = { + &python_audit1, + &python_audit2, + &python_audit3, + &python_audit4, + &python_audit5, + &python_audit6, + &python_audit7 +}; + +__dso_public struct audit_plugin * +python_audit_clone(void) +{ + static size_t counter = 0; + struct audit_plugin *next_plugin = NULL; + + size_t max = sizeof(extra_audit_plugins) / sizeof(*extra_audit_plugins); + if (counter < max) { + next_plugin = extra_audit_plugins[counter]; + ++counter; + } else if (counter == max) { + ++counter; + py_sudo_log(SUDO_CONV_ERROR_MSG, "sudo: loading more than %d sudo python audit plugins is not supported\n", counter); + } + + return next_plugin; +} diff --git a/plugins/python/python_plugin_audit_multi.inc b/plugins/python/python_plugin_audit_multi.inc new file mode 100644 index 000000000..e64038945 --- /dev/null +++ b/plugins/python/python_plugin_audit_multi.inc @@ -0,0 +1,78 @@ +/* The purpose of this file is to generate a audit_plugin symbols, + * with an I/O plugin context which is unique to it and its functions. + * The callbacks inside are just wrappers around the real functions in python_plugin_audit.c, + * their only purpose is to add the unique context to each separate audit_plugin call. + */ + +#define PLUGIN_CTX AUDIT_SYMBOL_NAME(plugin_ctx) +#define CALLBACK_CFUNC(func_name) AUDIT_SYMBOL_NAME(_python_plugin_audit_ ## func_name) + +extern struct audit_plugin AUDIT_SYMBOL_NAME(python_audit); +static struct AuditPluginContext PLUGIN_CTX = { {}, &AUDIT_SYMBOL_NAME(python_audit) }; + + +static int +CALLBACK_CFUNC(open)(unsigned int version, sudo_conv_t conversation, + sudo_printf_t sudo_printf, char * const settings[], + char * const user_info[], int submit_optind, + char * const submit_argv[], char * const submit_envp[], + char * const plugin_options[], const char **errstr) +{ + return python_plugin_audit_open(&PLUGIN_CTX, version, conversation, sudo_printf, + settings, user_info, submit_optind, submit_argv, submit_envp, + plugin_options, errstr); +} + +static void +CALLBACK_CFUNC(close)(int status_type, int status) +{ + python_plugin_audit_close(&PLUGIN_CTX, status_type, status); +} + +int +CALLBACK_CFUNC(accept)(const char *plugin_name, unsigned int plugin_type, + char * const command_info[], char * const run_argv[], + char * const run_envp[], const char **errstr) +{ + return python_plugin_audit_accept(&PLUGIN_CTX, plugin_name, plugin_type, + command_info, run_argv, run_envp, errstr); +} + +int +CALLBACK_CFUNC(reject)(const char *plugin_name, unsigned int plugin_type, + const char *audit_msg, char * const command_info[], const char **errstr) +{ + return python_plugin_audit_reject(&PLUGIN_CTX, plugin_name, plugin_type, + audit_msg, command_info, errstr); +} + +int +CALLBACK_CFUNC(error)(const char *plugin_name, unsigned int plugin_type, + const char *audit_msg, char * const command_info[], const char **errstr) +{ + return python_plugin_audit_error(&PLUGIN_CTX, plugin_name, plugin_type, + audit_msg, command_info, errstr); +} + +int +CALLBACK_CFUNC(show_version)(int verbose) +{ + return python_plugin_audit_show_version(&PLUGIN_CTX, verbose); +} + +__dso_public struct audit_plugin AUDIT_SYMBOL_NAME(python_audit) = { + SUDO_AUDIT_PLUGIN, + SUDO_API_VERSION, + CALLBACK_CFUNC(open), + CALLBACK_CFUNC(close), + CALLBACK_CFUNC(accept), + CALLBACK_CFUNC(reject), + CALLBACK_CFUNC(error), + CALLBACK_CFUNC(show_version), + NULL, /* register_hooks */ + NULL /* deregister_hooks */ +}; + +#undef PLUGIN_CTX +#undef CALLBACK_CFUNC +#undef AUDIT_SYMBOL_NAME diff --git a/plugins/python/sudo_python_module.c b/plugins/python/sudo_python_module.c index 6760355d7..4a1cc68d1 100644 --- a/plugins/python/sudo_python_module.c +++ b/plugins/python/sudo_python_module.c @@ -504,7 +504,7 @@ void sudo_module_register_enum(PyObject *py_module, const char *enum_name, PyObject *py_constants_dict) { // pseudo code: - // return IntEnum('MyEnum', {'DEFINITION_NAME': DEFINITION_VALUE, ...}) + // return enum.IntEnum('MyEnum', {'DEFINITION_NAME': DEFINITION_VALUE, ...}) debug_decl(sudo_module_register_enum, PYTHON_DEBUG_INTERNAL); @@ -602,6 +602,21 @@ sudo_module_init(void) }; MODULE_REGISTER_ENUM("DEBUG", constants_debug); + struct key_value_str_int constants_exit_reason[] = { + {"NO_STATUS", SUDO_PLUGIN_NO_STATUS}, + {"WAIT_STATUS", SUDO_PLUGIN_WAIT_STATUS}, + {"EXEC_ERROR", SUDO_PLUGIN_EXEC_ERROR}, + {"SUDO_ERROR", SUDO_PLUGIN_SUDO_ERROR} + }; + MODULE_REGISTER_ENUM("EXIT_REASON", constants_exit_reason); + + struct key_value_str_int constants_plugin_types[] = { + {"POLICY", SUDO_POLICY_PLUGIN}, + {"AUDIT", SUDO_AUDIT_PLUGIN}, + {"IO", SUDO_IO_PLUGIN}, + }; + MODULE_REGISTER_ENUM("PLUGIN_TYPE", constants_plugin_types); + // classes if (sudo_module_register_conv_message(py_module) != SUDO_RC_OK) goto cleanup;