diff options
Diffstat (limited to 'src/lib/randgen.c')
-rw-r--r-- | src/lib/randgen.c | 222 |
1 files changed, 222 insertions, 0 deletions
diff --git a/src/lib/randgen.c b/src/lib/randgen.c new file mode 100644 index 0000000..f6b2da9 --- /dev/null +++ b/src/lib/randgen.c @@ -0,0 +1,222 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "randgen.h" +#include <unistd.h> +#include <fcntl.h> + +#ifdef DEBUG +/* For reproducing tests, fall back onto using a simple deterministic PRNG */ +/* Marsaglia's 1999 KISS, de-macro-ified, and with the fixed KISS11 SHR3, + which is clearly what was intended given the "cycle length 2^123" claim. */ +static bool kiss_in_use; +static unsigned int kiss_seed; +static uint32_t kiss_z, kiss_w, kiss_jsr, kiss_jcong; +static void +kiss_init(unsigned int seed) +{ + i_info("Random numbers are PRNG using kiss, as per DOVECOT_SRAND=%u", seed); + kiss_seed = seed; + kiss_jsr = 0x5eed5eed; /* simply musn't be 0 */ + kiss_z = 1 ^ (kiss_w = kiss_jcong = seed); /* w=z=0 is bad, see Rose */ + kiss_in_use = TRUE; +} +static unsigned int +kiss_rand(void) +{ + kiss_z = 36969 * (kiss_z&65535) + (kiss_z>>16); + kiss_w = 18000 * (kiss_w&65535) + (kiss_w>>16); + kiss_jcong = 69069 * kiss_jcong + 1234567; + kiss_jsr^=(kiss_jsr<<13); /* <<17, >>13 gives cycle length 2^28.2 max */ + kiss_jsr^=(kiss_jsr>>17); /* <<13, >>17 gives maximal cycle length */ + kiss_jsr^=(kiss_jsr<<5); + return (((kiss_z<<16) + kiss_w) ^ kiss_jcong) + kiss_jsr; +} +int rand_get_last_seed(unsigned int *seed_r) +{ + if (!kiss_in_use) + return -1; /* not using a deterministic PRNG, seed is irrelevant */ + *seed_r = kiss_seed; + return 0; +} +#endif + +/* get randomness from either getrandom, arc4random or /dev/urandom */ + +#if defined(HAVE_GETRANDOM) && HAVE_DECL_GETRANDOM != 0 +# include <sys/random.h> +# define USE_GETRANDOM +static bool getrandom_present = TRUE; +#elif defined(HAVE_ARC4RANDOM) +# if defined(HAVE_LIBBSD) +# include <bsd/stdlib.h> +# endif +# define USE_ARC4RANDOM +#else +static bool getrandom_present = FALSE; +# define USE_RANDOM_DEV +#endif + +static int init_refcount = 0; +static int urandom_fd = -1; + +#if defined(USE_GETRANDOM) || defined(USE_RANDOM_DEV) +/* Use a small buffer when reading randomness. This is mainly to make small + random reads more efficient, such as i_rand*(). When reading larger amount + of randomness this buffer is bypassed. + + There doesn't seem to be a big difference in Linux system CPU usage when + buffer size is above 16 bytes. Double it just to be safe. Avoid it being + too large anyway so we don't unnecessarily waste CPU and memory. */ +#define RANDOM_READ_BUFFER_SIZE 32 +static unsigned char random_next[RANDOM_READ_BUFFER_SIZE]; +static size_t random_next_pos = 0; +static size_t random_next_size = 0; + +static void random_open_urandom(void) +{ + urandom_fd = open(DEV_URANDOM_PATH, O_RDONLY); + if (urandom_fd == -1) { + if (errno == ENOENT) { + i_fatal("open("DEV_URANDOM_PATH") failed: doesn't exist," + "currently we require it"); + } else { + i_fatal("open("DEV_URANDOM_PATH") failed: %m"); + } + } + fd_close_on_exec(urandom_fd, TRUE); +} + +static inline int random_read(unsigned char *buf, size_t size) +{ + ssize_t ret = 0; +# if defined(USE_GETRANDOM) + if (getrandom_present) { + ret = getrandom(buf, size, 0); + if (ret < 0 && errno == ENOSYS) { + getrandom_present = FALSE; + /* It gets complicated here... While the libc (and its + headers) indicated that getrandom() was available when + we were compiled, the kernel disagreed just now at + runtime. Fall back to reading /dev/urandom. */ + random_open_urandom(); + } + } + /* this is here to avoid clang complain, + because getrandom_present will be always FALSE + if USE_GETRANDOM is not defined */ + if (!getrandom_present) +# endif + ret = read(urandom_fd, buf, size); + if (unlikely(ret <= 0)) { + if (ret == 0) { + i_fatal("read("DEV_URANDOM_PATH") failed: EOF"); + } else if (errno != EINTR) { + if (getrandom_present) { + i_fatal("getrandom() failed: %m"); + } else { + i_fatal("read("DEV_URANDOM_PATH") failed: %m"); + } + } + } + i_assert(ret > 0 || errno == EINTR); + return ret; +} +#endif + +void random_fill(void *buf, size_t size) +{ + i_assert(init_refcount > 0); + i_assert(size < SSIZE_T_MAX); + +#ifdef DEBUG + if (kiss_in_use) { + for (size_t pos = 0; pos < size; pos++) + ((unsigned char*)buf)[pos] = kiss_rand(); + return; + } +#endif + +#if defined(USE_ARC4RANDOM) + arc4random_buf(buf, size); +#else + size_t pos; + ssize_t ret; + + for (pos = 0; pos < size; ) { + if (size >= sizeof(random_next) && random_next_size == 0) { + /* Asking for lots of randomness. Read directly to the + destination buffer. */ + ret = random_read(PTR_OFFSET(buf, pos), size - pos); + if (ret > -1) + pos += ret; + } else { + /* Asking for a little randomness. Read via a larger + buffer to reduce the number of syscalls. */ + if (random_next_size > random_next_pos) + ret = random_next_size - random_next_pos; + else { + random_next_pos = 0; + ret = random_read(random_next, + sizeof(random_next)); + random_next_size = ret < 0 ? 0 : ret; + } + if (ret > 0) { + size_t used = I_MIN(size - pos, (size_t)ret); + memcpy(PTR_OFFSET(buf, pos), + random_next + random_next_pos, used); + random_next_pos += used; + pos += used; + } + } + } +#endif /* defined(USE_ARC4RANDOM) */ +} + +void random_init(void) +{ + /* static analyzer seems to require this */ + unsigned int seed = 0; + const char *env_seed; + + if (init_refcount++ > 0) + return; + + env_seed = getenv("DOVECOT_SRAND"); +#ifdef DEBUG + if (env_seed != NULL && str_to_uint(env_seed, &seed) >= 0) { + kiss_init(seed); + /* getrandom_present = FALSE; not needed, only used in random_read() */ + goto normal_exit; + } +#else + if (env_seed != NULL && *env_seed != '\0') + i_warning("DOVECOT_SRAND is not available in non-debug builds"); +#endif /* DEBUG */ + +#if defined(USE_RANDOM_DEV) + random_open_urandom(); +#endif + /* DO NOT REMOVE THIS - It is also + needed to make sure getrandom really works. + */ + random_fill(&seed, sizeof(seed)); +#ifdef DEBUG + if (env_seed != NULL) { + if (strcmp(env_seed, "kiss") != 0) + i_fatal("DOVECOT_SRAND not a number or 'kiss'"); + kiss_init(seed); + i_close_fd(&urandom_fd); + } + +normal_exit: +#endif + srand(seed); +} + +void random_deinit(void) +{ + if (--init_refcount > 0) + return; + i_close_fd(&urandom_fd); +} |