changeset 1622:e11ed628708b

- Add adaptive authentication failure delay - Rework monotonic_now/gettime_wrapper and use clock_gettime on more platforms
author Matt Johnston <matt@ucc.asn.au>
date Mon, 05 Nov 2018 23:36:34 +0800
parents 8cdabd7d34aa
children c66c49ebf77d 1f3fb83b0524
files auth.h configure.ac dbutil.c dbutil.h includes.h svr-auth.c
diffstat 6 files changed, 96 insertions(+), 49 deletions(-) [+]
line wrap: on
line diff
--- 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;
--- 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)
--- 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) {
--- 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);
 
--- 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 <sys/types.h>
 #include <sys/ioctl.h>
 #include <sys/param.h> /* required for BSD4_4 define */
--- 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++;
 	}