]> git.ipfire.org Git - thirdparty/pdns.git/blobdiff - pdns/dns_random.cc
Merge pull request #8223 from PowerDNS/omoerbeek-patch-1
[thirdparty/pdns.git] / pdns / dns_random.cc
index 873de38622e438221ecc6df664e19c1dda5ae171..48b910c8f4f85d7c2a561f664de3c52fc37dd922 100644 (file)
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
-#include <openssl/aes.h>
-#include <openssl/opensslv.h>
-#if OPENSSL_VERSION_NUMBER > 0x1010000fL && !defined LIBRESSL_VERSION_NUMBER
-// Older OpenSSL does not have CRYPTO_ctr128_encrypt. Before 1.1.0 the header
-// file did not have the necessary extern "C" wrapper. In 1.1.0, AES_ctr128_encrypt
-// was removed.
-#include <openssl/modes.h>
-#endif
-#include <iostream>
-#include <cstdlib>
-#include <cstring>
 #include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
 #include <unistd.h>
-#include <sys/time.h>
-#include <limits>
-#include <stdexcept>
-#include <stdint.h>
+#include <stdlib.h>
+#include <string>
 #include "dns_random.hh"
+#include "arguments.hh"
+#include "logger.hh"
+#include "boost/lexical_cast.hpp"
+
+#if defined(HAVE_RANDOMBYTES_STIR)
+#include <sodium.h>
+#endif
+#if defined(HAVE_RAND_BYTES)
+#include <openssl/rand.h>
+#endif
+#if defined(HAVE_GETRANDOM)
+#include <sys/random.h>
+#endif
 
-using namespace std;
+static enum DNS_RNG {
+  RNG_UNINITIALIZED = 0,
+  RNG_SODIUM,
+  RNG_OPENSSL,
+  RNG_GETRANDOM,
+  RNG_ARC4RANDOM,
+  RNG_URANDOM,
+  RNG_KISS,
+} chosen_rng = RNG_UNINITIALIZED;
 
-static AES_KEY aes_key;
-static unsigned int g_offset;
-static unsigned char g_counter[16], g_stream[16];
-static uint32_t g_in;
+static int urandom_fd = -1;
 
-static bool g_initialized;
+#if defined(HAVE_KISS_RNG)
+/* KISS is intended for development use only */
+static unsigned int kiss_seed;
+static uint32_t kiss_z, kiss_w, kiss_jsr, kiss_jcong;
 
-void dns_random_init(const char data[16])
+static void
+kiss_init(unsigned int seed)
 {
-  g_offset = 0;
-  memset(&g_stream, 0, sizeof(g_stream));
-  if (AES_set_encrypt_key((const unsigned char*)data, 128, &aes_key) < 0) {
-    throw std::runtime_error("AES_set_encrypt_key failed");
+  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 */
+}
+
+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;
+}
+#endif
+
+static void dns_random_setup(bool force=false)
+{
+  string rdev;
+  string rng;
+  /* check if selection has been done */
+  if (chosen_rng > RNG_UNINITIALIZED && !force)
+    return;
+
+/* XXX: A horrible hack to allow using dns_random in places where arguments are not available.
+        Forces /dev/urandom usage
+*/
+#if defined(USE_URANDOM_ONLY)
+  chosen_rng = RNG_URANDOM;
+  rdev = "/dev/urandom";
+#else
+  rng = ::arg()["rng"];
+  rdev = ::arg()["entropy-source"];
+  if (rng == "auto") {
+# if defined(HAVE_GETRANDOM)
+    chosen_rng = RNG_GETRANDOM;
+# elif defined(HAVE_ARC4RANDOM)
+    chosen_rng = RNG_ARC4RANDOM;
+# elif defined(HAVE_RANDOMBYTES_STIR)
+    chosen_rng = RNG_SODIUM;
+# elif defined(HAVE_RAND_BYTES)
+    chosen_rng = RNG_OPENSSL;
+# else
+    chosen_rng = RNG_URANDOM;
+# endif
+# if defined(HAVE_RANDOMBYTES_STIR)
+  } else if (rng == "sodium") {
+    chosen_rng = RNG_SODIUM;
+# endif
+# if defined(HAVE_RAND_BYTES)
+  } else if (rng == "openssl") {
+    chosen_rng = RNG_OPENSSL;
+# endif
+# if defined(HAVE_GETRANDOM)
+  } else if (rng == "getrandom") {
+    chosen_rng = RNG_GETRANDOM;
+# endif
+# if defined(HAVE_ARC4RANDOM)
+  } else if (rng == "arc4random") {
+    chosen_rng = RNG_ARC4RANDOM;
+# endif
+  } else if (rng == "urandom") {
+    chosen_rng = RNG_URANDOM;
+#if defined(HAVE_KISS_RNG)
+  } else if (rng == "kiss") {
+    chosen_rng = RNG_KISS;
+    g_log<<Logger::Warning<<"kiss rng should not be used in production environment"<<std::endl;
+#endif
+  } else {
+    throw std::runtime_error("Unsupported rng '" + rng + "'");
+  }
+
+# if defined(HAVE_RANDOMBYTES_STIR)
+  if (chosen_rng == RNG_SODIUM) {
+    if (sodium_init() == -1)
+      throw std::runtime_error("Unable to initialize sodium crypto library");
+    /*  make sure it's set up */
+    randombytes_stir();
   }
+# endif
 
-  struct timeval now;
-  gettimeofday(&now, 0);
+# if defined(HAVE_GETRANDOM)
+  if (chosen_rng == RNG_GETRANDOM) {
+    char buf[1];
+    // some systems define getrandom but it does not really work, e.g. because it's
+    // not present in kernel.
+    if (getrandom(buf, sizeof(buf), 0) == -1) {
+       g_log<<Logger::Warning<<"getrandom() failed: "<<strerror(errno)<<", falling back to " + rdev<<std::endl;
+       chosen_rng = RNG_URANDOM;
+    }
+  }
+# endif
 
-  static_assert(sizeof(g_counter) >= (sizeof(now.tv_usec) + sizeof(now.tv_sec)), "g_counter must be large enough to get tv_sec + tv_usec");
-  memcpy(g_counter, &now.tv_usec, sizeof(now.tv_usec));
-  memcpy(g_counter+sizeof(now.tv_usec), &now.tv_sec, sizeof(now.tv_sec));
-  g_in = getpid() | (getppid()<<16);
+# if defined(HAVE_RAND_BYTES)
+  if (chosen_rng == RNG_OPENSSL) {
+    int ret;
+    unsigned char buf[1];
+    if ((ret = RAND_bytes(buf, sizeof(buf))) == -1)
+      throw std::runtime_error("RAND_bytes not supported by current SSL engine");
+    if (ret == 0)
+      throw std::runtime_error("Openssl RNG was not seeded");
+  }
+# endif
+#endif /* USE_URANDOM_ONLY */
+  if (chosen_rng == RNG_URANDOM) {
+    urandom_fd = open(rdev.c_str(), O_RDONLY);
+    if (urandom_fd == -1)
+      throw std::runtime_error("Cannot open " + rdev + ": " + std::string(strerror(errno)));
+  }
+#if defined(HAVE_KISS_RNG)
+  if (chosen_rng == RNG_KISS) {
+    unsigned int seed;
+    urandom_fd = open(rdev.c_str(), O_RDONLY);
+    if (urandom_fd == -1)
+      throw std::runtime_error("Cannot open " + rdev + ": " + std::string(strerror(errno)));
+    if (read(urandom_fd, &seed, sizeof(seed)) < 0) {
+      (void)close(urandom_fd);
+      throw std::runtime_error("Cannot read random device");
+    }
+    kiss_init(seed);
+    (void)close(urandom_fd);
+  }
+#endif
+}
 
-  g_initialized = true;
-  srandom(dns_random(numeric_limits<uint32_t>::max()));
+void dns_random_init(const string& data __attribute__((unused)), bool force) {
+  dns_random_setup(force);
+  (void)dns_random(1);
+  // init should occur already in dns_random_setup
+  // this interface is only for KISS
+#if defined(HAVE_KISS_RNG)
+  unsigned int seed;
+  if (chosen_rng != RNG_KISS)
+    return;
+  if (data.size() != 16)
+    throw std::runtime_error("invalid seed");
+  seed = (data[0] + (data[1]<<8) + (data[2]<<16) + (data[3]<<24)) ^
+         (data[4] + (data[5]<<8) + (data[6]<<16) + (data[7]<<24)) ^
+         (data[8] + (data[9]<<8) + (data[10]<<16) + (data[11]<<24)) ^
+         (data[12] + (data[13]<<8) + (data[14]<<16) + (data[15]<<24));
+  kiss_init(seed);
+#endif
 }
 
-unsigned int dns_random(unsigned int n)
-{
-  if(!g_initialized)
-    abort();
-  uint32_t out;
-#if OPENSSL_VERSION_NUMBER > 0x1010000fL && !defined LIBRESSL_VERSION_NUMBER
-  CRYPTO_ctr128_encrypt((const unsigned char*)&g_in, (unsigned char*) &out, sizeof(g_in), &aes_key, g_counter, g_stream, &g_offset, (block128_f) AES_encrypt);
+/* Parts of this code come from arc4random_uniform */
+uint32_t dns_random(uint32_t upper_bound) {
+  if (chosen_rng == RNG_UNINITIALIZED)
+    dns_random_setup();
+
+  unsigned int min;
+  if (upper_bound < 2)
+    return 0;
+  /* To avoid "modulo bias" for some methods, calculate
+     minimum acceptable value for random number to improve
+     uniformity.
+
+     On applicable rngs, we loop until the rng spews out
+     value larger than min, and then take modulo out of that.
+  */
+#if (ULONG_MAX > 0xffffffffUL)
+  min = 0x100000000UL % upper_bound;
 #else
-  AES_ctr128_encrypt((const unsigned char*)&g_in, (unsigned char*) &out, sizeof(g_in), &aes_key, g_counter, g_stream, &g_offset);
+  /* Calculate (2**32 % upper_bound) avoiding 64-bit math */
+  if (upper_bound > 0x80000000)
+    min = 1 + ~upper_bound; /* 2**32 - upper_bound */
+  else {
+    /* (2**32 - (x * 2)) % x == 2**32 % x when x <= 2**31 */
+    min = ((0xffffffff - (upper_bound * 2)) + 1) % upper_bound;
+  }
 #endif
-  return out % n;
-}
 
-#if 0
-int main()
-{
-  dns_random_init("0123456789abcdef");
+  switch(chosen_rng) {
+  case RNG_UNINITIALIZED:
+    throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
+  case RNG_SODIUM:
+#if defined(HAVE_RANDOMBYTES_STIR) && !defined(USE_URANDOM_ONLY)
+    return randombytes_uniform(upper_bound);
+#else
+    throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
+#endif /* RND_SODIUM */
+  case RNG_OPENSSL: {
+#if defined(HAVE_RAND_BYTES) && !defined(USE_URANDOM_ONLY)
+      uint32_t num = 0;
+      do {
+        if (RAND_bytes(reinterpret_cast<unsigned char*>(&num), sizeof(num)) < 1)
+          throw std::runtime_error("Openssl RNG was not seeded");
+      }
+      while(num < min);
 
-  for(int n = 0; n < 16; n++)
-    cerr<<dns_random(16384)<<endl;
-}
+      return num % upper_bound;
+#else
+      throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
+#endif /* RNG_OPENSSL */
+     }
+  case RNG_GETRANDOM: {
+#if defined(HAVE_GETRANDOM) && !defined(USE_URANDOM_ONLY)
+      uint32_t num = 0;
+      do {
+        if (getrandom(&num, sizeof(num), 0) != sizeof(num))
+          throw std::runtime_error("getrandom() failed: " + std::string(strerror(errno)));
+      }
+      while(num < min);
+
+      return num % upper_bound;
+#else
+      throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
+#endif
+      }
+  case RNG_ARC4RANDOM:
+#if defined(HAVE_ARC4RANDOM) && !defined(USE_URANDOM_ONLY)
+    return arc4random_uniform(upper_bound);
+#else
+    throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
 #endif
+  case RNG_URANDOM: {
+      uint32_t num = 0;
+      size_t attempts = 5;
+      do {
+        ssize_t got = read(urandom_fd, &num, sizeof(num));
+        if (got < 0) {
+          if (errno == EINTR) {
+            continue;
+          }
+
+          (void)close(urandom_fd);
+          throw std::runtime_error("Cannot read random device");
+        }
+        else if (static_cast<size_t>(got) != sizeof(num)) {
+          /* short read, let's retry */
+          if (attempts == 0) {
+            throw std::runtime_error("Too many short reads on random device");
+          }
+          attempts--;
+          continue;
+        }
+      }
+      while(num < min);
+
+      return num % upper_bound;
+    }
+#if defined(HAVE_KISS_RNG)
+  case RNG_KISS: {
+      uint32_t num = 0;
+      do {
+        num = kiss_rand();
+      }
+      while(num < min);
+
+      return num % upper_bound;
+    }
+#endif
+  default:
+    throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
+  };
+}