- Add adaptive authentication failure delay

- Rework monotonic_now/gettime_wrapper and use clock_gettime on more platforms
This commit is contained in:
Matt Johnston 2018-11-05 23:36:34 +08:00
parent 6f6ef4834c
commit 02ffdd09dc
6 changed files with 96 additions and 49 deletions

3
auth.h
View File

@ -108,11 +108,14 @@ struct AuthState {
unsigned int authdone; /* 0 if we haven't authed, 1 if we have. Applies for unsigned int authdone; /* 0 if we haven't authed, 1 if we have. Applies for
client and server (though has differing client and server (though has differing
meanings). */ meanings). */
unsigned int perm_warn; /* Server only, set if bad permissions on unsigned int perm_warn; /* Server only, set if bad permissions on
~/.ssh/authorized_keys have already been ~/.ssh/authorized_keys have already been
logged. */ logged. */
unsigned int checkusername_failed; /* Server only, set if checkusername unsigned int checkusername_failed; /* Server only, set if checkusername
has already failed */ has already failed */
struct timespec auth_starttime; /* Server only, time of receiving current
SSH_MSG_USERAUTH_REQUEST */
/* These are only used for the server */ /* These are only used for the server */
uid_t pw_uid; uid_t pw_uid;

View File

@ -497,6 +497,12 @@ AC_CHECK_FUNCS(endutxent getutxent getutxid getutxline pututxline )
AC_CHECK_FUNCS(setutxent utmpxname) AC_CHECK_FUNCS(setutxent utmpxname)
AC_CHECK_FUNCS(logout updwtmp logwtmp) 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 # OS X monotonic time
AC_CHECK_HEADERS([mach/mach_time.h]) AC_CHECK_HEADERS([mach/mach_time.h])
AC_CHECK_FUNCS(mach_absolute_time) AC_CHECK_FUNCS(mach_absolute_time)

View File

@ -605,71 +605,67 @@ int constant_time_memcmp(const void* a, const void *b, size_t n)
return c; return c;
} }
#if defined(__linux__) && defined(SYS_clock_gettime) /* higher-resolution monotonic timestamp, falls back to gettimeofday */
/* CLOCK_MONOTONIC_COARSE was added in Linux 2.6.32 but took a while to void gettime_wrapper(struct timespec *now) {
reach userspace include headers */ struct timeval tv;
#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() {
#if DROPBEAR_FUZZ #if DROPBEAR_FUZZ
if (fuzz.fuzzing) { if (fuzz.fuzzing) {
/* time stands still when fuzzing */ /* time stands still when fuzzing */
return 5; now->tv_sec = 5;
now->tv_nsec = 0;
} }
#endif #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) #if defined(__linux__) && defined(SYS_clock_gettime)
{ {
static clockid_t clock_source = -2; /* Old linux toolchain - kernel might support it but not the build headers */
/* Also glibc <2.17 requires -lrt which we neglect to add */
if (clock_source == -2) { static int linux_monotonic_failed = 0;
/* First run, find out which one works. if (!linux_monotonic_failed) {
-1 will fall back to time() */ /* CLOCK_MONOTONIC isn't in some headers */
clock_source = get_linux_clock_source(); int clock_source_monotonic = 1;
} if (syscall(SYS_clock_gettime, clock_source_monotonic, now) == 0) {
return;
if (clock_source >= 0) { } else {
struct timespec ts; /* Don't try again */
if (syscall(SYS_clock_gettime, clock_source, &ts) != 0) { linux_monotonic_failed = 1;
/* Intermittent clock failures should not happen */
dropbear_exit("Clock broke");
} }
return ts.tv_sec;
} }
} }
#endif /* linux clock_gettime */ #endif /* linux fallback clock_gettime */
#if defined(HAVE_MACH_ABSOLUTE_TIME) #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; static mach_timebase_info_data_t timebase_info;
uint64_t scaled_time;
if (timebase_info.denom == 0) { if (timebase_info.denom == 0) {
mach_timebase_info(&timebase_info); mach_timebase_info(&timebase_info);
} }
return mach_absolute_time() * timebase_info.numer / timebase_info.denom scaled_time = mach_absolute_time() * timebase_info.numer / timebase_info.denom;
/ 1e9; now->tv_sec = scaled_time / 1000000000;
now->tv_nsec = scaled_time % 1000000000;
} }
#endif /* osx mach_absolute_time */ #endif /* osx mach_absolute_time */
/* Fallback for everything else - this will sometimes go backwards */ /* 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) { void fsync_parent_dir(const char* fn) {

View File

@ -83,6 +83,8 @@ int constant_time_memcmp(const void* a, const void *b, size_t n);
/* Returns a time in seconds that doesn't go backwards - does not correspond to /* Returns a time in seconds that doesn't go backwards - does not correspond to
a real-world clock */ a real-world clock */
time_t monotonic_now(void); 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); char * expand_homedir_path(const char *inpath);

View File

@ -29,6 +29,11 @@
#include "options.h" #include "options.h"
#include "debug.h" #include "debug.h"
#if __linux__
/* For clock_gettime */
#define _POSIX_C_SOURCE 199309L
#endif
#include <sys/types.h> #include <sys/types.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/param.h> /* required for BSD4_4 define */ #include <sys/param.h> /* required for BSD4_4 define */

View File

@ -79,6 +79,9 @@ void recv_msg_userauth_request() {
TRACE(("enter recv_msg_userauth_request")) TRACE(("enter recv_msg_userauth_request"))
/* for compensating failure delay */
gettime_wrapper(&ses.authstate.auth_starttime);
/* ignore packets if auth is already done */ /* ignore packets if auth is already done */
if (ses.authstate.authdone == 1) { if (ses.authstate.authdone == 1) {
TRACE(("leave recv_msg_userauth_request: authdone already")) TRACE(("leave recv_msg_userauth_request: authdone already"))
@ -382,16 +385,48 @@ void send_msg_userauth_failure(int partial, int incrfail) {
encrypt_packet(); encrypt_packet();
if (incrfail) { if (incrfail) {
unsigned int delay; /* The SSH_MSG_AUTH_FAILURE response is delayed to attempt to
genrandom((unsigned char*)&delay, sizeof(delay)); avoid user enumeration and slow brute force attempts.
/* We delay for 300ms +- 50ms */ The delay is adjusted by the time already spent in processing
delay = 250000 + (delay % 100000); 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 DROPBEAR_FUZZ
if (!fuzz.fuzzing) if (!fuzz.fuzzing)
#endif #endif
{ {
usleep(delay); while (nanosleep(&delay, &delay) == -1 && errno == EINTR) { /* Go back to sleep */ }
} }
ses.authstate.failcount++; ses.authstate.failcount++;
} }