diff options
author | Glenn Strauss <gstrauss@gluelogic.com> | 2016-10-13 03:10:10 -0400 |
---|---|---|
committer | Glenn Strauss <gstrauss@gluelogic.com> | 2016-10-15 23:28:09 -0400 |
commit | 7f4e156e5f12093071e8746cd9702827a66952ed (patch) | |
tree | c46f12be6f48cd5c2e2ba7f8dfe7768f80a7e699 | |
parent | b8b38f306790a48db7e0e7a61ba6bb18114eba05 (diff) | |
download | lighttpd-git-7f4e156e5f12093071e8746cd9702827a66952ed.tar.gz |
[core] rand.[ch] to use better RNGs when available
prefer RAND_pseudo_bytes() (openssl), arc4random() or jrand48(),
if available, over rand()
These are not necessarily cryptographically secure, but should be better
than rand()
-rw-r--r-- | SConstruct | 5 | ||||
-rw-r--r-- | configure.ac | 6 | ||||
-rw-r--r-- | src/CMakeLists.txt | 9 | ||||
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/SConscript | 1 | ||||
-rw-r--r-- | src/base.h | 1 | ||||
-rw-r--r-- | src/mod_auth.c | 5 | ||||
-rw-r--r-- | src/mod_usertrack.c | 3 | ||||
-rw-r--r-- | src/rand.c | 190 | ||||
-rw-r--r-- | src/rand.h | 10 | ||||
-rw-r--r-- | src/server.c | 22 |
11 files changed, 232 insertions, 22 deletions
@@ -174,6 +174,7 @@ if 1: fcntl.h getopt.h inttypes.h + linux/random.h netinet/in.h poll.h pwd.h @@ -213,7 +214,9 @@ if 1: gethostbyname poll epoll_ctl getrlimit chroot \ getuid select signal pathconf madvise prctl\ writev sigaction sendfile64 send_file kqueue port_create localtime_r posix_fadvise issetugid inet_pton \ - memset_s explicit_bzero clock_gettime')) + memset_s explicit_bzero clock_gettime \ + getentropy arc4random jrand48')) + checkFunc(autoconf, getrandom, linux/random.h) checkTypes(autoconf, Split('pid_t size_t off_t')) diff --git a/configure.ac b/configure.ac index f5e2efa9..3842d31e 100644 --- a/configure.ac +++ b/configure.ac @@ -762,7 +762,11 @@ AC_CHECK_FUNCS([dup2 getcwd inet_ntoa inet_ntop inet_pton issetugid memset mmap gethostbyname poll epoll_ctl getrlimit chroot \ getuid select signal pathconf madvise posix_fadvise posix_madvise \ writev sigaction sendfile64 send_file kqueue port_create localtime_r gmtime_r \ - memset_s explicit_bzero clock_gettime]) + memset_s explicit_bzero clock_gettime \ + getentropy arc4random jrand48]) +AC_CHECK_HEADERS([linux/random.h],[ + AC_CHECK_FUNC([getrandom], AC_DEFINE([HAVE_GETRANDOM], [1], [getrandom])) +]) AC_MSG_CHECKING(if weak symbols are supported) AC_LINK_IFELSE([AC_LANG_PROGRAM([[ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d788112e..3d01272a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -120,16 +120,24 @@ set(CMAKE_EXTRA_INCLUDE_FILES sys/socket.h) check_type_size(socklen_t HAVE_SOCKLEN_T) set(CMAKE_EXTRA_INCLUDE_FILES) +check_include_files(linux/random.h HAVE_LINUX_RANDOM_H) +set(CMAKE_EXTRA_INCLUDE_FILES linux/random.h) +check_function_exists(getrandom HAVE_GETRANDOM) +set(CMAKE_EXTRA_INCLUDE_FILES) + check_type_size(long SIZEOF_LONG) check_type_size(off_t SIZEOF_OFF_T) +check_function_exists(arc4random HAVE_ARC4RANDOM) check_function_exists(chroot HAVE_CHROOT) check_function_exists(epoll_ctl HAVE_EPOLL_CTL) check_function_exists(fork HAVE_FORK) +check_function_exists(getentropy HAVE_GETENTROPY) check_function_exists(getrlimit HAVE_GETRLIMIT) check_function_exists(getuid HAVE_GETUID) check_function_exists(gmtime_r HAVE_GMTIME_R) check_function_exists(inet_ntop HAVE_INET_NTOP) +check_function_exists(jrand48 HAVE_JRAND48) check_function_exists(kqueue HAVE_KQUEUE) check_function_exists(localtime_r HAVE_LOCALTIME_R) check_function_exists(lstat HAVE_LSTAT) @@ -519,6 +527,7 @@ set(COMMON_SRC network_write.c network_linux_sendfile.c network_freebsd_sendfile.c network_solaris_sendfilev.c network_openssl.c + rand.c status_counter.c safe_memclear.c network_darwin_sendfile.c ) diff --git a/src/Makefile.am b/src/Makefile.am index dfdc8b27..5efa0347 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -82,6 +82,7 @@ common_src=base64.c buffer.c log.c \ network_write_mmap.c network_write_no_mmap.c \ network_freebsd_sendfile.c network_writev.c \ network_solaris_sendfilev.c network_openssl.c \ + rand.c \ splaytree.c status_counter.c \ safe_memclear.c network_darwin_sendfile.c @@ -324,6 +325,7 @@ hdr = server.h base64.h buffer.h network.h log.h keyvalue.h \ network_backends.h configfile.h \ mod_ssi.h mod_ssi_expr.h inet_ntop_cache.h \ configparser.h mod_ssi_exprparser.h \ + rand.h \ sys-endian.h sys-mmap.h sys-socket.h mod_cml.h mod_cml_funcs.h \ safe_memclear.h splaytree.h proc_open.h status_counter.h \ mod_magnet_cache.h \ diff --git a/src/SConscript b/src/SConscript index fca71503..ec039691 100644 --- a/src/SConscript +++ b/src/SConscript @@ -60,6 +60,7 @@ common_src = Split("base64.c buffer.c log.c \ network_write.c network_linux_sendfile.c \ network_freebsd_sendfile.c \ network_solaris_sendfilev.c network_openssl.c \ + rand.c \ status_counter.c safe_memclear.c network_darwin_sendfile.c \ ") @@ -641,7 +641,6 @@ typedef struct server { time_t startup_ts; char entropy[8]; /* from /dev/[u]random if possible, otherwise rand() */ - char is_real_entropy; /* whether entropy is from /dev/[u]random */ buffer *ts_debug_str; buffer *ts_date_str; diff --git a/src/mod_auth.c b/src/mod_auth.c index 3dc08416..4d8723d8 100644 --- a/src/mod_auth.c +++ b/src/mod_auth.c @@ -416,6 +416,7 @@ int mod_auth_plugin_init(plugin *p) { #include "response.h" #include "base64.h" #include "md5.h" +#include "rand.h" static handler_t mod_auth_send_400_bad_request(server *srv, connection *con) { UNUSED(srv); @@ -769,7 +770,7 @@ static handler_t mod_auth_check_digest(server *srv, connection *con, void *p_d, return mod_auth_send_401_unauthorized_digest(srv, con, require->realm, 0); } - /* check age of nonce. Note that rand() is used in nonce generation + /* check age of nonce. Note, random data is used in nonce generation * in mod_auth_send_401_unauthorized_digest(). If that were replaced * with nanosecond time, then nonce secret would remain unique enough * for the purposes of Digest auth, and would be reproducible (and @@ -820,7 +821,7 @@ static handler_t mod_auth_send_401_unauthorized_digest(server *srv, connection * li_itostrn(hh, sizeof(hh), srv->cur_ts); li_MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh)); li_MD5_Update(&Md5Ctx, (unsigned char *)srv->entropy, sizeof(srv->entropy)); - li_itostrn(hh, sizeof(hh), rand()); + li_itostrn(hh, sizeof(hh), li_rand()); li_MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh)); li_MD5_Final(h, &Md5Ctx); diff --git a/src/mod_usertrack.c b/src/mod_usertrack.c index 05ffe2a6..875e0393 100644 --- a/src/mod_usertrack.c +++ b/src/mod_usertrack.c @@ -3,6 +3,7 @@ #include "base.h" #include "log.h" #include "buffer.h" +#include "rand.h" #include "plugin.h" @@ -230,7 +231,7 @@ URIHANDLER_FUNC(mod_usertrack_uri_handler) { li_itostrn(hh, sizeof(hh), srv->cur_ts); li_MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh)); li_MD5_Update(&Md5Ctx, (unsigned char *)srv->entropy, sizeof(srv->entropy)); - li_itostrn(hh, sizeof(hh), rand()); + li_itostrn(hh, sizeof(hh), li_rand()); li_MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh)); li_MD5_Final(h, &Md5Ctx); diff --git a/src/rand.c b/src/rand.c new file mode 100644 index 00000000..c7924444 --- /dev/null +++ b/src/rand.c @@ -0,0 +1,190 @@ +#include "first.h" + +#include "rand.h" +#include "base.h" +#include "fdevent.h" +#include "safe_memclear.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#ifdef USE_OPENSSL +#include <openssl/rand.h> +#endif +#ifdef HAVE_LINUX_RANDOM_H +#include <sys/syscall.h> +#include <linux/random.h> +#endif +#ifdef RNDGETENTCNT +#include <sys/ioctl.h> +#endif + +/* Take some reasonable steps to attempt to *seed* random number generators with + * cryptographically random data. Some of these initialization routines may + * block, and are intended to be called only at startup in lighttpd, or + * immediately after fork() to start lighttpd workers. + * + * Note: results from li_rand() are not necessarily cryptographically random. + * + * https://wiki.openssl.org/index.php/Random_Numbers + * https://wiki.openssl.org/index.php/Random_fork-safety + * + * openssl random number generators are not thread-safe by default + * https://wiki.openssl.org/index.php/Manual:Threads(3) + * + * RFE: add more paranoid checks from the following to improve confidence: + * http://insanecoding.blogspot.co.uk/2014/05/a-good-idea-with-bad-usage-devurandom.html + * RFE: retry on EINTR + * RFE: check RAND_status() + */ + +static int li_getentropy (void *buf, size_t buflen) +{ + #ifdef HAVE_GETENTROPY + return getentropy(buf, buflen); + #else + /*(see NOTES section in 'man getrandom' on Linux)*/ + #if defined(HAVE_GETRANDOM) || defined(SYS_getrandom) + if (buflen <= 256) { + #ifdef HAVE_GETRANDOM /*(not implemented in glibc yet)*/ + int num = getrandom(buf, buflen, 0); + #elif defined(SYS_getrandom) + /* https://lwn.net/Articles/605828/ */ + /* https://bbs.archlinux.org/viewtopic.php?id=200039 */ + int num = (int)syscall(SYS_getrandom, buf, buflen, 0); + #endif + if (num == (int)buflen) return 0; + if (num < 0) return num; /* -1 */ + } + #else + UNUSED(buf); + UNUSED(buflen); + #endif + errno = EIO; + return -1; + #endif +} + +static int li_rand_device_bytes (unsigned char *buf, int num) +{ + /* randomness from these devices is cryptographically strong, + * unless /dev/urandom is low on entropy */ + + static const char * const devices[] = { + #ifdef __OpenBSD__ + "/dev/arandom", + #endif + "/dev/urandom", + "/dev/random" + }; + + /* device files might not be available in chroot environment, + * so prefer syscall, if available */ + if (0 == li_getentropy(buf, (size_t)num)) return 1; + + for (unsigned int u = 0; u < sizeof(devices)/sizeof(devices[0]); ++u) { + /*(some systems might have symlink to another device; omit O_NOFOLLOW)*/ + int fd = fdevent_open_cloexec(devices[u], O_RDONLY, 0); + if (fd >= 0) { + ssize_t rd = 0; + #ifdef RNDGETENTCNT + int entropy; + if (0 == ioctl(fd, RNDGETENTCNT, &entropy) && entropy >= num*8) + #endif + rd = read(fd, buf, (size_t)num); + close(fd); + if (rd == num) { + return 1; + } + } + } + + return 0; +} + +static unsigned short xsubi[3]; + +void li_rand_reseed (void) +{ + /* (intended to be called at init and after fork() in order to re-seed PRNG + * so that forked children, grandchildren, etc do not share PRNG seed) + * https://github.com/ramsey/uuid/issues/80 + * https://www.agwa.name/blog/post/libressls_prng_is_unsafe_on_linux + * (issue in early version of libressl has since been fixed) + * https://github.com/libressl-portable/portable/commit/32d9eeeecf4e951e1566d5f4a42b36ea37b60f35 + */ + unsigned int u; + if (1 == li_rand_device_bytes((unsigned char *)xsubi, (int)sizeof(xsubi))) { + u = ((unsigned int)xsubi[0] << 16) | xsubi[1]; + srand(u); /*(initialize just in case rand() used elsewhere)*/ + } + else { + #ifdef HAVE_ARC4RANDOM + srand(arc4random()); /*(initialize just in case rand() used elsewhere)*/ + arc4random_buf(xsubi, sizeof(xsubi)); + #else + /* NOTE: not cryptographically random !!! */ + srand((unsigned int)(time(NULL) ^ getpid())); + for (u = 0; u < sizeof(unsigned short); ++u) + xsubi[u] = (unsigned short)(rand() & 0xFFFF); + #endif + } + #ifdef USE_OPENSSL + RAND_poll(); + RAND_seed(xsubi, (int)sizeof(xsubi)); + #endif +} + +int li_rand (void) +{ + /* randomness *is not* cryptographically strong */ + /* (attempt to use better mechanisms to replace the more portable rand()) */ + #ifdef USE_OPENSSL + int i; + if (-1 != RAND_pseudo_bytes((unsigned char *)&i, sizeof(i))) return i; + #endif + #ifdef HAVE_ARC4RANDOM + return (int)arc4random(); + #endif + #ifdef HAVE_JRAND48 + /*(FYI: jrand48() reentrant, but use of file-scoped static xsubi[] is not)*/ + return (int)jrand48(xsubi); + #else + return rand(); + #endif +} + +int li_rand_bytes (unsigned char *buf, int num) +{ + #ifdef USE_OPENSSL + int rc = RAND_bytes(buf, num); + if (-1 != rc) { + return rc; + } + #endif + if (1 == li_rand_device_bytes(buf, num)) { + return 1; + } + else { + /* NOTE: not cryptographically random !!! */ + for (int i = 0; i < num; ++i) + buf[i] = li_rand() & 0xFF; + /*(openssl RAND_pseudo_bytes rc for non-cryptographically random data)*/ + return 0; + } +} + +void li_rand_cleanup (void) +{ + #ifdef USE_OPENSSL + RAND_cleanup(); + #endif + safe_memclear(xsubi, sizeof(xsubi)); +} diff --git a/src/rand.h b/src/rand.h new file mode 100644 index 00000000..c3bac17b --- /dev/null +++ b/src/rand.h @@ -0,0 +1,10 @@ +#ifndef LI_RAND_H_ +#define LI_RAND_H_ +#include "first.h" + +int li_rand (void); +void li_rand_reseed (void); +int li_rand_bytes (unsigned char *buf, int num); +void li_rand_cleanup (void); + +#endif diff --git a/src/server.c b/src/server.c index 68042532..01f10d87 100644 --- a/src/server.c +++ b/src/server.c @@ -5,6 +5,7 @@ #include "network.h" #include "log.h" #include "keyvalue.h" +#include "rand.h" #include "response.h" #include "request.h" #include "chunk.h" @@ -202,8 +203,6 @@ static int daemonize(void) { static server *server_init(void) { int i; - FILE *frandom = NULL; - server *srv = calloc(1, sizeof(*srv)); force_assert(srv); #define CLEAN(x) \ @@ -244,20 +243,8 @@ static server *server_init(void) { srv->mtime_cache[i].str = buffer_init(); } - if ((NULL != (frandom = fopen("/dev/urandom", "rb")) || NULL != (frandom = fopen("/dev/random", "rb"))) - && 1 == fread(srv->entropy, sizeof(srv->entropy), 1, frandom)) { - unsigned int e; - memcpy(&e, srv->entropy, sizeof(e) < sizeof(srv->entropy) ? sizeof(e) : sizeof(srv->entropy)); - srand(e); - srv->is_real_entropy = 1; - } else { - unsigned int j; - srand(time(NULL) ^ getpid()); - srv->is_real_entropy = 0; - for (j = 0; j < sizeof(srv->entropy); j++) - srv->entropy[j] = rand(); - } - if (frandom) fclose(frandom); + li_rand_reseed(); + li_rand_bytes((unsigned char *)srv->entropy, (int)sizeof(srv->entropy)); srv->cur_ts = time(NULL); srv->startup_ts = srv->cur_ts; @@ -404,6 +391,7 @@ static void server_free(server *srv) { EVP_cleanup(); } #endif + li_rand_cleanup(); free(srv); } @@ -1441,6 +1429,8 @@ int main (int argc, char **argv) { pid_fd = -1; } buffer_reset(srv->srvconf.pid_file); + + li_rand_reseed(); } #endif |