2
0
mirror of https://github.com/sudo-project/sudo.git synced 2025-08-22 01:49:11 +00:00

lib/iolog: add support for filtering password out of tty input

If a password regex is found in the tty output, tty input will be
replaced with '*' chars until a newline or another tty output
character is received.
This commit is contained in:
Todd C. Miller 2022-01-28 08:52:41 -07:00
parent 86f123cd9d
commit 946404434e
4 changed files with 284 additions and 8 deletions

View File

@ -129,6 +129,7 @@ lib/iolog/iolog_clearerr.c
lib/iolog/iolog_close.c
lib/iolog/iolog_conf.c
lib/iolog/iolog_eof.c
lib/iolog/iolog_filter.c
lib/iolog/iolog_flush.c
lib/iolog/iolog_gets.c
lib/iolog/iolog_json.c

View File

@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2009-2020 Todd C. Miller <Todd.Miller@sudo.ws>
* Copyright (c) 2009-2022 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
@ -56,6 +56,11 @@
#define IOFD_TIMING 5
#define IOFD_MAX 6
/*
* Default password prompt regex.
*/
#define PASSPROMPT_REGEX "[Pp]assword[: ]*"
struct timing_closure {
struct timespec delay;
const char *decimal;
@ -140,4 +145,11 @@ void iolog_set_owner(uid_t uid, uid_t gid);
bool iolog_swapids(bool restore);
bool iolog_mkdirs(char *path);
/* iolog_filter.c */
void *iolog_pwfilt_alloc();
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, unsigned int len, char **newbuf);
#endif /* SUDO_IOLOG_H */

View File

@ -1,7 +1,7 @@
#
# SPDX-License-Identifier: ISC
#
# Copyright (c) 2011-2021 Todd C. Miller <Todd.Miller@sudo.ws>
# Copyright (c) 2011-2022 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
@ -100,12 +100,12 @@ DEVEL = @DEVEL@
SHELL = @SHELL@
LIBIOLOG_OBJS = host_port.lo hostcheck.lo iolog_clearerr.lo iolog_close.lo \
iolog_conf.lo iolog_eof.lo iolog_flush.lo iolog_gets.lo \
iolog_json.lo iolog_legacy.lo iolog_loginfo.lo iolog_mkdirs.lo \
iolog_mkdtemp.lo iolog_mkpath.lo iolog_nextid.lo \
iolog_open.lo iolog_openat.lo iolog_path.lo iolog_read.lo \
iolog_seek.lo iolog_swapids.lo iolog_timing.lo iolog_util.lo \
iolog_write.lo
iolog_conf.lo iolog_eof.lo iolog_filter.lo iolog_flush.lo \
iolog_gets.lo iolog_json.lo iolog_legacy.lo iolog_loginfo.lo \
iolog_mkdirs.lo iolog_mkdtemp.lo iolog_mkpath.lo \
iolog_nextid.lo iolog_open.lo iolog_openat.lo iolog_path.lo \
iolog_read.lo iolog_seek.lo iolog_swapids.lo iolog_timing.lo \
iolog_util.lo iolog_write.lo
IOBJS = $(LIBIOLOG_OBJS:.lo=.i)
@ -553,6 +553,20 @@ iolog_eof.i: $(srcdir)/iolog_eof.c $(incdir)/compat/stdbool.h \
$(CC) -E -o $@ $(CPPFLAGS) $<
iolog_eof.plog: iolog_eof.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/iolog_eof.c --i-file $< --output-file $@
iolog_filter.lo: $(srcdir)/iolog_filter.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
$(incdir)/sudo_iolog.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(top_builddir)/config.h
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/iolog_filter.c
iolog_filter.i: $(srcdir)/iolog_filter.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
$(incdir)/sudo_iolog.h $(incdir)/sudo_plugin.h \
$(incdir)/sudo_queue.h $(top_builddir)/config.h
$(CC) -E -o $@ $(CPPFLAGS) $<
iolog_filter.plog: iolog_filter.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/iolog_filter.c --i-file $< --output-file $@
iolog_flush.lo: $(srcdir)/iolog_flush.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_iolog.h $(incdir)/sudo_queue.h \

249
lib/iolog/iolog_filter.c Normal file
View File

@ -0,0 +1,249 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2022 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/types.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_STDBOOL_H
# include <stdbool.h>
#else
# include "compat/stdbool.h"
#endif
#include <regex.h>
#include <string.h>
#include <time.h>
#include "sudo_compat.h"
#include "sudo_debug.h"
#include "sudo_fatal.h"
#include "sudo_gettext.h"
#include "sudo_iolog.h"
#include "sudo_queue.h"
struct pwfilt_regex {
TAILQ_ENTRY(pwfilt_regex) entries;
char *pattern;
regex_t regex;
};
TAILQ_HEAD(pwfilt_regex_list, pwfilt_regex);
struct pwfilt_handle {
struct pwfilt_regex_list filters;
bool is_filtered;
};
/*
* Allocate a new filter handle.
*/
void *
iolog_pwfilt_alloc(void)
{
struct pwfilt_handle *handle;
debug_decl(iolog_pwfilt_alloc, SUDO_DEBUG_UTIL);
handle = malloc(sizeof(*handle));
if (handle != NULL) {
TAILQ_INIT(&handle->filters);
handle->is_filtered = false;
}
debug_return_ptr(handle);
}
/*
* Unlink filt from filters and free it.
*/
static void
iolog_pwfilt_free_filter(struct pwfilt_regex_list *filters,
struct pwfilt_regex *filt)
{
debug_decl(iolog_pwfilt_free_filter, SUDO_DEBUG_UTIL);
TAILQ_REMOVE(filters, filt, entries);
regfree(&filt->regex);
free(filt->pattern);
free(filt);
debug_return;
}
/*
* Free the given password filter handle.
*/
void
iolog_pwfilt_free(void *vhandle)
{
struct pwfilt_handle *handle = vhandle;
struct pwfilt_regex *filt;
debug_decl(iolog_pwfilt_free, SUDO_DEBUG_UTIL);
if (handle != NULL) {
while ((filt = TAILQ_FIRST(&handle->filters)) != NULL) {
iolog_pwfilt_free_filter(&handle->filters, filt);
}
free(handle);
}
debug_return;
}
/*
* Add a pattern to the password filter list.
*/
bool
iolog_pwfilt_add(void *vhandle, const char *pattern)
{
struct pwfilt_handle *handle = vhandle;
struct pwfilt_regex *filt;
char errbuf[1024];
int errcode;
debug_decl(iolog_pwfilt_add, SUDO_DEBUG_UTIL);
filt = malloc(sizeof(*filt));
if (filt == NULL)
goto oom;
filt->pattern = strdup(pattern);
if (filt->pattern == NULL)
goto oom;
errcode = regcomp(&filt->regex, pattern, REG_EXTENDED|REG_NOSUB);
if (errcode != 0) {
regerror(errcode, &filt->regex, errbuf, sizeof(errbuf));
sudo_warnx(U_("invalid regular expression \"%s\": %s"),
pattern, errbuf);
goto bad;
}
TAILQ_INSERT_TAIL(&handle->filters, filt, entries);
debug_return_bool(true);
oom:
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
bad:
if (filt != NULL) {
free(filt->pattern);
free(filt);
}
debug_return_bool(false);
}
/*
* Remove a pattern from the password filter list.
*/
bool
iolog_pwfilt_remove(void *vhandle, const char *pattern)
{
struct pwfilt_handle *handle = vhandle;
struct pwfilt_regex *filt, *next;
bool ret = false;
debug_decl(iolog_pwfilt_remove, SUDO_DEBUG_UTIL);
TAILQ_FOREACH_SAFE(filt, &handle->filters, entries, next) {
if (strcmp(filt->pattern, pattern) == 0) {
iolog_pwfilt_free_filter(&handle->filters, filt);
ret = true;
}
}
debug_return_bool(ret);
}
/*
* If logging output and filtering is _not_ enabled, match buf against the
* password filter list patterns and, if there is a match, enable filtering.
* 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.
*/
bool
iolog_pwfilt_run(void *vhandle, int event, const char *buf,
unsigned int len, char **newbuf)
{
struct pwfilt_handle *handle = vhandle;
struct pwfilt_regex *filt;
char *copy;
debug_decl(iolog_pwfilt_run, SUDO_DEBUG_UTIL);
/*
* We only filter ttyin/ttyout. It is only possible to disable
* echo when a tty is present. Filtering passwords in the input
* log when they appear in the output is pointless. This does
* assume that the password prompt is written to the tty as well.
*/
switch (event) {
case IO_EVENT_TTYOUT:
/* If filtering passwords and we receive output, disable it. */
if (handle->is_filtered) {
handle->is_filtered = false;
break;
}
/* Make a copy of buf that is NUL-terminated. */
copy = malloc(len + 1);
if (copy == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
debug_return_ptr(false);
}
memcpy(copy, buf, len);
copy[len] = '\0';
/* 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;
break;
}
}
free(copy);
break;
case IO_EVENT_TTYIN:
if (handle->is_filtered) {
unsigned int i;
for (i = 0; i < len; i++) {
/* We will stop filtering after reaching cr/nl. */
if (buf[i] == '\r' || buf[i] == '\n')
break;
}
if (i != 0) {
/* Filtered, replace buffer with '*' chars. */
copy = malloc(len);
if (copy == NULL) {
sudo_warnx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
debug_return_ptr(NULL);
}
memset(copy, '*', i);
if (i != len) {
/* Done filtering, copy cr/nl and subsequent characters. */
memcpy(copy + i, buf + i, len - i);
handle->is_filtered = false;
}
*newbuf = copy;
}
}
break;
}
debug_return_bool(true);
}