# HG changeset patch # User Matt Johnston # Date 1541432194 -28800 # Node ID e11ed628708b4f9b4c8bd9b2aff445ae13e93f6d # Parent 8cdabd7d34aa3b21aa12f22b59c8cf6a44ab3f15 - Add adaptive authentication failure delay - Rework monotonic_now/gettime_wrapper and use clock_gettime on more platforms diff -r 8cdabd7d34aa -r e11ed628708b auth.h --- a/auth.h Fri Sep 07 23:02:53 2018 +0800 +++ b/auth.h Mon Nov 05 23:36:34 2018 +0800 @@ -108,11 +108,14 @@ unsigned int authdone; /* 0 if we haven't authed, 1 if we have. Applies for client and server (though has differing meanings). */ + unsigned int perm_warn; /* Server only, set if bad permissions on ~/.ssh/authorized_keys have already been logged. */ unsigned int checkusername_failed; /* Server only, set if checkusername has already failed */ + struct timespec auth_starttime; /* Server only, time of receiving current + SSH_MSG_USERAUTH_REQUEST */ /* These are only used for the server */ uid_t pw_uid; diff -r 8cdabd7d34aa -r e11ed628708b configure.ac --- a/configure.ac Fri Sep 07 23:02:53 2018 +0800 +++ b/configure.ac Mon Nov 05 23:36:34 2018 +0800 @@ -497,6 +497,12 @@ AC_CHECK_FUNCS(setutxent utmpxname) AC_CHECK_FUNCS(logout updwtmp logwtmp) +# POSIX monotonic time +OLDCFLAGS="$CFLAGS" +CFLAGS="$CFLAGS -D_POSIX_C_SOURCE=199309L" +AC_CHECK_FUNCS(clock_gettime) +CFLAGS="$OLDCFLAGS" + # OS X monotonic time AC_CHECK_HEADERS([mach/mach_time.h]) AC_CHECK_FUNCS(mach_absolute_time) diff -r 8cdabd7d34aa -r e11ed628708b dbutil.c --- a/dbutil.c Fri Sep 07 23:02:53 2018 +0800 +++ b/dbutil.c Mon Nov 05 23:36:34 2018 +0800 @@ -605,71 +605,67 @@ return c; } -#if defined(__linux__) && defined(SYS_clock_gettime) -/* CLOCK_MONOTONIC_COARSE was added in Linux 2.6.32 but took a while to -reach userspace include headers */ -#ifndef CLOCK_MONOTONIC_COARSE -#define CLOCK_MONOTONIC_COARSE 6 -#endif -/* Some old toolchains know SYS_clock_gettime but not CLOCK_MONOTONIC */ -#ifndef CLOCK_MONOTONIC -#define CLOCK_MONOTONIC 1 -#endif -static clockid_t get_linux_clock_source() { - struct timespec ts; - if (syscall(SYS_clock_gettime, CLOCK_MONOTONIC_COARSE, &ts) == 0) { - return CLOCK_MONOTONIC_COARSE; - } - - if (syscall(SYS_clock_gettime, CLOCK_MONOTONIC, &ts) == 0) { - return CLOCK_MONOTONIC; - } - return -1; -} -#endif - -time_t monotonic_now() { +/* higher-resolution monotonic timestamp, falls back to gettimeofday */ +void gettime_wrapper(struct timespec *now) { + struct timeval tv; #if DROPBEAR_FUZZ if (fuzz.fuzzing) { /* time stands still when fuzzing */ - return 5; + now->tv_sec = 5; + now->tv_nsec = 0; } #endif + +#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) + /* POSIX monotonic clock. Newer Linux, BSD, MacOSX >10.12 */ + if (clock_gettime(CLOCK_MONOTONIC, now) == 0) { + return; + } +#endif + #if defined(__linux__) && defined(SYS_clock_gettime) { - static clockid_t clock_source = -2; - - if (clock_source == -2) { - /* First run, find out which one works. - -1 will fall back to time() */ - clock_source = get_linux_clock_source(); - } - - if (clock_source >= 0) { - struct timespec ts; - if (syscall(SYS_clock_gettime, clock_source, &ts) != 0) { - /* Intermittent clock failures should not happen */ - dropbear_exit("Clock broke"); + /* Old linux toolchain - kernel might support it but not the build headers */ + /* Also glibc <2.17 requires -lrt which we neglect to add */ + static int linux_monotonic_failed = 0; + if (!linux_monotonic_failed) { + /* CLOCK_MONOTONIC isn't in some headers */ + int clock_source_monotonic = 1; + if (syscall(SYS_clock_gettime, clock_source_monotonic, now) == 0) { + return; + } else { + /* Don't try again */ + linux_monotonic_failed = 1; } - return ts.tv_sec; } } -#endif /* linux clock_gettime */ +#endif /* linux fallback clock_gettime */ #if defined(HAVE_MACH_ABSOLUTE_TIME) { - /* OS X, see https://developer.apple.com/library/mac/qa/qa1398/_index.html */ + /* OS X pre 10.12, see https://developer.apple.com/library/mac/qa/qa1398/_index.html */ static mach_timebase_info_data_t timebase_info; + uint64_t scaled_time; if (timebase_info.denom == 0) { mach_timebase_info(&timebase_info); } - return mach_absolute_time() * timebase_info.numer / timebase_info.denom - / 1e9; + scaled_time = mach_absolute_time() * timebase_info.numer / timebase_info.denom; + now->tv_sec = scaled_time / 1000000000; + now->tv_nsec = scaled_time % 1000000000; } #endif /* osx mach_absolute_time */ /* Fallback for everything else - this will sometimes go backwards */ - return time(NULL); + gettimeofday(&tv, NULL); + now->tv_sec = tv.tv_sec; + now->tv_nsec = 1000*tv.tv_usec; +} + +/* second-resolution monotonic timestamp */ +time_t monotonic_now() { + struct timespec ts; + gettime_wrapper(&ts); + return ts.tv_sec; } void fsync_parent_dir(const char* fn) { diff -r 8cdabd7d34aa -r e11ed628708b dbutil.h --- a/dbutil.h Fri Sep 07 23:02:53 2018 +0800 +++ b/dbutil.h Mon Nov 05 23:36:34 2018 +0800 @@ -83,6 +83,8 @@ /* Returns a time in seconds that doesn't go backwards - does not correspond to a real-world clock */ time_t monotonic_now(void); +/* Higher resolution clock_gettime(CLOCK_MONOTONIC) wrapper */ +void gettime_wrapper(struct timespec *now); char * expand_homedir_path(const char *inpath); diff -r 8cdabd7d34aa -r e11ed628708b includes.h --- a/includes.h Fri Sep 07 23:02:53 2018 +0800 +++ b/includes.h Mon Nov 05 23:36:34 2018 +0800 @@ -29,6 +29,11 @@ #include "options.h" #include "debug.h" +#if __linux__ +/* For clock_gettime */ +#define _POSIX_C_SOURCE 199309L +#endif + #include #include #include /* required for BSD4_4 define */ diff -r 8cdabd7d34aa -r e11ed628708b svr-auth.c --- a/svr-auth.c Fri Sep 07 23:02:53 2018 +0800 +++ b/svr-auth.c Mon Nov 05 23:36:34 2018 +0800 @@ -79,6 +79,9 @@ TRACE(("enter recv_msg_userauth_request")) + /* for compensating failure delay */ + gettime_wrapper(&ses.authstate.auth_starttime); + /* ignore packets if auth is already done */ if (ses.authstate.authdone == 1) { TRACE(("leave recv_msg_userauth_request: authdone already")) @@ -382,16 +385,48 @@ encrypt_packet(); if (incrfail) { - unsigned int delay; - genrandom((unsigned char*)&delay, sizeof(delay)); - /* We delay for 300ms +- 50ms */ - delay = 250000 + (delay % 100000); + /* The SSH_MSG_AUTH_FAILURE response is delayed to attempt to + avoid user enumeration and slow brute force attempts. + The delay is adjusted by the time already spent in processing + authentication (ses.authstate.auth_starttime timestamp). */ + + /* Desired total delay 300ms +-50ms (in nanoseconds). + Beware of integer overflow if increasing these values */ + const unsigned int mindelay = 250000000; + const unsigned int vardelay = 100000000; + unsigned int rand_delay; + struct timespec delay; + + gettime_wrapper(&delay); + delay.tv_sec -= ses.authstate.auth_starttime.tv_sec; + delay.tv_nsec -= ses.authstate.auth_starttime.tv_nsec; + + /* carry */ + if (delay.tv_nsec < 0) { + delay.tv_nsec += 1000000000; + delay.tv_sec -= 1; + } + + genrandom((unsigned char*)&rand_delay, sizeof(rand_delay)); + rand_delay = mindelay + (rand_delay % vardelay); + + if (delay.tv_sec == 0 && delay.tv_nsec <= mindelay) { + /* Compensate for elapsed time */ + delay.tv_nsec = rand_delay - delay.tv_nsec; + } else { + /* No time left or time went backwards, just delay anyway */ + delay.tv_sec = 0; + delay.tv_nsec = rand_delay; + } + + #if DROPBEAR_FUZZ if (!fuzz.fuzzing) #endif { - usleep(delay); + while (nanosleep(&delay, &delay) == -1 && errno == EINTR) { /* Go back to sleep */ } } + ses.authstate.failcount++; }