]> git.pond.sub.org Git - empserver/blobdiff - src/lib/gen/chance.c
Fix PRNG seeding to resist guessing
[empserver] / src / lib / gen / chance.c
index 73e24f286b0d7793475288f58b43f004eab68fa1..4c38b4ae75db6d542c57dcace4c13548105d6022 100644 (file)
 
 #include <config.h>
 
+#include <fcntl.h>
 #include <math.h>
+#include <stdint.h>
 #include <stdlib.h>
+#include <sys/time.h>
+#include <unistd.h>
 #include "chance.h"
+#include "mt19937ar.h"
 
 /*
  * Return non-zero with probability D.
@@ -42,7 +47,7 @@
 int
 chance(double d)
 {
-    return d > (random() % 32768) / 32768.0;
+    return d > genrand_real2();
 }
 
 /*
@@ -54,22 +59,43 @@ pct_chance(int pct)
     return roll(100) <= pct;
 }
 
+static unsigned
+round_up_to_pow2(unsigned val)
+{
+    val--;
+    val |= val >> 1;
+    val |= val >> 2;
+    val |= val >> 4;
+    val |= val >> 8;
+    val |= val >> 16;
+    val++;
+    return val;
+}
+
 /*
  * Return a random number in [0..N-1].
+ * N must be in [1..2^31-1].
  */
 int
 roll0(int n)
 {
-    return random() % n;
+    unsigned pow2 = round_up_to_pow2(n);
+    int r;
+
+    do
+       r = genrand_int32() & (pow2 - 1);
+    while (r >= n);
+    return r;
 }
 
 /*
  * Return a random number in [1..N].
+ * N must be in [0..2^31-1].
  */
 int
 roll(int n)
 {
-    return 1 + random() % n;
+    return 1 + roll0(n);
 }
 
 /*
@@ -91,5 +117,58 @@ roundavg(double val)
 void
 seed_prng(unsigned seed)
 {
-    srandom(seed);
+    init_genrand(seed);
+}
+
+static uint32_t
+djb_hash(uint32_t hash, void *buf, size_t sz)
+{
+    unsigned char *bp;
+
+    for (bp = buf; bp < (unsigned char *)buf + sz; bp++)
+       hash = hash * 33 ^ *bp;
+
+    return hash;
+}
+
+/*
+ * Pick a reasonably random seed for the pseudo-random number generator.
+ */
+unsigned
+pick_seed(void)
+{
+    int fd;
+    uint32_t seed;
+    int got_seed = 0;
+    struct timeval tv;
+    pid_t pid;
+
+    /*
+     * Modern systems provide random number devices, but the details
+     * vary.  On many systems, /dev/random blocks when the kernel
+     * entropy pool has been depleted, while /dev/urandom doesn't.
+     * The former should only be used for generating long-lived
+     * cryptographic keys.  On other systems, both devices behave
+     * exactly the same, or only /dev/random exists.
+     *
+     * Try /dev/urandom first, and if it can't be opened, blindly try
+     * /dev/random.
+     */
+    fd = open("/dev/urandom", O_RDONLY | O_NONBLOCK);
+    if (fd < 0)
+       fd = open("/dev/random", O_RDONLY | O_NONBLOCK);
+    if (fd >= 0) {
+       got_seed = read(fd, &seed, sizeof(seed)) == sizeof(seed);
+       close(fd);
+    }
+
+    if (!got_seed) {
+       /* Kernel didn't provide, fall back to hashing time and PID */
+       gettimeofday(&tv, NULL);
+       seed = djb_hash(5381, &tv, sizeof(tv));
+       pid = getpid();
+       seed = djb_hash(seed, &pid, sizeof(pid));
+    }
+
+    return seed;
 }