From: Willy Tarreau Date: Fri, 6 Mar 2020 17:57:15 +0000 (+0100) Subject: BUG/MEDIUM: random: initialize the random pool a bit better X-Git-Tag: v2.2-dev4~14 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=6c3a681bd61a3fd54de8fe644db17b9676939396;p=thirdparty%2Fhaproxy.git BUG/MEDIUM: random: initialize the random pool a bit better Since the UUID sample fetch was created, some people noticed that in certain virtualized environments they manage to get exact same UUIDs on different instances started exactly at the same moment. It turns out that the randoms were only initialized to spread the health checks originally, not to provide "clean" randoms. This patch changes this and collects more randomness from various sources, including existing randoms, /dev/urandom when available, RAND_bytes() when OpenSSL is available, as well as the timing for such operations, then applies a SHA1 on all this to keep a 160 bits random seed available, 32 of which are passed to srandom(). It's worth mentioning that there's no clean way to pass more than 32 bits to srandom() as even initstate() provides an opaque state that must absolutely not be tampered with since known implementations contain state information. At least this allows to have up to 4 billion different sequences from the boot, which is not that bad. Note that the thread safety was still not addressed, which is another issue for another patch. This must be backported to all versions containing the UUID sample fetch function, i.e. as far as 2.0. --- diff --git a/include/types/global.h b/include/types/global.h index aaff2a4847..82f1011558 100644 --- a/include/types/global.h +++ b/include/types/global.h @@ -239,6 +239,7 @@ extern int master; /* 1 if in master, 0 otherwise */ extern unsigned int rlim_fd_cur_at_boot; extern unsigned int rlim_fd_max_at_boot; extern int atexit_flag; +extern unsigned char boot_seed[20]; // per-boot random seed (160 bits initially) /* bit values to go with "warned" above */ /* unassigned : 0x00000001 (previously: WARN_BLOCK_DEPRECATED) */ diff --git a/src/haproxy.c b/src/haproxy.c index c7905a52e4..ef9010f958 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -77,6 +77,8 @@ #include #endif +#include + #include #include #include @@ -88,6 +90,7 @@ #include #include #include +#include #include #include #include @@ -233,6 +236,9 @@ int master = 0; /* 1 if in master, 0 if in child */ unsigned int rlim_fd_cur_at_boot = 0; unsigned int rlim_fd_max_at_boot = 0; +/* per-boot randomness */ +unsigned char boot_seed[20]; /* per-boot random seed (160 bits initially) */ + struct mworker_proc *proc_self = NULL; static void *run_thread_poll_loop(void *data); @@ -1356,6 +1362,90 @@ static char **copy_argv(int argc, char **argv) return newargv; } + +/* Performs basic random seed initialization. The main issue with this is that + * srandom_r() only takes 32 bits and purposely provides a reproducible sequence, + * which means that there will only be 4 billion possible random sequences once + * srandom() is called, regardless of the internal state. Not calling it is + * even worse as we'll always produce the same randoms sequences. What we do + * here is to create an initial sequence from various entropy sources, hash it + * using SHA1 and keep the resulting 160 bits available globally. + * + * We initialize the current process with the first 32 bits before starting the + * polling loop, where all this will be changed to have process specific and + * thread specific sequences. + */ +static void ha_random_boot(char *const *argv) +{ + unsigned char message[256]; + unsigned char *m = message; + struct timeval tv; + blk_SHA_CTX ctx; + unsigned long l; + int fd; + int i; + + /* start with current time as pseudo-random seed */ + gettimeofday(&tv, NULL); + write_u32(m, tv.tv_sec); m += 4; + write_u32(m, tv.tv_usec); m += 4; + + /* PID and PPID add some OS-based randomness */ + write_u16(m, getpid()); m += 2; + write_u16(m, getppid()); m += 2; + + /* take up to 160 bits bytes from /dev/urandom if available (non-blocking) */ + fd = open("/dev/urandom", O_RDONLY); + if (fd >= 0) { + i = read(fd, m, 20); + if (i > 0) + m += i; + close(fd); + } + + /* take up to 160 bits bytes from openssl (non-blocking) */ +#ifdef USE_OPENSSL + if (RAND_bytes(m, 20) == 1) + m += 20; +#endif + + /* take 160 bits from existing random in case it was already initialized */ + for (i = 0; i < 5; i++) { + write_u32(m, random()); + m += 4; + } + + /* stack address (benefit form operating system's ASLR) */ + l = (unsigned long)&m; + memcpy(m, &l, sizeof(l)); m += sizeof(l); + + /* argv address (benefit form operating system's ASLR) */ + l = (unsigned long)&argv; + memcpy(m, &l, sizeof(l)); m += sizeof(l); + + /* use tv_usec again after all the operations above */ + gettimeofday(&tv, NULL); + write_u32(m, tv.tv_usec); m += 4; + + /* + * At this point, ~84-92 bytes have been used + */ + + /* finish with the hostname */ + strncpy((char *)m, hostname, message + sizeof(message) - m); + m += strlen(hostname); + + /* total message length */ + l = m - message; + + memset(&ctx, 0, sizeof(ctx)); + blk_SHA1_Init(&ctx); + blk_SHA1_Update(&ctx, message, l); + blk_SHA1_Final(boot_seed, &ctx); + + srandom(read_u32(boot_seed)); +} + /* considers splicing proxies' maxconn, computes the ideal global.maxpipes * setting, and returns it. It may return -1 meaning "unlimited" if some * unlimited proxies have been found and the global.maxconn value is not yet @@ -1516,7 +1606,7 @@ static void init(int argc, char **argv) tv_update_date(-1,-1); start_date = now; - srandom(now_ms - getpid()); + ha_random_boot(argv); if (init_acl() != 0) exit(1);