ソースを参照

Revise getRandom facilities

Use one of the following to provide random bytes:
- Windows CryptGenRandom
- Linux getrandom (syscall interface to urandom, without nasty corner
  cases such as file descriptor exhaustion or re-linked /dev/urandom)
- std::device_random (C++ random device, which usually will be urandom)

This also equalizes util::getRandom and SimpleRandomizer (the former
will now use the latter) instead of having essentially two different
PRNG interfaces with potentially different quality.

Closes GH-320
Nils Maier 10 年 前
コミット
81bdd5f61a
9 ファイル変更230 行追加100 行削除
  1. 14 0
      configure.ac
  2. 0 1
      src/Context.cc
  3. 4 0
      src/Makefile.am
  4. 28 50
      src/SimpleRandomizer.cc
  5. 37 10
      src/SimpleRandomizer.h
  6. 50 0
      src/getrandom_linux.c
  7. 49 0
      src/getrandom_linux.h
  8. 2 37
      src/util.cc
  9. 46 2
      test/UtilTest.cc

+ 14 - 0
configure.ac

@@ -720,6 +720,7 @@ AC_CHECK_FUNCS([__argz_count \
                 gethostbyname \
 		getifaddrs \
                 getpagesize \
+                getrandom \
                 memchr \
                 memmove \
                 mempcpy \
@@ -755,6 +756,19 @@ AC_CHECK_FUNCS([__argz_count \
 		utime \
 		utimes])
 
+AC_MSG_CHECKING([for getrandom linux syscall interface])
+AC_LINK_IFELSE([AC_LANG_PROGRAM([[
+#include <linux/random.h>
+]],
+[[
+int x = GRND_NONBLOCK;
+]])],
+  [have_getrandom_interface=yes
+   AC_DEFINE([HAVE_GETRANDOM_INTERFACE], [1], [Define to 1 if getrandom linux syscall interface is available.])],
+  [have_getrandom_interface=no])
+AC_MSG_RESULT([$have_getrandom_interface])
+AM_CONDITIONAL([HAVE_GETRANDOM_INTERFACE], [test "x$have_getrandom_interface" = "xyes"])
+
 dnl Put tcmalloc/jemalloc checks after the posix_memalign check.
 dnl These libraries may implement posix_memalign, while the usual CRT may not
 dnl (e.g. mingw). Since we aren't including the corresponding library headers

+ 0 - 1
src/Context.cc

@@ -163,7 +163,6 @@ Context::Context(bool standalone,
       throw DL_ABORT_EX("Option processing failed");
     }
   }
-  SimpleRandomizer::getInstance()->init();
 #ifdef ENABLE_BITTORRENT
   bittorrent::generateStaticPeerId(op->get(PREF_PEER_ID_PREFIX));
 #endif // ENABLE_BITTORRENT

+ 4 - 0
src/Makefile.am

@@ -687,6 +687,10 @@ if HAVE_KQUEUE
 SRCS += KqueueEventPoll.cc KqueueEventPoll.h
 endif # HAVE_KQUEUE
 
+if HAVE_GETRANDOM_INTERFACE
+SRCS += getrandom_linux.c getrandom_linux.h
+endif # HAVE_GETRANDOM_INTERFACE
+
 if HAVE_LIBUV
 SRCS += LibuvEventPoll.cc LibuvEventPoll.h
 endif # HAVE_LIBUV

+ 28 - 50
src/SimpleRandomizer.cc

@@ -43,6 +43,10 @@
 #include "a2time.h"
 #include "a2functional.h"
 
+#ifdef HAVE_GETRANDOM_INTERFACE
+#  include "getrandom_linux.h"
+#endif
+
 namespace aria2 {
 
 std::unique_ptr<SimpleRandomizer> SimpleRandomizer::randomizer_;
@@ -55,75 +59,49 @@ const std::unique_ptr<SimpleRandomizer>& SimpleRandomizer::getInstance()
   return randomizer_;
 }
 
-void SimpleRandomizer::init()
-{
-#ifndef __MINGW32__
-  // Just in case std::random_device() is fixed, add time and pid too.
-  eng_.seed(std::random_device()()^time(nullptr)^getpid());
-#endif // !__MINGW32__
-}
-
 SimpleRandomizer::SimpleRandomizer()
 {
 #ifdef __MINGW32__
-  BOOL r = CryptAcquireContext(&cryProvider_, 0, 0, PROV_RSA_FULL,
-                               CRYPT_VERIFYCONTEXT|CRYPT_SILENT);
+  BOOL r = ::CryptAcquireContext(
+      &provider_,
+      0, 0, PROV_RSA_FULL,
+      CRYPT_VERIFYCONTEXT | CRYPT_SILENT);
   assert(r);
-#endif // __MINGW32__
+#endif
 }
 
 SimpleRandomizer::~SimpleRandomizer()
 {
 #ifdef __MINGW32__
-  CryptReleaseContext(cryProvider_, 0);
-#endif // __MINGW32__
+  CryptReleaseContext(provider_, 0);
+#endif
 }
 
 long int SimpleRandomizer::getRandomNumber(long int to)
 {
   assert(to > 0);
-#ifdef __MINGW32__
-  int32_t val;
-  BOOL r = CryptGenRandom(cryProvider_, sizeof(val),
-                          reinterpret_cast<BYTE*>(&val));
-  assert(r);
-  if(val == INT32_MIN) {
-    val = INT32_MAX;
-  } else if(val < 0) {
-    val = -val;
-  }
-  return val % to;
-#else // !__MINGW32__
-  return std::uniform_int_distribution<long int>(0, to - 1)(eng_);
-#endif // !__MINGW32__
-}
-
-long int SimpleRandomizer::operator()(long int to)
-{
-  return getRandomNumber(to);
+  return std::uniform_int_distribution<long int>(0, to - 1)(*this);
 }
 
-void SimpleRandomizer::getRandomBytes(unsigned char *buf, size_t len)
+void SimpleRandomizer::getRandomBytes(unsigned char* buf, size_t len)
 {
 #ifdef __MINGW32__
-  if (!CryptGenRandom(cryProvider_, len, (PBYTE)buf)) {
-    throw std::bad_alloc();
-  }
-#else
-  uint32_t val;
-  size_t q = len / sizeof(val);
-  size_t r = len % sizeof(val);
-  auto gen = std::bind(std::uniform_int_distribution<uint32_t>
-                       (0, std::numeric_limits<uint32_t>::max()),
-                       eng_);
-  for(; q > 0; --q) {
-    val = gen();
-    memcpy(buf, &val, sizeof(val));
-    buf += sizeof(val);
+  BOOL r = CryptGenRandom(provider_, len, reinterpret_cast<BYTE*>(buf));
+  assert(r);
+#elif defined(HAVE_GETRANDOM_INTERFACE)
+  auto rv = getrandom_linux(buf, len);
+  assert(rv >= 0 && (size_t)rv == len);
+#else // ! __MINGW32__
+  auto ubuf = reinterpret_cast<result_type*>(buf);
+  size_t q = len / sizeof(result_type);
+  auto gen = std::uniform_int_distribution<result_type>();
+  for(; q > 0; --q, ++ubuf) {
+    *ubuf = gen(dev_);
   }
-  val = gen();
-  memcpy(buf, &val, r);
-#endif
+  const size_t r = len % sizeof(result_type);
+  auto last = gen(dev_);
+  memcpy(ubuf, &last, r);
+#endif // ! __MINGW32__
 }
 
 } // namespace aria2

+ 37 - 10
src/SimpleRandomizer.h

@@ -41,30 +41,32 @@
 #include <random>
 
 #ifdef __MINGW32__
-# include <wincrypt.h>
-#endif // __MINGW32__
+#  include <wincrypt.h>
+#endif
 
 namespace aria2 {
 
 class SimpleRandomizer : public Randomizer {
 private:
   static std::unique_ptr<SimpleRandomizer> randomizer_;
+  SimpleRandomizer();
 
+private:
 #ifdef __MINGW32__
-  HCRYPTPROV cryProvider_;
-#else // !__MINGW32__
-  std::minstd_rand eng_;
-#endif //!__MINGW32__
+  HCRYPTPROV provider_;
+#elif defined(HAVE_GETRANDOM_INTERFACE)
+  // Nothing special needed
+#else
+  std::random_device dev_;
+#endif // ! __MINGW32__
 
-  SimpleRandomizer();
 public:
+  typedef std::random_device::result_type result_type;
 
   static const std::unique_ptr<SimpleRandomizer>& getInstance();
 
   virtual ~SimpleRandomizer();
 
-  void init();
-
   /**
    * Returns random number in [0, to).
    */
@@ -72,7 +74,32 @@ public:
 
   void getRandomBytes(unsigned char *buf, size_t len);
 
-  long int operator()(long int to);
+  long int operator()(long int to)
+  {
+    return getRandomNumber(to);
+  }
+
+  result_type operator()()
+  {
+    result_type rv;
+    getRandomBytes(reinterpret_cast<unsigned char*>(&rv), sizeof(rv));
+    return rv;
+  }
+
+  static constexpr result_type min()
+  {
+    return std::numeric_limits<result_type>::min();
+  }
+
+  static constexpr result_type max()
+  {
+    return std::numeric_limits<result_type>::max();
+  }
+
+  static double entropy()
+  {
+    return 0.0;
+  }
 };
 
 } // namespace aria2

+ 50 - 0
src/getrandom_linux.c

@@ -0,0 +1,50 @@
+/* <!-- copyright */
+/*
+ * aria2 - The high speed download utility
+ *
+ * Copyright (C) 2014 Nils Maier
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL.  If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so.  If you
+ * do not wish to do so, delete this exception statement from your
+ * version.  If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ */
+/* copyright --> */
+
+#define _GNUSOURCE
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <linux/random.h>
+
+#include "config.h"
+#include "getrandom_linux.h"
+
+int getrandom_linux(void *buf, size_t buflen) {
+#ifdef HAVE_GETRANDOM
+  return getrandom(buf, buflen, 0);
+#else // HAVE_GETRANDOM
+  return syscall(SYS_getrandom, buf, buflen, 0);
+#endif // HAVE_GETRANDOM
+}

+ 49 - 0
src/getrandom_linux.h

@@ -0,0 +1,49 @@
+/* <!-- copyright */
+/*
+ * aria2 - The high speed download utility
+ *
+ * Copyright (C) 2014 Nils Maier
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL.  If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so.  If you
+ * do not wish to do so, delete this exception statement from your
+ * version.  If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ */
+/* copyright --> */
+
+#ifndef D_GETRANDOM_LINUX_H
+#define D_GETRANDOM_LINUX_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int getrandom_linux(void *buf, size_t buflen);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* D_GETRANDOM_LINUX_H */

+ 2 - 37
src/util.cc

@@ -1545,45 +1545,10 @@ std::vector<std::pair<size_t, std::string> > createIndexPaths(std::istream& i)
   return indexPaths;
 }
 
-namespace {
-void generateRandomDataRandom(unsigned char* data, size_t length)
-{
-  const auto& rd = SimpleRandomizer::getInstance();
-  rd->getRandomBytes(data, length);
-}
-} // namespace
-
-#ifndef __MINGW32__
-namespace {
-void generateRandomDataUrandom
-(unsigned char* data, size_t length, std::ifstream& devUrand)
-{
-  devUrand.read(reinterpret_cast<char*>(data), length);
-}
-} // namespace
-#endif
-
 void generateRandomData(unsigned char* data, size_t length)
 {
-#ifdef __MINGW32__
-  generateRandomDataRandom(data, length);
-#else // !__MINGW32__
-  static int method = -1;
-  static std::ifstream devUrand;
-  if(method == 0) {
-    generateRandomDataUrandom(data, length, devUrand);
-  } else if(method == 1) {
-    generateRandomDataRandom(data, length);
-  } else {
-    devUrand.open("/dev/urandom");
-    if(devUrand) {
-      method = 0;
-    } else {
-      method = 1;
-    }
-    generateRandomData(data, length);
-  }
-#endif // !__MINGW32__
+  const auto& rd = SimpleRandomizer::getInstance();
+  return rd->getRandomBytes(data, length);
 }
 
 bool saveAs

+ 46 - 2
test/UtilTest.cc

@@ -1,5 +1,6 @@
 #include "util.h"
 
+#include <cmath>
 #include <cstring>
 #include <string>
 #include <iostream>
@@ -1954,11 +1955,54 @@ void UtilTest::testCreateIndexPaths()
 
 void UtilTest::testGenerateRandomData()
 {
-  unsigned char data1[20];
+  using namespace std;
+
+  // Simple sanity check
+  unsigned char data1[25];
+  memset(data1, 0, sizeof(data1));
   util::generateRandomData(data1, sizeof(data1));
-  unsigned char data2[20];
+
+  unsigned char data2[25];
+  memset(data2, 0, sizeof(data2));
   util::generateRandomData(data2, sizeof(data2));
+
   CPPUNIT_ASSERT(memcmp(data1, data2, sizeof(data1)) != 0);
+
+  // Simple stddev/mean tests
+  map<uint8_t, size_t> counts;
+  uint8_t bytes[1 << 20];
+  for (auto i = 0; i < 10; ++i) {
+    util::generateRandomData(bytes, sizeof(bytes));
+    for (auto b : bytes) {
+      counts[b]++;
+    }
+  }
+  CPPUNIT_ASSERT_MESSAGE("Should see all kinds of bytes", counts.size() == 256);
+  if (counts.size() != 256) {
+    throw std::domain_error(
+        "Would have expected to see at one of each possible byte value!");
+  }
+  double sum = accumulate(
+    counts.begin(),
+    counts.end(),
+    0.0,
+    [](double total, const decltype(counts)::value_type & elem) {
+      return total + elem.second;
+    });
+  double mean = sum / counts.size();
+  vector<double> diff(counts.size());
+  transform(
+    counts.begin(),
+    counts.end(),
+    diff.begin(),
+    [&](const decltype(counts)::value_type & elem) -> double {
+      return (double)elem.second - mean;
+    });
+  double sq_sum = inner_product(diff.begin(), diff.end(), diff.begin(), 0.0);
+  double stddev = sqrt(sq_sum / counts.size());
+  cout << "stddev: " << fixed << stddev << endl;
+  CPPUNIT_ASSERT_MESSAGE("stddev makes sense (lower)", stddev <= 320);
+  CPPUNIT_ASSERT_MESSAGE("stddev makes sense (upper)", stddev >= 100);
 }
 
 void UtilTest::testFromHex()