aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPieter Wuille <pieter.wuille@gmail.com>2018-12-13 18:37:29 -0800
committerPieter Wuille <pieter.wuille@gmail.com>2019-01-16 16:34:56 -0800
commit9d7032e4f066777c97c58b1394884716e213790a (patch)
tree16a2de523fc29912c6f6e37d63795b46b6234a97 /src
parent16e40a8b562ad849a5f5e8b21ceb375e46038243 (diff)
downloadbitcoin-9d7032e4f066777c97c58b1394884716e213790a.tar.xz
Switch all RNG code to the built-in PRNG.
It includes the following policy changes: * All GetRand* functions seed the stack pointer and rdrand result (in addition to the performance counter) * The periodic entropy added by the idle scheduler now seeds stack pointer, rdrand and perfmon data (once every 10 minutes) in addition to just a sleep timing. * The entropy added when calling GetStrongRandBytes no longer includes the once-per-10-minutes perfmon data on windows (it is moved to the idle scheduler instead, where latency matters less). Other changes: * OpenSSL is no longer seeded directly anywhere. Instead, any generated randomness through our own RNG is fed back to OpenSSL (after an additional hashing step to prevent leaking our RNG state). * Seeding that was previously done directly in RandAddSeedSleep is now moved to SeedSleep(), which is indirectly invoked through ProcRand from RandAddSeedSleep. * Seeding that was previously done directly in GetStrongRandBytes() is now moved to SeedSlow(), which is indirectly invoked through ProcRand from GetStrongRandBytes().
Diffstat (limited to 'src')
-rw-r--r--src/random.cpp185
-rw-r--r--src/random.h31
-rw-r--r--src/scheduler.cpp2
3 files changed, 138 insertions, 80 deletions
diff --git a/src/random.cpp b/src/random.cpp
index 6699318ee0..ca54cc9962 100644
--- a/src/random.cpp
+++ b/src/random.cpp
@@ -145,18 +145,8 @@ static bool GetHardwareRand(unsigned char* ent32) {
return false;
}
-void RandAddSeed()
+static void RandAddSeedPerfmon(CSHA512& hasher)
{
- // Seed with CPU performance counter
- int64_t nCounter = GetPerformanceCounter();
- RAND_add(&nCounter, sizeof(nCounter), 1.5);
- memory_cleanse((void*)&nCounter, sizeof(nCounter));
-}
-
-static void RandAddSeedPerfmon()
-{
- RandAddSeed();
-
#ifdef WIN32
// Don't need this on Linux, OpenSSL automatically uses /dev/urandom
// Seed with the entire set of perfmon data
@@ -180,7 +170,7 @@ static void RandAddSeedPerfmon()
}
RegCloseKey(HKEY_PERFORMANCE_DATA);
if (ret == ERROR_SUCCESS) {
- RAND_add(vData.data(), nSize, nSize / 100.0);
+ hasher.Write(vData.data(), nSize);
memory_cleanse(vData.data(), nSize);
} else {
// Performance data is only a best-effort attempt at improving the
@@ -288,13 +278,6 @@ void GetOSRand(unsigned char *ent32)
#endif
}
-void GetRandBytes(unsigned char* buf, int num)
-{
- if (RAND_bytes(buf, num) != 1) {
- RandFailure();
- }
-}
-
void LockingCallbackOpenSSL(int mode, int i, const char* file, int line);
namespace {
@@ -303,6 +286,7 @@ struct RNGState {
Mutex m_mutex;
unsigned char m_state[32] GUARDED_BY(m_mutex) = {0};
uint64_t m_counter GUARDED_BY(m_mutex) = 0;
+ bool m_strongly_seeded GUARDED_BY(m_mutex) = false;
std::unique_ptr<Mutex[]> m_mutex_openssl;
RNGState()
@@ -319,14 +303,6 @@ struct RNGState {
// or corrupt. Explicitly tell OpenSSL not to try to load the file. The result for our libs will be
// that the config appears to have been loaded and there are no modules/engines available.
OPENSSL_no_config();
-
-#ifdef WIN32
- // Seed OpenSSL PRNG with current contents of the screen
- RAND_screen();
-#endif
-
- // Seed OpenSSL PRNG with performance counter
- RandAddSeed();
}
~RNGState()
@@ -337,14 +313,19 @@ struct RNGState {
CRYPTO_set_locking_callback(nullptr);
}
- /** Extract up to 32 bytes of entropy from the RNG state, mixing in new entropy from hasher. */
- void MixExtract(unsigned char* out, size_t num, CSHA512&& hasher)
+ /** Extract up to 32 bytes of entropy from the RNG state, mixing in new entropy from hasher.
+ *
+ * If this function has never been called with strong_seed = true, false is returned.
+ */
+ bool MixExtract(unsigned char* out, size_t num, CSHA512&& hasher, bool strong_seed)
{
assert(num <= 32);
unsigned char buf[64];
static_assert(sizeof(buf) == CSHA512::OUTPUT_SIZE, "Buffer needs to have hasher's output size");
+ bool ret;
{
LOCK(m_mutex);
+ ret = (m_strongly_seeded |= strong_seed);
// Write the current state of the RNG into the hasher
hasher.Write(m_state, 32);
// Write a new counter number into the state
@@ -363,6 +344,7 @@ struct RNGState {
// Best effort cleanup of internal state
hasher.Reset();
memory_cleanse(buf, 64);
+ return ret;
}
};
@@ -386,61 +368,128 @@ void LockingCallbackOpenSSL(int mode, int i, const char* file, int line) NO_THRE
}
}
-static void AddDataToRng(void* data, size_t len, RNGState& rng);
+static void SeedTimestamp(CSHA512& hasher)
+{
+ int64_t perfcounter = GetPerformanceCounter();
+ hasher.Write((const unsigned char*)&perfcounter, sizeof(perfcounter));
+}
-void RandAddSeedSleep()
+static void SeedFast(CSHA512& hasher)
{
- RNGState& rng = GetRNGState();
+ unsigned char buffer[32];
- int64_t nPerfCounter1 = GetPerformanceCounter();
- std::this_thread::sleep_for(std::chrono::milliseconds(1));
- int64_t nPerfCounter2 = GetPerformanceCounter();
+ // Stack pointer to indirectly commit to thread/callstack
+ const unsigned char* ptr = buffer;
+ hasher.Write((const unsigned char*)&ptr, sizeof(ptr));
- // Combine with and update state
- AddDataToRng(&nPerfCounter1, sizeof(nPerfCounter1), rng);
- AddDataToRng(&nPerfCounter2, sizeof(nPerfCounter2), rng);
+ // Hardware randomness is very fast when available; use it always.
+ bool have_hw_rand = GetHardwareRand(buffer);
+ if (have_hw_rand) hasher.Write(buffer, sizeof(buffer));
- memory_cleanse(&nPerfCounter1, sizeof(nPerfCounter1));
- memory_cleanse(&nPerfCounter2, sizeof(nPerfCounter2));
+ // High-precision timestamp
+ SeedTimestamp(hasher);
}
-static void AddDataToRng(void* data, size_t len, RNGState& rng) {
- CSHA512 hasher;
- hasher.Write((const unsigned char*)&len, sizeof(len));
- hasher.Write((const unsigned char*)data, len);
- rng.MixExtract(nullptr, 0, std::move(hasher));
+static void SeedSlow(CSHA512& hasher)
+{
+ unsigned char buffer[32];
+
+ // Everything that the 'fast' seeder includes
+ SeedFast(hasher);
+
+ // OS randomness
+ GetOSRand(buffer);
+ hasher.Write(buffer, sizeof(buffer));
+
+ // OpenSSL RNG (for now)
+ RAND_bytes(buffer, sizeof(buffer));
+ hasher.Write(buffer, sizeof(buffer));
+
+ // High-precision timestamp.
+ //
+ // Note that we also commit to a timestamp in the Fast seeder, so we indirectly commit to a
+ // benchmark of all the entropy gathering sources in this function).
+ SeedTimestamp(hasher);
}
-void GetStrongRandBytes(unsigned char* out, int num)
+static void SeedSleep(CSHA512& hasher)
{
- RNGState& rng = GetRNGState();
+ // Everything that the 'fast' seeder includes
+ SeedFast(hasher);
- assert(num <= 32);
- CSHA512 hasher;
- unsigned char buf[64];
+ // High-precision timestamp
+ SeedTimestamp(hasher);
- // First source: OpenSSL's RNG
- RandAddSeedPerfmon();
- GetRandBytes(buf, 32);
- hasher.Write(buf, 32);
+ // Sleep for 1ms
+ MilliSleep(1);
- // Second source: OS RNG
- GetOSRand(buf);
- hasher.Write(buf, 32);
+ // High-precision timestamp after sleeping (as we commit to both the time before and after, this measures the delay)
+ SeedTimestamp(hasher);
- // Third source: HW RNG, if available.
- if (GetHardwareRand(buf)) {
- hasher.Write(buf, 32);
+ // Windows performance monitor data (once every 10 minutes)
+ RandAddSeedPerfmon(hasher);
+}
+
+static void SeedStartup(CSHA512& hasher)
+{
+#ifdef WIN32
+ RAND_screen();
+#endif
+
+ // Everything that the 'slow' seeder includes.
+ SeedSlow(hasher);
+
+ // Windows performance monitor data.
+ RandAddSeedPerfmon(hasher);
+}
+
+enum class RNGLevel {
+ FAST, //!< Automatically called by GetRandBytes
+ SLOW, //!< Automatically called by GetStrongRandBytes
+ SLEEP, //!< Called by RandAddSeedSleep()
+};
+
+static void ProcRand(unsigned char* out, int num, RNGLevel level)
+{
+ // Make sure the RNG is initialized first (as all Seed* function possibly need hwrand to be available).
+ RNGState& rng = GetRNGState();
+
+ assert(num <= 32);
+
+ CSHA512 hasher;
+ switch (level) {
+ case RNGLevel::FAST:
+ SeedFast(hasher);
+ break;
+ case RNGLevel::SLOW:
+ SeedSlow(hasher);
+ break;
+ case RNGLevel::SLEEP:
+ SeedSleep(hasher);
+ break;
}
// Combine with and update state
- rng.MixExtract(out, num, std::move(hasher));
+ if (!rng.MixExtract(out, num, std::move(hasher), false)) {
+ // On the first invocation, also seed with SeedStartup().
+ CSHA512 startup_hasher;
+ SeedStartup(startup_hasher);
+ rng.MixExtract(out, num, std::move(startup_hasher), true);
+ }
- // Produce output
- memcpy(out, buf, num);
- memory_cleanse(buf, 64);
+ // For anything but the 'fast' level, feed the resulting RNG output (after an additional hashing step) back into OpenSSL.
+ if (level != RNGLevel::FAST) {
+ unsigned char buf[64];
+ CSHA512().Write(out, num).Finalize(buf);
+ RAND_add(buf, sizeof(buf), num);
+ memory_cleanse(buf, 64);
+ }
}
+void GetRandBytes(unsigned char* buf, int num) { ProcRand(buf, num, RNGLevel::FAST); }
+void GetStrongRandBytes(unsigned char* buf, int num) { ProcRand(buf, num, RNGLevel::SLOW); }
+void RandAddSeedSleep() { ProcRand(nullptr, 0, RNGLevel::SLEEP); }
+
uint64_t GetRand(uint64_t nMax)
{
if (nMax == 0)
@@ -539,8 +588,10 @@ bool Random_SanityCheck()
if (stop == start) return false;
// We called GetPerformanceCounter. Use it as entropy.
- RAND_add((const unsigned char*)&start, sizeof(start), 1);
- RAND_add((const unsigned char*)&stop, sizeof(stop), 1);
+ CSHA512 to_add;
+ to_add.Write((const unsigned char*)&start, sizeof(start));
+ to_add.Write((const unsigned char*)&stop, sizeof(stop));
+ GetRNGState().MixExtract(nullptr, 0, std::move(to_add), false);
return true;
}
@@ -571,7 +622,7 @@ FastRandomContext& FastRandomContext::operator=(FastRandomContext&& from) noexce
void RandomInit()
{
// Invoke RNG code to trigger initialization (if not already performed)
- GetRNGState();
+ ProcRand(nullptr, 0, RNGLevel::FAST);
ReportHardwareRand();
}
diff --git a/src/random.h b/src/random.h
index 221545a8ef..592f36a406 100644
--- a/src/random.h
+++ b/src/random.h
@@ -13,11 +13,13 @@
#include <stdint.h>
#include <limits>
-/* Seed OpenSSL PRNG with additional entropy data */
-void RandAddSeed();
-
/**
- * Functions to gather random data via the OpenSSL PRNG
+ * Generate random data via the internal PRNG.
+ *
+ * These functions are designed to be fast (sub microsecond), but do not necessarily
+ * meaningfully add entropy to the PRNG state.
+ *
+ * Thread-safe.
*/
void GetRandBytes(unsigned char* buf, int num);
uint64_t GetRand(uint64_t nMax);
@@ -25,21 +27,26 @@ int GetRandInt(int nMax);
uint256 GetRandHash();
/**
- * Add a little bit of randomness to the output of GetStrongRangBytes.
- * This sleeps for a millisecond, so should only be called when there is
- * no other work to be done.
+ * Gather entropy from various sources, feed it into the internal PRNG, and
+ * generate random data using it.
+ *
+ * This function will cause failure whenever the OS RNG fails.
+ *
+ * Thread-safe.
*/
-void RandAddSeedSleep();
+void GetStrongRandBytes(unsigned char* buf, int num);
/**
- * Function to gather random data from multiple sources, failing whenever any
- * of those sources fail to provide a result.
+ * Sleep for 1ms, gather entropy from various sources, and feed them to the PRNG state.
+ *
+ * Thread-safe.
*/
-void GetStrongRandBytes(unsigned char* buf, int num);
+void RandAddSeedSleep();
/**
* Fast randomness source. This is seeded once with secure random data, but
- * is completely deterministic and insecure after that.
+ * is completely deterministic and does not gather more entropy after that.
+ *
* This class is not thread-safe.
*/
class FastRandomContext {
diff --git a/src/scheduler.cpp b/src/scheduler.cpp
index b2da62fc75..fdc859b3a0 100644
--- a/src/scheduler.cpp
+++ b/src/scheduler.cpp
@@ -41,7 +41,7 @@ void CScheduler::serviceQueue()
try {
if (!shouldStop() && taskQueue.empty()) {
reverse_lock<boost::unique_lock<boost::mutex> > rlock(lock);
- // Use this chance to get a tiny bit more entropy
+ // Use this chance to get more entropy
RandAddSeedSleep();
}
while (!shouldStop() && taskQueue.empty()) {