summaryrefslogtreecommitdiffstats
path: root/src/lib/randgen.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/randgen.c')
-rw-r--r--src/lib/randgen.c222
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);
+}