diff --git a/docs/sudoers.man.in b/docs/sudoers.man.in index 67ca7cec6..17148c109 100644 --- a/docs/sudoers.man.in +++ b/docs/sudoers.man.in @@ -25,7 +25,7 @@ .nr BA @BAMAN@ .nr LC @LCMAN@ .nr PS @PSMAN@ -.TH "SUDOERS" "@mansectform@" "January 20, 2022" "Sudo @PACKAGE_VERSION@" "File Formats Manual" +.TH "SUDOERS" "@mansectform@" "January 27, 2022" "Sudo @PACKAGE_VERSION@" "File Formats Manual" .nh .if n .ad l .SH "NAME" @@ -2660,6 +2660,43 @@ This flag is \fIoff\fR by default. .TP 18n +log_passwords +Most programs that require a user's password will disable echo before +reading the password to avoid displaying the plaintext password on +the screen. +However, if terminal input is being logged (see +\fIlog_input\fR), +the password will still be present in the I/O log. +If the +\fIlog_passwords\fR +option is disabled, +\fBsudoers\fR +will attempt to prevent passwords from being logged. +It does this by using the regular expressions in +\fIpassprompt_regex\fR +to match a password prompt in the terminal output buffer. +When a match is found, input characters in the I/O log will be replaced with +\(oq*\(cq +until either a line feed or carriage return is found in the terminal input +or a new terminal output buffer is received. +If, however, a program displays characters as the user types +(such as +\fBsudo\fR +when +\fIpwfeedback\fR +is set), only the +first character of the password will be replaced in the I/O log. +This option has no effect unless +\fIlog_input\fR +and +\fIlog_input\fR +are also set. +This flag is +\fIon\fR +by default. +.sp +This setting is only supported by version 1.9.10 or higher. +.TP 18n fqdn Set this flag if you want to put fully qualified host names in the \fIsudoers\fR @@ -5261,6 +5298,17 @@ flag (I/O logging enabled) or the flag (I/O logging disabled) is set. .sp This setting is only supported by version 1.9.0 or higher. +.TP 18n +passprompt_regex +A list of POSIX extended regular expressions used to +match password prompts in the terminal output. +This option is only used when +\fIlog_passwords\fR +has been disabled. +The default value is +\(lq[Pp]assword[: ]*\(rq +.sp +This setting is only supported by version 1.9.10 or higher. .SH "GROUP PROVIDER PLUGINS" The \fBsudoers\fR diff --git a/docs/sudoers.mdoc.in b/docs/sudoers.mdoc.in index 1b9ea07cf..6e9390177 100644 --- a/docs/sudoers.mdoc.in +++ b/docs/sudoers.mdoc.in @@ -24,7 +24,7 @@ .nr BA @BAMAN@ .nr LC @LCMAN@ .nr PS @PSMAN@ -.Dd January 20, 2022 +.Dd January 27, 2022 .Dt SUDOERS @mansectform@ .Os Sudo @PACKAGE_VERSION@ .Sh NAME @@ -2507,6 +2507,42 @@ characters. This flag is .Em off by default. +.It log_passwords +Most programs that require a user's password will disable echo before +reading the password to avoid displaying the plaintext password on +the screen. +However, if terminal input is being logged (see +.Em log_input ) , +the password will still be present in the I/O log. +If the +.Em log_passwords +option is disabled, +.Nm +will attempt to prevent passwords from being logged. +It does this by using the regular expressions in +.Em passprompt_regex +to match a password prompt in the terminal output buffer. +When a match is found, input characters in the I/O log will be replaced with +.Ql * +until either a line feed or carriage return is found in the terminal input +or a new terminal output buffer is received. +If, however, a program displays characters as the user types +(such as +.Nm sudo +when +.Em pwfeedback +is set), only the +first character of the password will be replaced in the I/O log. +This option has no effect unless +.Em log_input +and +.Em log_input +are also set. +This flag is +.Em on +by default. +.Pp +This setting is only supported by version 1.9.10 or higher. .It fqdn Set this flag if you want to put fully qualified host names in the .Em sudoers @@ -4908,6 +4944,16 @@ flag (I/O logging enabled) or the flag (I/O logging disabled) is set. .Pp This setting is only supported by version 1.9.0 or higher. +.It passprompt_regex +A list of POSIX extended regular expressions used to +match password prompts in the terminal output. +This option is only used when +.Em log_passwords +has been disabled. +The default value is +.Dq [Pp]assword[: ]* +.Pp +This setting is only supported by version 1.9.10 or higher. .El .Sh GROUP PROVIDER PLUGINS The diff --git a/plugins/sudoers/Makefile.in b/plugins/sudoers/Makefile.in index f40193560..e17fb13cf 100644 --- a/plugins/sudoers/Makefile.in +++ b/plugins/sudoers/Makefile.in @@ -1,7 +1,7 @@ # # SPDX-License-Identifier: ISC # -# Copyright (c) 1996, 1998-2005, 2007-2021 +# Copyright (c) 1996, 1998-2005, 2007-2022 # Todd C. Miller # # Permission to use, copy, modify, and distribute this software for any @@ -1504,22 +1504,22 @@ defaults.lo: $(srcdir)/defaults.c $(devdir)/def_data.c $(devdir)/def_data.h \ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \ $(incdir)/sudo_debug.h $(incdir)/sudo_eventlog.h \ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ - $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \ - $(incdir)/sudo_util.h $(srcdir)/defaults.h $(srcdir)/logging.h \ - $(srcdir)/parse.h $(srcdir)/sudo_nss.h $(srcdir)/sudoers.h \ - $(srcdir)/sudoers_debug.h $(top_builddir)/config.h \ - $(top_builddir)/pathnames.h + $(incdir)/sudo_iolog.h $(incdir)/sudo_plugin.h \ + $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(srcdir)/defaults.h \ + $(srcdir)/logging.h $(srcdir)/parse.h $(srcdir)/sudo_nss.h \ + $(srcdir)/sudoers.h $(srcdir)/sudoers_debug.h \ + $(top_builddir)/config.h $(top_builddir)/pathnames.h $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/defaults.c defaults.i: $(srcdir)/defaults.c $(devdir)/def_data.c $(devdir)/def_data.h \ $(devdir)/gram.h $(incdir)/compat/stdbool.h \ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \ $(incdir)/sudo_debug.h $(incdir)/sudo_eventlog.h \ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ - $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \ - $(incdir)/sudo_util.h $(srcdir)/defaults.h $(srcdir)/logging.h \ - $(srcdir)/parse.h $(srcdir)/sudo_nss.h $(srcdir)/sudoers.h \ - $(srcdir)/sudoers_debug.h $(top_builddir)/config.h \ - $(top_builddir)/pathnames.h + $(incdir)/sudo_iolog.h $(incdir)/sudo_plugin.h \ + $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(srcdir)/defaults.h \ + $(srcdir)/logging.h $(srcdir)/parse.h $(srcdir)/sudo_nss.h \ + $(srcdir)/sudoers.h $(srcdir)/sudoers_debug.h \ + $(top_builddir)/config.h $(top_builddir)/pathnames.h $(CC) -E -o $@ $(CPPFLAGS) $< defaults.plog: defaults.i rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/defaults.c --i-file $< --output-file $@ diff --git a/plugins/sudoers/def_data.c b/plugins/sudoers/def_data.c index 0afddace8..c3f023f3e 100644 --- a/plugins/sudoers/def_data.c +++ b/plugins/sudoers/def_data.c @@ -645,6 +645,14 @@ struct sudo_defs_types sudo_defs_table[] = { "rlimit_stack", T_RLIMIT|T_BOOL, N_("The maximum size to which the process's stack may grow (in bytes): %s"), NULL, + }, { + "log_passwords", T_FLAG, + N_("Store plaintext passwords in I/O log input"), + NULL, + }, { + "passprompt_regex", T_LIST|T_BOOL, + N_("List of regular expressions to use when matching a password prompt"), + NULL, }, { NULL, 0, NULL } diff --git a/plugins/sudoers/def_data.h b/plugins/sudoers/def_data.h index 25bf3a71d..850f71cf1 100644 --- a/plugins/sudoers/def_data.h +++ b/plugins/sudoers/def_data.h @@ -300,6 +300,10 @@ #define def_rlimit_rss (sudo_defs_table[I_RLIMIT_RSS].sd_un.str) #define I_RLIMIT_STACK 149 #define def_rlimit_stack (sudo_defs_table[I_RLIMIT_STACK].sd_un.str) +#define I_LOG_PASSWORDS 150 +#define def_log_passwords (sudo_defs_table[I_LOG_PASSWORDS].sd_un.flag) +#define I_PASSPROMPT_REGEX 151 +#define def_passprompt_regex (sudo_defs_table[I_PASSPROMPT_REGEX].sd_un.list) enum def_tuple { never, diff --git a/plugins/sudoers/def_data.in b/plugins/sudoers/def_data.in index 8309779f7..e0e5c93c8 100644 --- a/plugins/sudoers/def_data.in +++ b/plugins/sudoers/def_data.in @@ -466,3 +466,9 @@ rlimit_rss rlimit_stack T_RLIMIT|T_BOOL "The maximum size to which the process's stack may grow (in bytes): %s" +log_passwords + T_FLAG + "Store plaintext passwords in I/O log input" +passprompt_regex + T_LIST|T_BOOL + "List of regular expressions to use when matching a password prompt" diff --git a/plugins/sudoers/defaults.c b/plugins/sudoers/defaults.c index f36ca205d..4a2d1346f 100644 --- a/plugins/sudoers/defaults.c +++ b/plugins/sudoers/defaults.c @@ -36,10 +36,12 @@ #include #include #include +#include #include #include "sudoers.h" #include "sudo_eventlog.h" +#include "sudo_iolog.h" #include static struct early_default early_defaults[] = { @@ -454,6 +456,24 @@ free_defs_val(int type, union sudo_defs_val *sd_un) memset(sd_un, 0, sizeof(*sd_un)); } +static bool +init_passprompt_regex(void) +{ + struct list_member *lm; + debug_decl(init_passprompt_regex, SUDOERS_DEBUG_DEFAULTS); + + /* Add initial defaults setting. */ + lm = calloc(1, sizeof(struct list_member)); + if (lm == NULL || (lm->value = strdup(PASSPROMPT_REGEX)) == NULL) { + free(lm); + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + debug_return_bool(false); + } + SLIST_INSERT_HEAD(&def_passprompt_regex, lm, entries); + + debug_return_bool(true); +} + /* * Set default options to compiled-in values. * Any of these may be overridden at runtime by a "Defaults" file. @@ -596,6 +616,7 @@ init_defaults(void) #ifdef HAVE_ZLIB_H def_compress_io = true; #endif + def_log_passwords = true; def_log_server_timeout = 30; def_log_server_verify = true; def_log_server_keepalive = true; @@ -658,6 +679,10 @@ init_defaults(void) /* Init eventlog config. */ init_eventlog_config(); + /* Initial iolog password prompt regex. */ + if (!init_passprompt_regex()) + debug_return_bool(false); + firsttime = false; debug_return_bool(true); @@ -1252,3 +1277,29 @@ oom: } debug_return_bool(false); } + +bool +cb_passprompt_regex(const union sudo_defs_val *sd_un, int op) +{ + struct list_member *lm; + int errcode; + char errbuf[1024]; + regex_t re; + debug_decl(cb_passprompt_regex, SUDOERS_DEBUG_DEFAULTS); + + /* If adding one or more regexps, validate them with regcomp(). */ + if (op == '+' || op == true) { + SLIST_FOREACH(lm, &sd_un->list, entries) { + errcode = regcomp(&re, lm->value, REG_EXTENDED|REG_NOSUB); + if (errcode != 0) { + regerror(errcode, &re, errbuf, sizeof(errbuf)); + sudo_warnx(U_("invalid regular expression \"%s\": %s"), + lm->value, errbuf); + debug_return_bool(false); + } + regfree(&re); + } + } + + debug_return_bool(true); +} diff --git a/plugins/sudoers/defaults.h b/plugins/sudoers/defaults.h index b78a9ae9b..ce85b7c99 100644 --- a/plugins/sudoers/defaults.h +++ b/plugins/sudoers/defaults.h @@ -137,6 +137,7 @@ bool set_default(const char *var, const char *val, int op, const char *file, int bool update_defaults(struct sudoers_parse_tree *parse_tree, struct defaults_list *defs, int what, bool quiet); bool check_defaults(struct sudoers_parse_tree *parse_tree, bool quiet); bool append_default(const char *var, const char *val, int op, char *source, struct defaults_list *defs); +bool cb_passprompt_regex(const union sudo_defs_val *sd_un, int op); extern struct sudo_defs_types sudo_defs_table[]; diff --git a/plugins/sudoers/iolog.c b/plugins/sudoers/iolog.c index 6198ce9bc..eac5dbaef 100644 --- a/plugins/sudoers/iolog.c +++ b/plugins/sudoers/iolog.c @@ -69,8 +69,10 @@ static struct sudoers_io_operations { static struct log_details iolog_details; static bool warned = false; +static bool log_passwords = true; static int iolog_dir_fd = -1; static struct timespec last_time; +static void *passprompt_regex_handle; static void sudoers_io_setops(void); /* sudoers_io is declared at the end of this file. */ @@ -209,6 +211,7 @@ free_iolog_details(void) /* * Convert a comma-separated list to a string list. + * XXX - handle escaped commas */ static struct sudoers_str_list * deserialize_stringlist(const char *s) @@ -244,6 +247,39 @@ bad: debug_return_ptr(NULL); } +/* + * Set passprompt regex filter based on a comma-separated string. + * Returns a passprompt regex handle pointer. + */ +static void * +set_passprompt_regex(const char *cstr) +{ + void *handle; + char *cp, *last, *str; + debug_decl(set_passprompt_regex, SUDOERS_DEBUG_UTIL); + + handle = iolog_pwfilt_alloc(); + str = strdup(cstr); + if (handle == NULL || str == NULL) { + sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory")); + goto bad; + } + + /* XXX - handle escaped commas */ + for ((cp = strtok_r(str, ",", &last)); cp != NULL; + (cp = strtok_r(NULL, ",", &last))) { + if (!iolog_pwfilt_add(handle, cp)) + goto bad; + } + + free(str); + debug_return_ptr(handle); +bad: + free(str); + iolog_pwfilt_free(handle); + debug_return_ptr(NULL); +} + /* * Pull out I/O log related data from user_info and command_info arrays. * Returns true if I/O logging is enabled, false if not and -1 on error. @@ -441,6 +477,16 @@ iolog_deserialize_info(struct log_details *details, char * const user_info[], } break; case 'l': + if (strncmp(*cur, "log_passwords=", sizeof("log_passwords=") - 1) == 0) { + int val = sudo_strtobool(*cur + sizeof("log_passwords=") - 1); + if (val != -1) { + log_passwords = val; + } else { + sudo_debug_printf(SUDO_DEBUG_WARN, + "%s: unable to parse %s", __func__, *cur); + } + continue; + } if (strncmp(*cur, "log_servers=", sizeof("log_servers=") - 1) == 0) { details->log_servers = deserialize_stringlist(*cur + sizeof("log_servers=") - 1); @@ -506,6 +552,14 @@ iolog_deserialize_info(struct log_details *details, char * const user_info[], continue; } break; + case 'p': + if (strncmp(*cur, "passprompt_regex=", sizeof("passprompt_regex=") - 1) == 0) { + passprompt_regex_handle = + set_passprompt_regex(*cur + sizeof("passprompt_regex=") - 1); + if (passprompt_regex_handle == NULL) + debug_return_int(-1); + } + break; case 'r': if (strncmp(*cur, "runas_gid=", sizeof("runas_gid=") - 1) == 0) { runas_gid_str = *cur + sizeof("runas_gid=") - 1; @@ -852,6 +906,8 @@ sudoers_io_close(int exit_status, int error) free_iolog_details(); sudo_freepwcache(); sudo_freegrcache(); + iolog_pwfilt_free(passprompt_regex_handle); + passprompt_regex_handle = NULL; /* sudoers_debug_deregister() calls sudo_debug_exit() for us. */ sudoers_debug_deregister(); @@ -879,6 +935,7 @@ sudoers_io_log_local(int event, const char *buf, unsigned int len, { struct iolog_file *iol; char tbuf[1024]; + char *newbuf = NULL; int ret = -1; debug_decl(sudoers_io_log_local, SUDOERS_DEBUG_PLUGIN); @@ -895,8 +952,13 @@ sudoers_io_log_local(int event, const char *buf, unsigned int len, debug_return_int(-1); } + if (!log_passwords && passprompt_regex_handle != NULL) { + if (!iolog_pwfilt_run(passprompt_regex_handle, event, buf, len, &newbuf)) + debug_return_int(-1); + } + /* Write I/O log file entry. */ - if (iolog_write(iol, buf, len, errstr) == -1) + if (iolog_write(iol, newbuf ? newbuf : buf, len, errstr) == -1) goto done; /* Write timing file entry. */ @@ -914,6 +976,7 @@ sudoers_io_log_local(int event, const char *buf, unsigned int len, ret = 1; done: + free(newbuf); debug_return_int(ret); } diff --git a/plugins/sudoers/policy.c b/plugins/sudoers/policy.c index 14d1650e7..c75e818c5 100644 --- a/plugins/sudoers/policy.c +++ b/plugins/sudoers/policy.c @@ -1,7 +1,7 @@ /* * SPDX-License-Identifier: ISC * - * Copyright (c) 2010-2021 Todd C. Miller + * Copyright (c) 2010-2022 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -575,6 +575,7 @@ bad: /* * Convert struct list_members to a comma-separated string with * the given variable name. + * XXX - escape commas in member values */ static char * serialize_list(const char *varname, struct list_members *members) @@ -638,7 +639,7 @@ sudoers_policy_store_result(bool accepted, char *argv[], char *envp[], } /* Increase the length of command_info as needed, it is *not* checked. */ - command_info = calloc(68, sizeof(char *)); + command_info = calloc(70, sizeof(char *)); if (command_info == NULL) goto oom; @@ -676,6 +677,16 @@ sudoers_policy_store_result(bool accepted, char *argv[], char *envp[], if ((command_info[info_len++] = strdup("iolog_flush=true")) == NULL) goto oom; } + if ((command_info[info_len++] = sudo_new_key_val("log_passwords", + def_log_passwords ? "true" : "false")) == NULL) + goto oom; + if (!SLIST_EMPTY(&def_passprompt_regex)) { + char *passprompt_regex = + serialize_list("passprompt_regex", &def_passprompt_regex); + if (passprompt_regex == NULL) + goto oom; + command_info[info_len++] = passprompt_regex; + } if (def_maxseq != NULL) { if ((command_info[info_len++] = sudo_new_key_val("maxseq", def_maxseq)) == NULL) goto oom; diff --git a/plugins/sudoers/sudoers.c b/plugins/sudoers/sudoers.c index e0e50d796..dd0708abd 100644 --- a/plugins/sudoers/sudoers.c +++ b/plugins/sudoers/sudoers.c @@ -1658,6 +1658,7 @@ set_callbacks(void) sudo_defs_table[I_MAILFROM].callback = cb_mailfrom; sudo_defs_table[I_MAILTO].callback = cb_mailto; sudo_defs_table[I_MAILSUB].callback = cb_mailsub; + sudo_defs_table[I_PASSPROMPT_REGEX].callback = cb_passprompt_regex; debug_return; }