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
This commit is contained in:
Nils Maier 2014-12-30 18:17:44 +01:00
parent 747131a06c
commit 81bdd5f61a
9 changed files with 234 additions and 104 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
return std::uniform_int_distribution<long int>(0, to - 1)(*this);
}
void SimpleRandomizer::getRandomBytes(unsigned char* buf, size_t len)
{
#ifdef __MINGW32__
int32_t val;
BOOL r = CryptGenRandom(cryProvider_, sizeof(val),
reinterpret_cast<BYTE*>(&val));
BOOL r = CryptGenRandom(provider_, len, reinterpret_cast<BYTE*>(buf));
assert(r);
if(val == INT32_MIN) {
val = INT32_MAX;
} else if(val < 0) {
val = -val;
#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_);
}
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);
}
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);
}
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

View File

@ -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_;
#ifdef __MINGW32__
HCRYPTPROV cryProvider_;
#else // !__MINGW32__
std::minstd_rand eng_;
#endif //!__MINGW32__
SimpleRandomizer();
private:
#ifdef __MINGW32__
HCRYPTPROV provider_;
#elif defined(HAVE_GETRANDOM_INTERFACE)
// Nothing special needed
#else
std::random_device dev_;
#endif // ! __MINGW32__
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
src/getrandom_linux.c Normal file
View File

@ -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
src/getrandom_linux.h Normal file
View File

@ -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 */

View File

@ -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

View File

@ -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()