diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 13:14:46 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 13:14:46 +0000 |
commit | 025c439e829e0db9ac511cd9c1b8d5fd53475ead (patch) | |
tree | fa6986b4690f991613ffb97cea1f6942427baf5d /plugins/sudoers/audit.c | |
parent | Initial commit. (diff) | |
download | sudo-upstream/1.9.15p5.tar.xz sudo-upstream/1.9.15p5.zip |
Adding upstream version 1.9.15p5.upstream/1.9.15p5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'plugins/sudoers/audit.c')
-rw-r--r-- | plugins/sudoers/audit.c | 494 |
1 files changed, 494 insertions, 0 deletions
diff --git a/plugins/sudoers/audit.c b/plugins/sudoers/audit.c new file mode 100644 index 0000000..a469514 --- /dev/null +++ b/plugins/sudoers/audit.c @@ -0,0 +1,494 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2009-2015, 2019-2023 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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 <config.h> + +#include <sys/wait.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <sudoers.h> +#ifdef SUDOERS_LOG_CLIENT +# include <log_client.h> +#endif + +#ifdef HAVE_BSM_AUDIT +# include <bsm_audit.h> +#endif +#ifdef HAVE_LINUX_AUDIT +# include <linux_audit.h> +#endif +#ifdef HAVE_SOLARIS_AUDIT +# include <solaris_audit.h> +#endif + +#ifdef SUDOERS_LOG_CLIENT +static struct log_details audit_details; +#endif +char *audit_msg = NULL; + +/* sudoers_audit is declared at the end of this file. */ +extern sudo_dso_public struct audit_plugin sudoers_audit; + +static int +audit_success(const struct sudoers_context *ctx, char *const argv[]) +{ + int rc = 0; + debug_decl(audit_success, SUDOERS_DEBUG_AUDIT); + + if (argv != NULL) { +#ifdef HAVE_BSM_AUDIT + if (bsm_audit_success(ctx, argv) == -1) + rc = -1; +#endif +#ifdef HAVE_LINUX_AUDIT + if (linux_audit_command(argv, 1) == -1) + rc = -1; +#endif +#ifdef HAVE_SOLARIS_AUDIT + if (solaris_audit_success(ctx, argv) == -1) + rc = -1; +#endif + } + + debug_return_int(rc); +} + +static int +audit_failure_int(const struct sudoers_context *ctx, char *const argv[], + const char *message) +{ + int ret = 0; + debug_decl(audit_failure_int, SUDOERS_DEBUG_AUDIT); + +#if defined(HAVE_BSM_AUDIT) || defined(HAVE_LINUX_AUDIT) + if (def_log_denied && argv != NULL) { +#ifdef HAVE_BSM_AUDIT + if (bsm_audit_failure(ctx, argv, message) == -1) + ret = -1; +#endif +#ifdef HAVE_LINUX_AUDIT + if (linux_audit_command(argv, 0) == -1) + ret = -1; +#endif +#ifdef HAVE_SOLARIS_AUDIT + if (solaris_audit_failure(ctx, argv, message) == -1) + ret = -1; +#endif + } +#endif /* HAVE_BSM_AUDIT || HAVE_LINUX_AUDIT */ + + debug_return_int(ret); +} + +int +vaudit_failure(const struct sudoers_context *ctx, char *const argv[], + char const * restrict const fmt, va_list ap) +{ + int oldlocale, ret; + char *message; + debug_decl(vaudit_failure, SUDOERS_DEBUG_AUDIT); + + /* Audit messages should be in the sudoers locale. */ + sudoers_setlocale(SUDOERS_LOCALE_SUDOERS, &oldlocale); + + if ((ret = vasprintf(&message, _(fmt), ap)) == -1) + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + + if (ret != -1) { + /* Set audit_msg for audit plugins. */ + free(audit_msg); + audit_msg = message; + + ret = audit_failure_int(ctx, argv, audit_msg); + } + + sudoers_setlocale(oldlocale, NULL); + + debug_return_int(ret); +} + +int +audit_failure(const struct sudoers_context *ctx, char *const argv[], + char const * restrict const fmt, ...) +{ + va_list ap; + int ret; + debug_decl(audit_failure, SUDOERS_DEBUG_AUDIT); + + va_start(ap, fmt); + ret = vaudit_failure(ctx, argv, fmt, ap); + va_end(ap); + + debug_return_int(ret); +} + +static int +sudoers_audit_open(unsigned int version, sudo_conv_t conversation, + sudo_printf_t plugin_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) +{ + struct sudo_conf_debug_file_list debug_files = TAILQ_HEAD_INITIALIZER(debug_files); + struct sudoers_open_info info; + const char *cp, *plugin_path = NULL; + char * const *cur; + int ret; + debug_decl(sudoers_audit_open, SUDOERS_DEBUG_PLUGIN); + + sudo_conv = conversation; + sudo_printf = plugin_printf; + if (sudoers_audit.event_alloc != NULL) + plugin_event_alloc = sudoers_audit.event_alloc; + + bindtextdomain("sudoers", LOCALEDIR); + + /* Initialize the debug subsystem. */ + for (cur = settings; (cp = *cur) != NULL; cur++) { + if (strncmp(cp, "debug_flags=", sizeof("debug_flags=") - 1) == 0) { + cp += sizeof("debug_flags=") - 1; + if (!sudoers_debug_parse_flags(&debug_files, cp)) + debug_return_int(-1); + continue; + } + if (strncmp(cp, "plugin_path=", sizeof("plugin_path=") - 1) == 0) { + plugin_path = cp + sizeof("plugin_path=") - 1; + continue; + } + } + if (!sudoers_debug_register(plugin_path, &debug_files)) + debug_return_int(-1); + + /* Call the sudoers init function. */ + info.settings = settings; + info.user_info = user_info; + info.plugin_args = plugin_options; + ret = sudoers_init(&info, log_parse_error, submit_envp); + + if (ret == true) { + /* Unset close function if we don't need it to avoid extra process. */ +#ifdef SUDOERS_LOG_CLIENT + if (client_closure == NULL) +#endif + sudoers_audit.close = NULL; + } else { + /* The audit functions set audit_msg on failure. */ + if (audit_msg != NULL) + *errstr = audit_msg; + } + + debug_return_int(ret); +} + +static void +audit_to_eventlog(const struct sudoers_context *ctx, struct eventlog *evlog, + char * const command_info[], char * const run_argv[], + char * const run_envp[], const char *uuid_str) +{ + char * const *cur; + debug_decl(audit_to_eventlog, SUDOERS_DEBUG_PLUGIN); + + /* Fill in evlog from sudoers Defaults, run_argv and run_envp. */ + sudoers_to_eventlog(ctx, evlog, NULL, run_argv, run_envp, uuid_str); + + /* Update iolog and execution environment from command_info[]. */ + if (command_info != NULL) { + for (cur = command_info; *cur != NULL; cur++) { + switch (**cur) { + case 'c': + if (strncmp(*cur, "command=", sizeof("command=") - 1) == 0) { + evlog->command = *cur + sizeof("command=") - 1; + continue; + } + if (strncmp(*cur, "chroot=", sizeof("chroot=") - 1) == 0) { + evlog->runchroot = *cur + sizeof("chroot=") - 1; + continue; + } + break; + case 'i': + if (strncmp(*cur, "iolog_path=", sizeof("iolog_path=") - 1) == 0) { + evlog->iolog_path = *cur + sizeof("iolog_path=") - 1; + continue; + } + break; + case 'r': + if (strncmp(*cur, "runcwd=", sizeof("runcwd=") - 1) == 0) { + evlog->runcwd = *cur + sizeof("runcwd=") - 1; + continue; + } + break; + } + } + } + + debug_return; +} + +#ifdef SUDOERS_LOG_CLIENT +static bool +log_server_accept(const struct sudoers_context *ctx, struct eventlog *evlog) +{ + struct timespec now; + bool ret = false; + debug_decl(log_server_accept, SUDOERS_DEBUG_PLUGIN); + + if (SLIST_EMPTY(&def_log_servers)) + debug_return_bool(true); + + if (client_closure != NULL && ISSET(ctx->mode, MODE_POLICY_INTERCEPTED)) { + /* Older servers don't support multiple commands per session. */ + if (!client_closure->subcommands) + debug_return_bool(true); + } else { + /* Only send accept event to log server if I/O log plugin did not. */ + if (iolog_enabled) + debug_return_bool(true); + } + + if (sudo_gettime_real(&now) == -1) { + sudo_warn("%s", U_("unable to get time of day")); + goto done; + } + + if (client_closure != NULL) { + /* Use existing client closure. */ + if (fmt_accept_message(client_closure, evlog)) { + if (client_closure->write_ev->add(client_closure->write_ev, + &client_closure->log_details->server_timeout) == -1) { + sudo_warn("%s", U_("unable to add event to queue")); + goto done; + } + ret = true; + } + } else { + if (!init_log_details(&audit_details, evlog)) + goto done; + + /* Open connection to log server, send hello and accept messages. */ + client_closure = log_server_open(&audit_details, &now, false, + SEND_ACCEPT, NULL); + if (client_closure != NULL) + ret = true; + } + +done: + debug_return_bool(ret); +} + +static void +log_server_exit(int status_type, int status) +{ + debug_decl(log_server_exit, SUDOERS_DEBUG_PLUGIN); + + if (client_closure != NULL) { + int exit_status = 0, error = 0; + + if (status_type == SUDO_PLUGIN_WAIT_STATUS) { + if (WIFEXITED(status)) + exit_status = WEXITSTATUS(status); + else + exit_status = WTERMSIG(status) | 128; + } else { + /* Must be errno. */ + error = status; + } + log_server_close(client_closure, exit_status, error); + client_closure = NULL; + free(audit_details.evlog); + audit_details.evlog = NULL; + } + + debug_return; +} +#else +static bool +log_server_accept(const struct sudoers_context *ctx, struct eventlog *evlog) +{ + return true; +} + +static void +log_server_exit(int status_type, int status) +{ + return; +} +#endif /* SUDOERS_LOG_CLIENT */ + +static int +sudoers_audit_accept(const char *plugin_name, unsigned int plugin_type, + char * const command_info[], char * const run_argv[], + char * const run_envp[], const char **errstr) +{ + const struct sudoers_context *ctx = sudoers_get_context(); + const char *uuid_str = NULL; + struct eventlog evlog; + static bool first = true; + int ret = true; + debug_decl(sudoers_audit_accept, SUDOERS_DEBUG_PLUGIN); + + /* Only log the accept event from the sudo front-end */ + if (plugin_type != SUDO_FRONT_END) + debug_return_int(true); + + /* Log sub-commands with the uuid of the original command. */ + if (!ISSET(ctx->mode, MODE_POLICY_INTERCEPTED)) + uuid_str = ctx->uuid_str; + + /* + * We must always call log_allowed() even if def_log_allowed is disabled + * since it will send mail if def_mail_always or def_mail_all_cmnds are + * set (it has its own checks for def_log_allowed). + */ + audit_to_eventlog(ctx, &evlog, command_info, run_argv, run_envp, uuid_str); + if (!log_allowed(ctx, &evlog) && !def_ignore_logfile_errors) + ret = false; + + /* + * Skip auditing and log server logging if "log_allowed" is disabled. + */ + if (!def_log_allowed) + goto done; + + if (audit_success(ctx, run_argv) != 0) { + if (!def_ignore_logfile_errors) + ret = false; + } + + if (!log_server_accept(ctx, &evlog)) { + if (!def_ignore_logfile_errors) + ret = false; + } + + if (first) { + /* log_subcmds doesn't go through sudo_policy_main again to set this. */ + if (def_log_subcmds) { + if (!sudoers_set_mode(MODE_POLICY_INTERCEPTED, UINT_MAX)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "unable to set 0x%x in ctx->mode", MODE_POLICY_INTERCEPTED); + } + } + first = false; + } + +done: + debug_return_int(ret); +} + +static int +sudoers_audit_reject(const char *plugin_name, unsigned int plugin_type, + const char *message, char * const command_info[], const char **errstr) +{ + const struct sudoers_context *ctx = sudoers_get_context(); + struct eventlog evlog; + int ret = true; + debug_decl(sudoers_audit_reject, SUDOERS_DEBUG_PLUGIN); + + /* Skip reject events that sudoers generated itself. */ + if (strncmp(plugin_name, "sudoers_", 8) == 0) + debug_return_int(true); + + if (!def_log_denied) + debug_return_int(true); + + if (audit_failure_int(ctx, ctx->runas.argv, message) != 0) { + if (!def_ignore_audit_errors) + ret = false; + } + + audit_to_eventlog(ctx, &evlog, command_info, ctx->runas.argv, NULL, NULL); + if (!eventlog_reject(&evlog, 0, message, NULL, NULL)) + ret = false; + + if (!log_server_reject(ctx, &evlog, message)) + ret = false; + + debug_return_int(ret); +} + +static int +sudoers_audit_error(const char *plugin_name, unsigned int plugin_type, + const char *message, char * const command_info[], const char **errstr) +{ + const struct sudoers_context *ctx = sudoers_get_context(); + struct eventlog evlog; + struct timespec now; + int ret = true; + debug_decl(sudoers_audit_error, SUDOERS_DEBUG_PLUGIN); + + /* Skip error events that sudoers generated itself. */ + if (strncmp(plugin_name, "sudoers_", 8) == 0) + debug_return_int(true); + + if (audit_failure_int(ctx, ctx->runas.argv, message) != 0) { + if (!def_ignore_audit_errors) + ret = false; + } + + if (sudo_gettime_real(&now)) { + sudo_warn("%s", U_("unable to get time of day")); + debug_return_bool(false); + } + + audit_to_eventlog(ctx, &evlog, command_info, ctx->runas.argv, NULL, NULL); + if (!eventlog_alert(&evlog, 0, &now, message, NULL)) + ret = false; + + if (!log_server_alert(ctx, &evlog, &now, message, NULL)) + ret = false; + + debug_return_int(ret); +} + +static void +sudoers_audit_close(int status_type, int status) +{ + log_server_exit(status_type, status); +} + +static int +sudoers_audit_version(int verbose) +{ + debug_decl(sudoers_audit_version, SUDOERS_DEBUG_PLUGIN); + + sudo_printf(SUDO_CONV_INFO_MSG, "Sudoers audit plugin version %s\n", + PACKAGE_VERSION); + + debug_return_int(true); +} + +sudo_dso_public struct audit_plugin sudoers_audit = { + SUDO_AUDIT_PLUGIN, + SUDO_API_VERSION, + sudoers_audit_open, + sudoers_audit_close, + sudoers_audit_accept, + sudoers_audit_reject, + sudoers_audit_error, + sudoers_audit_version, + NULL, /* register_hooks */ + NULL, /* deregister_hooks */ + NULL /* event_alloc() filled in by sudo */ +}; |