From e318f27fba94304567bdcca884ac48bb76a359ed Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Wed, 29 Aug 2018 09:57:12 -0600 Subject: [PATCH] When parsing an I/O log timing line, store the result in a timespec, not a double. The speed factor (for scaling the delay) in sudoreplay is still a double but we only need to adjust the delay if the factor is something other than 1.0. --- MANIFEST | 1 + plugins/sudoers/Makefile.in | 14 +- plugins/sudoers/iolog_util.c | 175 ++++++++++++++---- plugins/sudoers/iolog_util.h | 4 +- .../regress/iolog_plugin/check_iolog_plugin.c | 141 +++++++------- .../regress/iolog_util/check_iolog_util.c | 151 +++++++++++++++ plugins/sudoers/sudoreplay.c | 17 +- 7 files changed, 383 insertions(+), 120 deletions(-) create mode 100644 plugins/sudoers/regress/iolog_util/check_iolog_util.c diff --git a/MANIFEST b/MANIFEST index 6f1cc104c..834f91c47 100644 --- a/MANIFEST +++ b/MANIFEST @@ -465,6 +465,7 @@ plugins/sudoers/regress/env_match/data plugins/sudoers/regress/iolog_path/check_iolog_path.c plugins/sudoers/regress/iolog_path/data plugins/sudoers/regress/iolog_plugin/check_iolog_plugin.c +plugins/sudoers/regress/iolog_util/check_iolog_util.c plugins/sudoers/regress/logging/check_wrap.c plugins/sudoers/regress/logging/check_wrap.in plugins/sudoers/regress/logging/check_wrap.out.ok diff --git a/plugins/sudoers/Makefile.in b/plugins/sudoers/Makefile.in index 9649cbe72..8ab5a4350 100644 --- a/plugins/sudoers/Makefile.in +++ b/plugins/sudoers/Makefile.in @@ -143,7 +143,7 @@ PROGS = sudoers.la visudo sudoreplay cvtsudoers testsudoers TEST_PROGS = check_addr check_base64 check_digest check_env_pattern check_fill \ check_gentime check_hexchar check_iolog_path check_iolog_plugin \ - check_wrap check_starttime @SUDOERS_TEST_PROGS@ + check_iolog_util check_wrap check_starttime @SUDOERS_TEST_PROGS@ AUTH_OBJS = sudo_auth.lo @AUTH_OBJS@ @@ -197,6 +197,9 @@ CHECK_IOLOG_PLUGIN_OBJS = check_iolog_plugin.o iolog.lo iolog_path.lo \ iolog_util.lo locale.lo mkdir_parents.lo pwutil.lo \ pwutil_impl.lo redblack.lo sudoers_debug.lo +CHECK_IOLOG_UTIL_OBJS = check_iolog_util.o iolog_util.lo locale.lo \ + sudoers_debug.lo + CHECK_SYMBOLS_OBJS = check_symbols.o CHECK_STARTTIME_OBJS = check_starttime.o starttime.lo sudoers_debug.lo @@ -285,6 +288,9 @@ check_iolog_path: $(CHECK_IOLOG_PATH_OBJS) $(LT_LIBS) check_iolog_plugin: $(CHECK_IOLOG_PLUGIN_OBJS) $(LT_LIBS) $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_IOLOG_PLUGIN_OBJS) $(LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS) @ZLIB@ +check_iolog_util: $(CHECK_IOLOG_UTIL_OBJS) $(LT_LIBS) + $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_IOLOG_UTIL_OBJS) $(LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS) @ZLIB@ + check_starttime: $(CHECK_STARTTIME_OBJS) $(LT_LIBS) $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_STARTTIME_OBJS) $(LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS) @@ -419,6 +425,7 @@ check: $(TEST_PROGS) visudo testsudoers cvtsudoers ./check_hexchar || rval=`expr $$rval + $$?`; \ ./check_iolog_path $(srcdir)/regress/iolog_path/data || rval=`expr $$rval + $$?`; \ ./check_iolog_plugin $(srcdir)/regress/iolog_plugin/iolog || rval=`expr $$rval + $$?`; \ + ./check_iolog_util || rval=`expr $$rval + $$?`; \ ./check_starttime || rval=`expr $$rval + $$?`; \ if test -f check_symbols; then \ ./check_symbols .libs/sudoers.so $(shlib_exp) || rval=`expr $$rval + $$?`; \ @@ -730,6 +737,11 @@ check_iolog_plugin.o: $(srcdir)/regress/iolog_plugin/check_iolog_plugin.c \ $(srcdir)/sudoers.h $(srcdir)/sudoers_debug.h \ $(top_builddir)/config.h $(top_builddir)/pathnames.h $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/iolog_plugin/check_iolog_plugin.c +check_iolog_util.o: $(srcdir)/regress/iolog_util/check_iolog_util.c \ + $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_fatal.h $(incdir)/sudo_util.h \ + $(srcdir)/iolog_util.h $(top_builddir)/config.h + $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/iolog_util/check_iolog_util.c check_starttime.o: $(srcdir)/regress/starttime/check_starttime.c \ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ $(incdir)/sudo_fatal.h $(incdir)/sudo_util.h \ diff --git a/plugins/sudoers/iolog_util.c b/plugins/sudoers/iolog_util.c index 630f76b30..1704e27da 100644 --- a/plugins/sudoers/iolog_util.c +++ b/plugins/sudoers/iolog_util.c @@ -35,6 +35,7 @@ #include #include #include +#include #ifdef HAVE_STDBOOL_H # include #else @@ -49,6 +50,14 @@ #include "sudo_util.h" #include "iolog_util.h" +#ifndef TIME_T_MAX +# if SIZEOF_TIME_T == 8 +# define TIME_T_MAX LLONG_MAX +# else +# define TIME_T_MAX INT_MAX +# endif +#endif + static int timing_idx_adj; struct log_info * @@ -177,6 +186,115 @@ bad: debug_return_ptr(NULL); } +void +adjust_delay(struct timespec *delay, struct timespec *max_delay, + double scale_factor) +{ + double seconds; + debug_decl(adjust_delay, SUDO_DEBUG_UTIL) + + if (scale_factor != 1.0) { + /* Order is important: we don't want to double the remainder. */ + seconds = (double)delay->tv_sec / scale_factor; + delay->tv_sec = (time_t)seconds; + delay->tv_nsec /= scale_factor; + delay->tv_nsec += (seconds - delay->tv_sec) * 1000000000; + while (delay->tv_nsec >= 1000000000) { + delay->tv_sec++; + delay->tv_nsec -= 1000000000; + } + } + + /* Clamp to max delay. */ + if (max_delay != NULL) { + if (sudo_timespeccmp(delay, max_delay, >)) { + delay->tv_sec = max_delay->tv_sec; + delay->tv_nsec = max_delay->tv_nsec; + } + } + + debug_return; +} + +/* + * Parse the delay as seconds and nanoseconds: %lld.%09ld + * Sudo used to write this as a double, but since timing data is logged + * in the C locale this may not match the current locale. + */ +char * +parse_delay(const char *cp, struct timespec *delay, const char *decimal_point) +{ + char numbuf[(((sizeof(long long) * 8) + 2) / 3) + 2]; + const char *errstr, *ep; + long long llval; + size_t len; + debug_decl(parse_delay, SUDO_DEBUG_UTIL) + + /* Parse seconds (whole number portion). */ + for (ep = cp; isdigit((unsigned char)*ep); ep++) + continue; + len = (size_t)(ep - cp); + if (len >= sizeof(numbuf)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: number of seconds is too large", cp); + debug_return_ptr(NULL); + } + memcpy(numbuf, cp, len); + numbuf[len] = '\0'; + delay->tv_sec = strtonum(numbuf, 0, TIME_T_MAX, &errstr); + if (errstr != NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: number of seconds is %s", numbuf, errstr); + debug_return_ptr(NULL); + } + + /* Radix may be in user's locale for sudo < 1.7.4 so accept that too. */ + if (*ep != '.' && *ep != *decimal_point) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "invalid characters after seconds: %s", ep); + debug_return_ptr(NULL); + } + cp = ep + 1; + + /* Parse fractional part, we may read more precision than we can store. */ + for (ep = cp; isdigit((unsigned char)*ep); ep++) + continue; + len = (size_t)(ep - cp); + if (len >= sizeof(numbuf)) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: number of nanoseconds is too large", cp); + debug_return_ptr(NULL); + } + memcpy(numbuf, cp, len); + numbuf[len] = '\0'; + llval = strtonum(numbuf, 0, LLONG_MAX, &errstr); + if (errstr != NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: number of nanoseconds is %s", numbuf, errstr); + debug_return_ptr(NULL); + } + + /* Adjust fractional part to nanosecond precision. */ + if (len < 9) { + /* Convert to nanosecond precision. */ + do { + llval *= 10; + } while (++len < 9); + } else if (len > 9) { + /* Clamp to nanoseconds. */ + do { + llval /= 10; + } while (--len > 9); + } + delay->tv_nsec = (long)llval; + + /* Advance to the next field. */ + while (isspace((unsigned char)*ep)) + ep++; + + debug_return_str((char *)ep); +} + /* * Parse a timing line, which is formatted as: * index sleep_time num_bytes @@ -185,78 +303,57 @@ bad: * Returns true on success and false on failure. */ bool -parse_timing(const char *buf, double *seconds, struct timing_closure *timing) +parse_timing(const char *buf, struct timespec *delay, + struct timing_closure *timing) { - unsigned long ul; - long l; - double d, fract = 0; + unsigned long ulval; char *cp, *ep; debug_decl(parse_timing, SUDO_DEBUG_UTIL) /* Parse index */ - ul = strtoul(buf, &ep, 10); + ulval = strtoul(buf, &ep, 10); if (ep == buf || !isspace((unsigned char) *ep)) goto bad; - if (ul >= IOFD_MAX) { - if (ul != 6) + if (ulval >= IOFD_MAX) { + if (ulval != 6) goto bad; /* work around a bug in timing files generated by sudo 1.8.7 */ timing_idx_adj = 2; } - timing->idx = (int)ul - timing_idx_adj; + timing->idx = (int)ulval - timing_idx_adj; for (cp = ep + 1; isspace((unsigned char) *cp); cp++) continue; - /* - * Parse number of seconds. Sudo logs timing data in the C locale - * but this may not match the current locale so we cannot use strtod(). - * Furthermore, sudo < 1.7.4 logged with the user's locale so we need - * to be able to parse those logs too. - */ - errno = 0; - l = strtol(cp, &ep, 10); - if (ep == cp || (*ep != '.' && strncmp(ep, timing->decimal, strlen(timing->decimal)) != 0)) + /* Parse delay, returns the next field or NULL on error. */ + if ((cp = parse_delay(cp, delay, timing->decimal)) == NULL) goto bad; - if (l < 0 || l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) - goto bad; - *seconds = (double)l; - cp = ep + (*ep == '.' ? 1 : strlen(timing->decimal)); - d = 10.0; - while (isdigit((unsigned char) *cp)) { - fract += (*cp - '0') / d; - d *= 10; - cp++; - } - *seconds += fract; - while (isspace((unsigned char) *cp)) - cp++; if (timing->idx == IOFD_TIMING) { errno = 0; - ul = strtoul(cp, &ep, 10); + ulval = strtoul(cp, &ep, 10); if (ep == cp || !isspace((unsigned char) *ep)) goto bad; - if (ul > INT_MAX || (errno == ERANGE && ul == ULONG_MAX)) + if (ulval > INT_MAX || (errno == ERANGE && ulval == ULONG_MAX)) goto bad; - timing->u.winsize.rows = (int)ul; + timing->u.winsize.rows = (int)ulval; for (cp = ep + 1; isspace((unsigned char) *cp); cp++) continue; errno = 0; - ul = strtoul(cp, &ep, 10); + ulval = strtoul(cp, &ep, 10); if (ep == cp || *ep != '\0') goto bad; - if (ul > INT_MAX || (errno == ERANGE && ul == ULONG_MAX)) + if (ulval > INT_MAX || (errno == ERANGE && ulval == ULONG_MAX)) goto bad; - timing->u.winsize.cols = (int)ul; + timing->u.winsize.cols = (int)ulval; } else { errno = 0; - ul = strtoul(cp, &ep, 10); + ulval = strtoul(cp, &ep, 10); if (ep == cp || *ep != '\0') goto bad; - if (ul > SIZE_MAX || (errno == ERANGE && ul == ULONG_MAX)) + if (ulval > SIZE_MAX || (errno == ERANGE && ulval == ULONG_MAX)) goto bad; - timing->u.nbytes = (size_t)ul; + timing->u.nbytes = (size_t)ulval; } debug_return_bool(true); diff --git a/plugins/sudoers/iolog_util.h b/plugins/sudoers/iolog_util.h index dd2908f17..5d8cbc4e6 100644 --- a/plugins/sudoers/iolog_util.h +++ b/plugins/sudoers/iolog_util.h @@ -59,8 +59,10 @@ struct timing_closure { # define IOFD_MAX 6 #endif -bool parse_timing(const char *buf, double *seconds, struct timing_closure *timing); +bool parse_timing(const char *buf, struct timespec *delay, struct timing_closure *timing); +char *parse_delay(const char *cp, struct timespec *delay, const char *decimal_point); struct log_info *parse_logfile(const char *logfile); void free_log_info(struct log_info *li); +void adjust_delay(struct timespec *delay, struct timespec *max_delay, double scale_factor); #endif /* SUDOERS_IOLOG_UTIL_H */ diff --git a/plugins/sudoers/regress/iolog_plugin/check_iolog_plugin.c b/plugins/sudoers/regress/iolog_plugin/check_iolog_plugin.c index 20c145a84..c909cbd16 100644 --- a/plugins/sudoers/regress/iolog_plugin/check_iolog_plugin.c +++ b/plugins/sudoers/regress/iolog_plugin/check_iolog_plugin.c @@ -147,7 +147,7 @@ validate_timing(FILE *fp, int recno, int type, unsigned int p1, unsigned int p2) { struct timing_closure timing; char buf[LINE_MAX]; - double delay; + struct timespec delay; if (!fgets(buf, sizeof(buf), fp)) { sudo_warn("unable to read timing file"); @@ -181,21 +181,23 @@ validate_timing(FILE *fp, int recno, int type, unsigned int p1, unsigned int p2) return false; } } - if (delay > 0.01) { - sudo_warnx("record %d: got excessive delay %f", recno, delay); + if (delay.tv_sec != 0 || delay.tv_nsec > 10000000) { + sudo_warnx("record %d: got excessive delay %lld.%09ld", recno, + (long long)delay.tv_sec, delay.tv_nsec); return false; } return true; } -int -main(int argc, char *argv[], char *envp[]) + +/* + * Test sudoers I/O log plugin endpoints. + */ +void +test_endpoints(int *ntests, int *nerrors, const char *iolog_dir, char *envp[]) { - struct passwd pw, rpw, *tpw; - int rc, tests = 0, errors = 0; - int cmnd_argc = 1; - const char *iolog_dir; + int rc, cmnd_argc = 1; char buf[1024], iolog_path[PATH_MAX]; char runas_gid[64], runas_uid[64]; FILE *fp; @@ -230,76 +232,54 @@ main(int argc, char *argv[], char *envp[]) }; const char output[] = "uid=0(root) gid=0(wheel)\r\n"; - initprogname(argc > 0 ? argv[0] : "check_iolog_plugin"); - - if (argc != 2) - usage(); - iolog_dir = argv[1]; - - /* Bare minimum to link. */ - memset(&pw, 0, sizeof(pw)); - memset(&rpw, 0, sizeof(rpw)); - if ((tpw = getpwuid(0)) == NULL) { - if ((tpw = getpwnam("root")) == NULL) - sudo_fatalx("unable to look up uid 0 or root"); - } - rpw.pw_uid = tpw->pw_uid; - rpw.pw_gid = tpw->pw_gid; - sudo_user.pw = &pw; - sudo_user._runas_pw = &rpw; - /* Set runas uid/gid to root. */ snprintf(runas_uid, sizeof(runas_uid), "runas_uid=%u", - (unsigned int)rpw.pw_uid); + (unsigned int)runas_pw->pw_uid); snprintf(runas_gid, sizeof(runas_gid), "runas_gid=%u", - (unsigned int)rpw.pw_gid); + (unsigned int)runas_pw->pw_gid); /* Set path to the iolog directory the user passed in. */ snprintf(iolog_path, sizeof(iolog_path), "iolog_path=%s", iolog_dir); - /* Set iolog uid/gid to invoking user. */ - iolog_uid = geteuid(); - iolog_gid = getegid(); - /* Test open endpoint. */ rc = sudoers_io.open(SUDO_API_VERSION, NULL, sudo_printf_int, settings, user_info, command_info, cmnd_argc, cmnd_argv, envp, NULL); - tests++; + (*ntests)++; if (rc != 1) { sudo_warnx("I/O log open endpoint failed"); - errors++; - goto done; + (*nerrors)++; + return; } /* Validate I/O log info file. */ - tests++; + (*ntests)++; snprintf(iolog_path, sizeof(iolog_path), "%s/log", iolog_dir); if (!validate_iolog_info(iolog_path)) - errors++; + (*nerrors)++; /* Test log_ttyout endpoint. */ rc = sudoers_io.log_ttyout(output, strlen(output)); - tests++; + (*ntests)++; if (rc != 1) { sudo_warnx("I/O log_ttyout endpoint failed"); - errors++; - goto done; + (*nerrors)++; + return; } /* Test change_winsize endpoint (twice). */ rc = sudoers_io.change_winsize(32, 128); - tests++; + (*ntests)++; if (rc != 1) { sudo_warnx("I/O change_winsize endpoint failed"); - errors++; - goto done; + (*nerrors)++; + return; } rc = sudoers_io.change_winsize(24, 80); - tests++; + (*ntests)++; if (rc != 1) { sudo_warnx("I/O change_winsize endpoint failed"); - errors++; - goto done; + (*nerrors)++; + return; } /* Close the plugin. */ @@ -307,54 +287,85 @@ main(int argc, char *argv[], char *envp[]) /* Validate the timing file. */ snprintf(iolog_path, sizeof(iolog_path), "%s/timing", iolog_dir); - tests++; + (*ntests)++; if ((fp = fopen(iolog_path, "r")) == NULL) { sudo_warn("unable to open %s", iolog_path); - errors++; - goto done; + (*nerrors)++; + return; } /* Line 1: output of id command. */ if (!validate_timing(fp, 1, IOFD_TTYOUT, strlen(output), 0)) { - errors++; - goto done; + (*nerrors)++; + return; } /* Line 2: window size change. */ if (!validate_timing(fp, 2, IOFD_TIMING, 32, 128)) { - errors++; - goto done; + (*nerrors)++; + return; } /* Line 3: window size change. */ if (!validate_timing(fp, 3, IOFD_TIMING, 24, 80)) { - errors++; - goto done; + (*nerrors)++; + return; } /* Validate ttyout log file. */ snprintf(iolog_path, sizeof(iolog_path), "%s/ttyout", iolog_dir); - tests++; + (*ntests)++; fclose(fp); if ((fp = fopen(iolog_path, "r")) == NULL) { sudo_warn("unable to open %s", iolog_path); - errors++; - goto done; + (*nerrors)++; + return; } if (!fgets(buf, sizeof(buf), fp)) { sudo_warn("unable to read %s", iolog_path); - errors++; - goto done; + (*nerrors)++; + return; } if (strcmp(buf, output) != 0) { sudo_warnx("ttylog mismatch: want \"%s\", got \"%s\"", output, buf); - errors++; - goto done; + (*nerrors)++; + return; } +} + +int +main(int argc, char *argv[], char *envp[]) +{ + struct passwd pw, rpw, *tpw; + int tests = 0, errors = 0; + const char *iolog_dir; + + initprogname(argc > 0 ? argv[0] : "check_iolog_plugin"); + + if (argc != 2) + usage(); + iolog_dir = argv[1]; + + /* Bare minimum to link. */ + memset(&pw, 0, sizeof(pw)); + memset(&rpw, 0, sizeof(rpw)); + if ((tpw = getpwuid(0)) == NULL) { + if ((tpw = getpwnam("root")) == NULL) + sudo_fatalx("unable to look up uid 0 or root"); + } + rpw.pw_uid = tpw->pw_uid; + rpw.pw_gid = tpw->pw_gid; + sudo_user.pw = &pw; + sudo_user._runas_pw = &rpw; + + /* Set iolog uid/gid to invoking user. */ + iolog_uid = geteuid(); + iolog_gid = getegid(); + + test_endpoints(&tests, &errors, iolog_dir, envp); -done: if (tests != 0) { - printf("iolog_plugin: %d test%s run, %d errors, %d%% success rate\n", + printf("check_iolog_plugin: %d test%s run, %d errors, %d%% success rate\n", tests, tests == 1 ? "" : "s", errors, (tests - errors) * 100 / tests); } diff --git a/plugins/sudoers/regress/iolog_util/check_iolog_util.c b/plugins/sudoers/regress/iolog_util/check_iolog_util.c new file mode 100644 index 000000000..d25aed4e3 --- /dev/null +++ b/plugins/sudoers/regress/iolog_util/check_iolog_util.c @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2018 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 + * 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. + */ + +#include + +#include +#include +#include +#ifdef HAVE_STRING_H +# include +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include +#endif /* HAVE_STRINGS_H */ +#include +#include + +#define SUDO_ERROR_WRAP 0 + +#include "sudo_compat.h" +#include "sudo_util.h" +#include "sudo_fatal.h" +#include "iolog_util.h" + +__dso_public int main(int argc, char *argv[]); + +static struct parse_delay_test { + const char *input; + const char *next_field; + struct timespec expected_delay; +} parse_delay_tests[] = { + { "10.99999999999 X", "X", { 10, 999999999 } }, /* clamp to nsec */ + { "10.999999999 X", "X", { 10, 999999999 } }, /* nsec */ + { "10.999999 X", "X", { 10, 999999000 } }, /* usec -> nsec */ + { "10.000999999 X", "X", { 10, 999999 } }, + { "10.9 X", "X", { 10, 900000000 } }, + { "10.0 X", "X", { 10, 0 } } +}; + +/* + * Test parse_delay() + */ +void +test_parse_delay(int *ntests, int *nerrors) +{ + unsigned int i; + + for (i = 0; i < nitems(parse_delay_tests); i++) { + struct timespec delay; + struct parse_delay_test *test = &parse_delay_tests[i]; + char *cp = parse_delay(test->input, &delay, "."); + if (cp == NULL) { + sudo_warnx("%s:%u failed to parse delay: %s", __func__, + i, test->input); + (*nerrors)++; + continue; + } + if (strcmp(cp, test->next_field) != 0) { + sudo_warnx("%s:%u next field (want \"%s\", got \"%s\"", __func__, + i, test->next_field, cp); + (*nerrors)++; + continue; + } + if (delay.tv_sec != test->expected_delay.tv_sec) { + sudo_warnx("%s:%u wrong seconds (want %lld, got %lld)", __func__, + i, (long long)test->expected_delay.tv_sec, + (long long)delay.tv_sec); + (*nerrors)++; + continue; + } + if (delay.tv_nsec != test->expected_delay.tv_nsec) { + sudo_warnx("%s:%u wrong nanoseconds (want %ld, got %ld)", __func__, + i, test->expected_delay.tv_nsec, delay.tv_nsec); + (*nerrors)++; + continue; + } + } + (*ntests) += i; +} + +static struct adjust_delay_test { + struct timespec in_delay; + struct timespec out_delay; + struct timespec max_delay; + double scale_factor; +} adjust_delay_tests[] = { + { { 10, 300 }, { 10, 300 }, { 0, 0 }, 1.0 }, + { { 10, 300 }, { 5, 150 }, { 0, 0 }, 2.0 }, + { { 5, 300 }, { 2, 500000150 }, { 0, 0 }, 2.0 }, + { { 0, 1000000 }, { 0, 333333 }, { 0, 0 }, 3 }, + { { 10, 1000000 }, { 3, 333666666 }, { 0, 0 }, 3 }, + { { 5, 150 }, { 10, 300 }, { 0, 0 }, 0.5 }, + { { 5, 500000000 }, { 11, 0 }, { 0, 0 }, 0.5 }, + { { 5, 150 }, { 5, 0 }, { 5, 0 }, 0.5 } +}; + +/* + * Test adjust_delay() + */ +void +test_adjust_delay(int *ntests, int *nerrors) +{ + unsigned int i; + + for (i = 0; i < nitems(adjust_delay_tests); i++) { + struct adjust_delay_test *test = &adjust_delay_tests[i]; + + adjust_delay(&test->in_delay, sudo_timespecisset(&test->max_delay) ? + &test->max_delay : NULL, test->scale_factor); + if (!sudo_timespeccmp(&test->in_delay, &test->out_delay, ==)) { + sudo_warnx("%s:%u want {%lld, %ld}, got {%lld, %ld}", __func__, i, + (long long)test->out_delay.tv_sec, test->out_delay.tv_nsec, + (long long)test->in_delay.tv_sec, test->in_delay.tv_nsec); + (*nerrors)++; + } + } + (*ntests) += i; +} + +int +main(int argc, char *argv[]) +{ + int tests = 0, errors = 0; + + initprogname(argc > 0 ? argv[0] : "check_iolog_util"); + + test_parse_delay(&tests, &errors); + + test_adjust_delay(&tests, &errors); + + if (tests != 0) { + printf("check_iolog_util: %d test%s run, %d errors, %d%% success rate\n", + tests, tests == 1 ? "" : "s", errors, + (tests - errors) * 100 / tests); + } + + exit(errors); +} diff --git a/plugins/sudoers/sudoreplay.c b/plugins/sudoers/sudoreplay.c index 979b13140..667dbc64b 100644 --- a/plugins/sudoers/sudoreplay.c +++ b/plugins/sudoers/sudoreplay.c @@ -745,7 +745,6 @@ read_timing_record(struct replay_closure *closure) { struct timespec timeout; char buf[LINE_MAX]; - double delay; debug_decl(read_timing_record, SUDO_DEBUG_UTIL) /* Read next record from timing file. */ @@ -756,7 +755,7 @@ read_timing_record(struct replay_closure *closure) /* Parse timing file record. */ buf[strcspn(buf, "\n")] = '\0'; - if (!parse_timing(buf, &delay, &closure->timing)) + if (!parse_timing(buf, &timeout, &closure->timing)) sudo_fatalx(U_("invalid timing file line: %s"), buf); /* Record number bytes to read. */ @@ -768,18 +767,8 @@ read_timing_record(struct replay_closure *closure) closure->iobuf.toread = closure->timing.u.nbytes; } - /* Adjust delay using speed factor. */ - delay /= speed_factor; - - /* Convert delay to a timespec. */ - timeout.tv_sec = delay; - timeout.tv_nsec = (delay - timeout.tv_sec) * 1000000000.0; - - /* Clamp timeout to max delay. */ - if (closure->timing.max_delay != NULL) { - if (sudo_timespeccmp(&timeout, closure->timing.max_delay, >)) - timeout = *closure->timing.max_delay; - } + /* Adjust delay using speed factor and max_delay. */ + adjust_delay(&timeout, closure->timing.max_delay, speed_factor); /* Schedule the delay event. */ if (sudo_ev_add(closure->evbase, closure->delay_ev, &timeout, false) == -1) -- 2.40.0