From: Nick Mathewson Date: Wed, 10 Feb 2010 22:19:18 +0000 (-0500) Subject: Add an arc4random implementation for use by evdns X-Git-Tag: release-2.0.4-alpha~29^2~2 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=d4de062efcf3facf451c826deee9240fac478bfd;p=libevent Add an arc4random implementation for use by evdns Previously, evdns was at the mercy of the user for providing a good entropy source; without one, it would be vulnerable to various active attacks. This patch adds a port of OpenBSD's arc4random() calls to Libevent [port by Chris Davis], and wraps it up a little bit so we can use it more safely. --- diff --git a/Makefile.am b/Makefile.am index 22e7d3e3..f5fc7a63 100644 --- a/Makefile.am +++ b/Makefile.am @@ -44,7 +44,7 @@ EXTRA_DIST = \ Doxyfile \ kqueue.c epoll_sub.c epoll.c select.c poll.c signal.c \ evport.c devpoll.c win32select.c event_rpcgen.py \ - event_iocp.c buffer_iocp.c iocp-internal.h \ + event_iocp.c buffer_iocp.c iocp-internal.h arc4random.c \ sample/Makefile.am sample/Makefile.in sample/event-test.c \ sample/signal-test.c sample/time-test.c \ test/Makefile.am test/Makefile.in test/bench.c test/regress.c \ @@ -107,7 +107,7 @@ event-config.h: config.h CORE_SRC = event.c evthread.c buffer.c \ bufferevent.c bufferevent_sock.c bufferevent_filter.c \ bufferevent_pair.c listener.c bufferevent_ratelim.c \ - evmap.c log.c evutil.c strlcpy.c $(SYS_SRC) + evmap.c log.c evutil.c evutil_rand.c strlcpy.c $(SYS_SRC) EXTRA_SRC = event_tagging.c http.c evdns.c evrpc.c diff --git a/arc4random.c b/arc4random.c new file mode 100644 index 00000000..2353626e --- /dev/null +++ b/arc4random.c @@ -0,0 +1,358 @@ +/* Portable arc4random.c based on arc4random.c from OpenBSD. + * Portable version by Chris Davis, adapted for Libevent by Nick Mathewson + * + * Note that in Libevent, this file isn't compiled directly. Instead, + * it's included from evutil_rand.c + */ + +/* + * Copyright (c) 1996, David Mazieres + * Copyright (c) 2008, Damien 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. + */ + +/* + * Arc4 random number generator for OpenBSD. + * + * This code is derived from section 17.1 of Applied Cryptography, + * second edition, which describes a stream cipher allegedly + * compatible with RSA Labs "RC4" cipher (the actual description of + * which is a trade secret). The same algorithm is used as a stream + * cipher called "arcfour" in Tatu Ylonen's ssh package. + * + * Here the stream cipher has been modified always to include the time + * when initializing the state. That makes it impossible to + * regenerate the same random sequence twice, so this can't be used + * for encryption, but will generate good random numbers. + * + * RC4 is a registered trademark of RSA Laboratories. + */ + +#ifndef ARC4RANDOM_EXPORT +#define ARC4RANDOM_EXPORT +#endif + +#ifndef ARC4RANDOM_NO_INCLUDES +#ifdef WIN32 +#include +#else +#include +#include +#include +#include +#endif +#include +#include +#include +#endif + +/* Add platform entropy 32 bytes (256 bits) at a time. */ +#define ADD_ENTROPY 32 + +/* Re-seed from the platform RNG after generating this many bytes. */ +#define BYTES_BEFORE_RESEED 1600000 + +struct arc4_stream { + unsigned char i; + unsigned char j; + unsigned char s[256]; +}; + +static int rs_initialized; +static struct arc4_stream rs; +static pid_t arc4_stir_pid; +static int arc4_count; +static int arc4_seeded_ok; + +static inline unsigned char arc4_getbyte(void); + +static inline void +arc4_init(void) +{ + int n; + + for (n = 0; n < 256; n++) + rs.s[n] = n; + rs.i = 0; + rs.j = 0; +} + +static inline void +arc4_addrandom(const unsigned char *dat, int datlen) +{ + int n; + unsigned char si; + + rs.i--; + for (n = 0; n < 256; n++) { + rs.i = (rs.i + 1); + si = rs.s[rs.i]; + rs.j = (rs.j + si + dat[n % datlen]); + rs.s[rs.i] = rs.s[rs.j]; + rs.s[rs.j] = si; + } + rs.j = rs.i; +} + +#ifndef WIN32 +static ssize_t +read_all(int fd, unsigned char *buf, size_t count) +{ + size_t numread = 0; + ssize_t result; + + while (numread < count) { + result = read(fd, buf+numread, count-numread); + if (result<0) + return -1; + else if (result == 0) + break; + numread += result; + } + + return (ssize_t)numread; +} +#endif + +/* This is adapted from Tor's crypto_seed_rng() */ +static int +arc4_seed(void) +{ + unsigned char buf[ADD_ENTROPY]; + + /* local variables */ +#ifdef WIN32 + static int provider_set = 0; + static HCRYPTPROV provider; +#else + static const char *filenames[] = { + "/dev/srandom", "/dev/urandom", "/dev/random", NULL + }; + int fd, i; + size_t n; +#endif + +#ifdef WIN32 + if (!provider_set) { + if (!CryptAcquireContext(&provider, NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT)) { + if ((unsigned long)GetLastError() != (unsigned long)NTE_BAD_KEYSET) + return -1; + } + provider_set = 1; + } + if (!CryptGenRandom(provider, sizeof(buf), buf)) + return -1; + arc4_addrandom(buf, sizeof(buf)); + memset(buf, 0, sizeof(buf)); + arc4_seeded_ok = 1; + return 0; +#else + for (i = 0; filenames[i]; ++i) { + fd = open(filenames[i], O_RDONLY, 0); + if (fd<0) + continue; + n = read_all(fd, buf, sizeof(buf)); + close(fd); + if (n != sizeof(buf)) + return -1; + arc4_addrandom(buf, sizeof(buf)); + memset(buf, 0, sizeof(buf)); + arc4_seeded_ok = 1; + return 0; + } + + return -1; +#endif +} + +static void +arc4_stir(void) +{ + int i; + + if (!rs_initialized) { + arc4_init(); + rs_initialized = 1; + } + + arc4_seed(); + + /* + * Discard early keystream, as per recommendations in + * "Weaknesses in the Key Scheduling Algorithm of RC4" by + * Scott Fluhrer, Itsik Mantin, and Adi Shamir. + * http://www.wisdom.weizmann.ac.il/~itsik/RC4/Papers/Rc4_ksa.ps + * + * Ilya Mironov's "(Not So) Random Shuffles of RC4" suggests that + * we drop at least 2*256 bytes, with 12*256 as a conservative + * value. + * + * RFC4345 says to drop 6*256. + * + * At least some versions of this code drop 4*256, in a mistaken + * belief that "words" in the Fluhrer/Mantin/Shamir paper refers + * to processor words. + * + * We add another sect to the cargo cult, and choose 12*256. + */ + for (i = 0; i < 12*256; i++) + (void)arc4_getbyte(); + arc4_count = BYTES_BEFORE_RESEED; +} + +static void +arc4_stir_if_needed(void) +{ + pid_t pid = getpid(); + + if (arc4_count <= 0 || !rs_initialized || arc4_stir_pid != pid) + { + arc4_stir_pid = pid; + arc4_stir(); + } +} + +static inline unsigned char +arc4_getbyte(void) +{ + unsigned char si, sj; + + rs.i = (rs.i + 1); + si = rs.s[rs.i]; + rs.j = (rs.j + si); + sj = rs.s[rs.j]; + rs.s[rs.i] = sj; + rs.s[rs.j] = si; + return (rs.s[(si + sj) & 0xff]); +} + +static inline unsigned int +arc4_getword(void) +{ + unsigned int val; + + val = arc4_getbyte() << 24; + val |= arc4_getbyte() << 16; + val |= arc4_getbyte() << 8; + val |= arc4_getbyte(); + + return val; +} + +#ifndef ARC4RANDOM_NOSTIR +ARC4RANDOM_EXPORT int +arc4random_stir(void) +{ + int val; + _ARC4_LOCK(); + val = arc4_stir(); + _ARC4_UNLOCK(); + return val; +} +#endif + +#ifndef ARC4RANDOM_NOADDRANDOM +ARC4RANDOM_EXPORT void +arc4random_addrandom(const unsigned char *dat, int datlen) +{ + int j; + _ARC4_LOCK(); + if (!rs_initialized) + arc4_stir(); + for (j = 0; j < datlen; j += 256) { + /* arc4_addrandom() ignores all but the first 256 bytes of + * its input. We want to make sure to look at ALL the + * data in 'dat', just in case the user is doing something + * crazy like passing us all the files in /var/log. */ + arc4_addrandom(dat + j, datlen - j); + } + _ARC4_UNLOCK(); +} +#endif + +#ifndef ARC4RANDOM_NORANDOM +ARC4RANDOM_EXPORT unsigned int +arc4random(void) +{ + unsigned int val; + _ARC4_LOCK(); + arc4_count -= 4; + arc4_stir_if_needed(); + val = arc4_getword(); + _ARC4_UNLOCK(); + return val; +} +#endif + +ARC4RANDOM_EXPORT void +arc4random_buf(void *_buf, size_t n) +{ + unsigned char *buf = _buf; + _ARC4_LOCK(); + arc4_stir_if_needed(); + while (n--) { + if (--arc4_count <= 0) + arc4_stir(); + buf[n] = arc4_getbyte(); + } + _ARC4_UNLOCK(); +} + +#ifndef ARC4RANDOM_NOUNIFORM +/* + * Calculate a uniformly distributed random number less than upper_bound + * avoiding "modulo bias". + * + * Uniformity is achieved by generating new random numbers until the one + * returned is outside the range [0, 2**32 % upper_bound). This + * guarantees the selected random number will be inside + * [2**32 % upper_bound, 2**32) which maps back to [0, upper_bound) + * after reduction modulo upper_bound. + */ +ARC4RANDOM_EXPORT unsigned int +arc4random_uniform(unsigned int upper_bound) +{ + unsigned int r, min; + + if (upper_bound < 2) + return 0; + +#if (UINT_MAX > 0xffffffffUL) + min = 0x100000000UL % upper_bound; +#else + /* Calculate (2**32 % upper_bound) avoiding 64-bit math */ + if (upper_bound > 0x80000000) + min = 1 + ~upper_bound; /* 2**32 - upper_bound */ + else { + /* (2**32 - (x * 2)) % x == 2**32 % x when x <= 2**31 */ + min = ((0xffffffff - (upper_bound * 2)) + 1) % upper_bound; + } +#endif + + /* + * This could theoretically loop forever but each retry has + * p > 0.5 (worst case, usually far better) of selecting a + * number inside the range we need, so it should rarely need + * to re-roll. + */ + for (;;) { + r = arc4random(); + if (r >= min) + break; + } + + return r % upper_bound; +} +#endif diff --git a/configure.in b/configure.in index 26ef40ae..7f911e5a 100644 --- a/configure.in +++ b/configure.in @@ -244,14 +244,6 @@ AC_CHECK_FUNC(gethostbyname_r, [ AC_CHECK_SIZEOF(long) -if test "x$ac_cv_func_arc4random" = "xyes" ; then - AC_DEFINE(DNS_USE_ARC4RANDOM_FOR_ID, 1, [Define if we should use arc4random to generate dns transation IDs]) -elif test "x$ac_cv_func_clock_gettime" = "xyes"; then - AC_DEFINE(DNS_USE_CPU_CLOCK_FOR_ID, 1, [Define if we should use clock_gettime to generate dns transation IDs]) -else - AC_DEFINE(DNS_USE_GETTIMEOFDAY_FOR_ID, 1, [Define if s no secure id variant is available]) -fi - AC_MSG_CHECKING(for F_SETFD in fcntl.h) AC_EGREP_CPP(yes, [ diff --git a/evdns.c b/evdns.c index d18d64c9..7a010399 100644 --- a/evdns.c +++ b/evdns.c @@ -37,45 +37,6 @@ #include #include "event-config.h" -#ifdef _EVENT_DNS_USE_FTIME_FOR_ID -#include -#endif - -#ifndef _EVENT_DNS_USE_CPU_CLOCK_FOR_ID -#ifndef _EVENT_DNS_USE_GETTIMEOFDAY_FOR_ID -#ifndef _EVENT_DNS_USE_OPENSSL_FOR_ID -#ifndef _EVENT_DNS_USE_FTIME_FOR_ID -#ifndef _EVENT_DNS_USE_ARC4RANDOM_FOR_ID -#error Must configure at least one id generation method. -#error Please see the documentation. -#endif -#endif -#endif -#endif -#endif - -/* #define _POSIX_C_SOURCE 200507 */ -#define _GNU_SOURCE -/* for strtok_r */ -#define _REENTRANT - -#ifdef _EVENT_DNS_USE_CPU_CLOCK_FOR_ID -#ifdef _EVENT_DNS_USE_OPENSSL_FOR_ID -#error Multiple id options selected -#endif -#ifdef _EVENT_DNS_USE_GETTIMEOFDAY_FOR_ID -#error Multiple id options selected -#endif -#include -#endif - -#ifdef _EVENT_DNS_USE_OPENSSL_FOR_ID -#ifdef _EVENT_DNS_USE_GETTIMEOFDAY_FOR_ID -#error Multiple id options selected -#endif -#include -#endif - #ifndef _FORTIFY_SOURCE #define _FORTIFY_SOURCE 3 #endif @@ -1213,97 +1174,15 @@ err: #undef GET8 } -static u16 -default_transaction_id_fn(void) -{ - u16 trans_id; -#ifdef _EVENT_DNS_USE_CPU_CLOCK_FOR_ID - struct timespec ts; - static int clkid = -1; - if (clkid == -1) { - clkid = CLOCK_REALTIME; -#ifdef CLOCK_MONOTONIC - if (clock_gettime(CLOCK_MONOTONIC, &ts) != -1) - clkid = CLOCK_MONOTONIC; -#endif - } - if (clock_gettime(clkid, &ts) == -1) - event_err(1, "clock_gettime"); - trans_id = ts.tv_nsec & 0xffff; -#endif - -#ifdef _EVENT_DNS_USE_FTIME_FOR_ID - struct _timeb tb; - _ftime(&tb); - trans_id = tb.millitm & 0xffff; -#endif - -#ifdef _EVENT_DNS_USE_GETTIMEOFDAY_FOR_ID - struct timeval tv; - evutil_gettimeofday(&tv, NULL); - trans_id = tv.tv_usec & 0xffff; -#endif - -#ifdef _EVENT_DNS_USE_OPENSSL_FOR_ID - if (RAND_pseudo_bytes((u8 *) &trans_id, 2) == -1) { - /* in the case that the RAND call fails we used to back */ - /* down to using gettimeofday. */ - /* - struct timeval tv; - gettimeofday(&tv, NULL); - trans_id = tv.tv_usec & 0xffff; - */ - event_errx("RAND_pseudo_bytes failed!"); - } -#endif - -#ifdef _EVENT_DNS_USE_ARC4RANDOM_FOR_ID - trans_id = arc4random() & 0xffff; -#endif - - return trans_id; -} - -static ev_uint16_t (*trans_id_function)(void) = default_transaction_id_fn; - -static void -default_random_bytes_fn(char *buf, size_t n) -{ - unsigned i; - for (i = 0; i < n; i += 2) { - u16 tid = trans_id_function(); - buf[i] = (tid >> 8) & 0xff; - if (i+1> 3] & (1<<(i & 7)))) @@ -3720,6 +3600,12 @@ evdns_base_new(struct event_base *event_base, int initialize_nameservers) { struct evdns_base *base; + if (evutil_secure_rng_init() < 0) { + log(EVDNS_LOG_WARN, "Unable to seed random number generator; " + "DNS can't run."); + return NULL; + } + /* Give the evutil library a hook into its evdns-enabled * functionality. We can't just call evdns_getaddrinfo directly or * else libevent-core will depend on libevent-extras. */ diff --git a/evutil.c b/evutil.c index 7fd73de6..ac89d47c 100644 --- a/evutil.c +++ b/evutil.c @@ -1859,9 +1859,9 @@ long _evutil_weakrand(void) { #ifdef WIN32 - return rand(); + return rand(); #else - return random(); + return random(); #endif } diff --git a/evutil_rand.c b/evutil_rand.c new file mode 100644 index 00000000..8639f092 --- /dev/null +++ b/evutil_rand.c @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2007-2010 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* This file has our secure PRNG code. On platforms that have arc4random(), + * we just use that. Otherwise, we include arc4random.c as a bunch of static + * functions, and wrap it lightly. We don't expose the arc4random*() APIs + * because A) they aren't in our namespace, and B) it's not nice to name your + * APIs after their implementations. We keep them in a separate file + * so that other people can rip it out and use it for whatever. + */ + +#include "event-config.h" + +#include "util-internal.h" +#include "evthread-internal.h" + +#ifdef _EVENT_HAVE_ARC4RANDOM +#include +int +evutil_secure_rng_init(void) +{ + return 0; +} +#else /* !_EVENT_HAVE_ARC4RANDOM { */ + +#ifdef _EVENT_ssize_t +#define ssize_t _EVENT_SSIZE_t +#endif +#define ARC4RANDOM_EXPORT static +#define _ARC4_LOCK() EVLOCK_LOCK(arc4rand_lock, 0) +#define _ARC4_UNLOCK() EVLOCK_UNLOCK(arc4rand_lock, 0) +static void *arc4rand_lock; + +#define ARC4RANDOM_NOSTIR +#define ARC4RANDOM_NORANDOM +#define ARC4RANDOM_NOUNIFORM + +#include "./arc4random.c" + +int +evutil_secure_rng_init(void) +{ + int val; + if (!arc4rand_lock) { + EVTHREAD_ALLOC_LOCK(arc4rand_lock, 0); + } + + _ARC4_LOCK(); + if (!arc4_seeded_ok) + arc4_stir(); + val = arc4_seeded_ok ? 0 : -1; + _ARC4_UNLOCK(); + return val; +} + +#endif /* } !_EVENT_HAVE_ARC4RANDOM */ + +void +evutil_secure_rng_get_bytes(void *buf, size_t n) +{ + arc4random_buf(buf, n); +} + +void +evutil_secure_rng_add_bytes(const char *buf, size_t n) +{ + arc4random_addrandom((const unsigned char*)buf, n); +} + diff --git a/include/event2/dns.h b/include/event2/dns.h index 392d6459..3adabf70 100644 --- a/include/event2/dns.h +++ b/include/event2/dns.h @@ -58,31 +58,6 @@ * (see http://www.imperialviolet.org/page25.html#e498). Otherwise, * please continue. * - * This code is based on Libevent and you must call event_init before - * any of the APIs in this file. You must also seed the OpenSSL random - * source if you are using OpenSSL for ids (see below). - * - * This library is designed to be included and shipped with your source - * code. You statically link with it. You should also test for the - * existence of strtok_r and define HAVE_STRTOK_R if you have it. - * - * The DNS protocol requires a good source of id numbers and these - * numbers should be unpredictable for spoofing reasons. There are - * three methods for generating them here and you must define exactly - * one of them. In increasing order of preference: - * - * DNS_USE_GETTIMEOFDAY_FOR_ID: - * Using the bottom 16 bits of the usec result from gettimeofday. This - * is a pretty poor solution but should work anywhere. - * DNS_USE_CPU_CLOCK_FOR_ID: - * Using the bottom 16 bits of the nsec result from the CPU's time - * counter. This is better, but may not work everywhere. Requires - * POSIX realtime support and you'll need to link against -lrt on - * glibc systems at least. - * DNS_USE_OPENSSL_FOR_ID: - * Uses the OpenSSL RAND_bytes call to generate the data. You must - * have seeded the pool before making any calls to this library. - * * The library keeps track of the state of nameservers and will avoid * them when they go down. Otherwise it will round robin between them. * @@ -513,6 +488,9 @@ void evdns_set_log_fn(evdns_debug_log_fn_type fn); is bad for security. @param fn the new callback, or NULL to use the default. + + NOTE: This function has no effect in Libevent 2.0.4-alpha and later, + since Libevent now provides its own secure RNG. */ void evdns_set_transaction_id_fn(ev_uint16_t (*fn)(void)); @@ -521,6 +499,9 @@ void evdns_set_transaction_id_fn(ev_uint16_t (*fn)(void)); the same function as passed to evdns_set_transaction_id_fn to generate bytes two at a time. If a function is provided here, it's also used to generate transaction IDs. + + NOTE: This function has no effect in Libevent 2.0.4-alpha and later, + since Libevent now provides its own secure RNG. */ void evdns_set_random_bytes_fn(void (*fn)(char *, size_t)); diff --git a/include/event2/util.h b/include/event2/util.h index 0ecef033..8de9b28b 100644 --- a/include/event2/util.h +++ b/include/event2/util.h @@ -525,6 +525,18 @@ void evutil_freeaddrinfo(struct evutil_addrinfo *ai); const char *evutil_gai_strerror(int err); +/* Generate n bytes of secure pseudorandom data, and store them in buf. + * + * By default, Libevent uses an ARC4-based random number generator, seeded + * using the platform's entropy source (/dev/urandom on Unix-like systems; + * CryptGenRandom on Windows). + */ +void evutil_secure_rng_get_bytes(void *buf, size_t n); + +int evutil_secure_rng_init(void); + +void evutil_secure_rng_add_bytes(const char *dat, size_t datlen); + #ifdef __cplusplus } #endif diff --git a/test/regress_dns.c b/test/regress_dns.c index 9a8d4735..d99a9d36 100644 --- a/test/regress_dns.c +++ b/test/regress_dns.c @@ -704,6 +704,7 @@ end: evdns_close_server_port(port2); } +#if 0 static void dumb_bytes_fn(char *p, size_t n) { @@ -713,6 +714,7 @@ dumb_bytes_fn(char *p, size_t n) for(i=0;i