diff --git a/include/sudo_iolog.h b/include/sudo_iolog.h index eced01fbac..f7bb282b7f 100644 --- a/include/sudo_iolog.h +++ b/include/sudo_iolog.h @@ -155,6 +155,6 @@ void *iolog_pwfilt_alloc(void); bool iolog_pwfilt_add(void *handle, const char *pattern); void iolog_pwfilt_free(void *handle); bool iolog_pwfilt_remove(void *handle, const char *pattern); -bool iolog_pwfilt_run(void *handle, int event, const char *buf, size_t len, char **newbuf); +bool iolog_pwfilt_run(void *handle, bool *is_filtered, int event, const char *buf, size_t len, char **newbuf); #endif /* SUDO_IOLOG_H */ diff --git a/lib/iolog/iolog_filter.c b/lib/iolog/iolog_filter.c index 05f30e7edf..deab4a54c1 100644 --- a/lib/iolog/iolog_filter.c +++ b/lib/iolog/iolog_filter.c @@ -47,7 +47,6 @@ TAILQ_HEAD(pwfilt_regex_list, pwfilt_regex); struct pwfilt_handle { struct pwfilt_regex_list filters; - bool is_filtered; }; /* @@ -62,7 +61,6 @@ iolog_pwfilt_alloc(void) handle = malloc(sizeof(*handle)); if (handle != NULL) { TAILQ_INIT(&handle->filters); - handle->is_filtered = false; } debug_return_ptr(handle); @@ -169,9 +167,11 @@ iolog_pwfilt_remove(void *vhandle, const char *pattern) * If logging output and filtering _is_ enabled, disable filtering. * If logging input and filtering is enabled, replace all characters in * buf with stars ('*') up to the next linefeed or carriage return. + * The filtering state is stored in is_filtered, not in the handle, since + * a handle may be shared by multiple sessions (as in sudo_logsrvd). */ bool -iolog_pwfilt_run(void *vhandle, int event, const char *buf, +iolog_pwfilt_run(void *vhandle, bool *is_filtered, int event, const char *buf, size_t len, char **newbuf) { struct pwfilt_handle *handle = vhandle; @@ -188,8 +188,8 @@ iolog_pwfilt_run(void *vhandle, int event, const char *buf, switch (event) { case IO_EVENT_TTYOUT: /* If filtering passwords and we receive output, disable it. */ - if (handle->is_filtered) - handle->is_filtered = false; + if (*is_filtered) + *is_filtered = false; /* Make a copy of buf that is NUL-terminated. */ copy = malloc(len + 1); @@ -203,20 +203,20 @@ iolog_pwfilt_run(void *vhandle, int event, const char *buf, /* Check output for a password prompt. */ TAILQ_FOREACH(filt, &handle->filters, entries) { if (regexec(&filt->regex, copy, 0, NULL, 0) == 0) { - handle->is_filtered = true; + *is_filtered = true; break; } } free(copy); break; case IO_EVENT_TTYIN: - if (handle->is_filtered) { + if (*is_filtered) { size_t i; for (i = 0; i < len; i++) { /* We will stop filtering after reaching cr/nl. */ if (buf[i] == '\r' || buf[i] == '\n') { - handle->is_filtered = false; + *is_filtered = false; break; } } diff --git a/lib/iolog/regress/iolog_filter/check_iolog_filter.c b/lib/iolog/regress/iolog_filter/check_iolog_filter.c index e6e12bd543..b295273d41 100644 --- a/lib/iolog/regress/iolog_filter/check_iolog_filter.c +++ b/lib/iolog/regress/iolog_filter/check_iolog_filter.c @@ -34,6 +34,49 @@ sudo_dso_public int main(int argc, char *argv[]); +/* + * Two sessions that share a filter handle (as in sudo_logsrvd) must + * not affect each other's filter state. + */ +static int +test_shared_handle(void *handle) +{ + bool filtered[2] = { false, false }; + char *newbuf = NULL; + int errors = 0; + + /* Password prompt in the first session. */ + if (!iolog_pwfilt_run(handle, &filtered[0], IO_EVENT_TTYOUT, + "Password: ", 10, &newbuf)) + return 1; + + /* Output and input in the second session, input must not be filtered. */ + if (!iolog_pwfilt_run(handle, &filtered[1], IO_EVENT_TTYOUT, + "total 0\r\n", 9, &newbuf)) + return 1; + if (!iolog_pwfilt_run(handle, &filtered[1], IO_EVENT_TTYIN, + "ls\r", 3, &newbuf)) + return 1; + if (newbuf != NULL) { + sudo_warnx("shared handle: input filtered without a password prompt"); + errors++; + free(newbuf); + newbuf = NULL; + } + + /* The password in the first session must still be filtered. */ + if (!iolog_pwfilt_run(handle, &filtered[0], IO_EVENT_TTYIN, + "secret\r", 7, &newbuf)) + return 1; + if (newbuf == NULL || memcmp(newbuf, "******\r", 7) != 0) { + sudo_warnx("shared handle: password not filtered"); + errors++; + } + free(newbuf); + + return errors; +} + int main(int argc, char *argv[]) { @@ -67,6 +110,7 @@ main(int argc, char *argv[]) struct timing_closure timing; const char *logdir = argv[i]; char tbuf[8192], fbuf[8192]; + bool is_filtered = false; ssize_t nread; ntests++; @@ -147,8 +191,8 @@ main(int argc, char *argv[]) } /* Apply filter. */ - if (!iolog_pwfilt_run(passprompt_regex, timing.event, tbuf, - timing.u.nbytes, &newbuf)) { + if (!iolog_pwfilt_run(passprompt_regex, &is_filtered, timing.event, + tbuf, timing.u.nbytes, &newbuf)) { errors++; continue; } @@ -191,6 +235,11 @@ main(int argc, char *argv[]) if (iolog_timing.enabled) iolog_close(&iolog_timing, NULL); } + + ntests++; + if (test_shared_handle(passprompt_regex) != 0) + errors++; + iolog_pwfilt_free(passprompt_regex); if (ntests != 0) { diff --git a/logsrvd/logsrvd.h b/logsrvd/logsrvd.h index 55dc421a8c..f446a8eb8e 100644 --- a/logsrvd/logsrvd.h +++ b/logsrvd/logsrvd.h @@ -114,6 +114,7 @@ struct connection_closure { bool error; bool tls; bool log_io; + bool pwfilt_active; bool store_first; bool read_instead_of_write; bool write_instead_of_read; diff --git a/logsrvd/logsrvd_local.c b/logsrvd/logsrvd_local.c index e946ad76dd..98a19fec77 100644 --- a/logsrvd/logsrvd_local.c +++ b/logsrvd/logsrvd_local.c @@ -662,8 +662,9 @@ store_iobuf_local(int iofd, const IoBuffer *iobuf, const uint8_t *buf, } if (!logsrvd_conf_iolog_log_passwords()) { - if (!iolog_pwfilt_run(logsrvd_conf_iolog_passprompt_regex(), iofd, - (char *)data.data, data.len, &newbuf)) + if (!iolog_pwfilt_run(logsrvd_conf_iolog_passprompt_regex(), + &closure->pwfilt_active, iofd, (char *)data.data, data.len, + &newbuf)) goto bad; if (newbuf != NULL) data.data = (uint8_t *)newbuf; diff --git a/plugins/sudoers/iolog.c b/plugins/sudoers/iolog.c index fa2a23bafd..66af62031e 100644 --- a/plugins/sudoers/iolog.c +++ b/plugins/sudoers/iolog.c @@ -68,6 +68,7 @@ static bool log_passwords = false; static int iolog_dir_fd = -1; static struct timespec last_time; static void *passprompt_regex_handle; +static bool pwfilt_active = false; static void sudoers_io_setops(void); /* sudoers_io is declared at the end of this file. */ @@ -934,6 +935,7 @@ sudoers_io_close(int exit_status, int error) sudo_freegrcache(); iolog_pwfilt_free(passprompt_regex_handle); passprompt_regex_handle = NULL; + pwfilt_active = false; /* sudoers_debug_deregister() calls sudo_debug_exit() for us. */ sudoers_debug_deregister(); @@ -979,7 +981,8 @@ sudoers_io_log_local(int event, const char *buf, unsigned int len, } if (!log_passwords && passprompt_regex_handle != NULL) { - if (!iolog_pwfilt_run(passprompt_regex_handle, event, buf, len, &newbuf)) + if (!iolog_pwfilt_run(passprompt_regex_handle, &pwfilt_active, event, + buf, len, &newbuf)) debug_return_int(-1); }