From 6a57ca91da23c6a5d91399ffc7fc09a99b6d4c76 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Mon, 17 Dec 2018 17:00:06 -0800 Subject: Use FRC::randbytes instead of reading >32 bytes from RNG There was only one place in the codebase where we're directly reading >32 bytes from the RNG. One possibility would be to make the built-in RNG support large reads, but using FastRandomContext lets us reuse code better. There is no change in behavior here, because the FastRandomContext constructor uses GetRandBytes internally. --- src/qt/test/paymentservertests.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/qt/test/paymentservertests.cpp b/src/qt/test/paymentservertests.cpp index 94907595f5..f0eca899fc 100644 --- a/src/qt/test/paymentservertests.cpp +++ b/src/qt/test/paymentservertests.cpp @@ -181,12 +181,12 @@ void PaymentServerTests::paymentServerTests() QCOMPARE(PaymentServer::verifyExpired(r.paymentRequest.getDetails()), true); // Test BIP70 DoS protection: - unsigned char randData[BIP70_MAX_PAYMENTREQUEST_SIZE + 1]; - GetRandBytes(randData, sizeof(randData)); + auto randdata = FastRandomContext().randbytes(BIP70_MAX_PAYMENTREQUEST_SIZE + 1); + // Write data to a temp file: QTemporaryFile tempFile; tempFile.open(); - tempFile.write((const char*)randData, sizeof(randData)); + tempFile.write((const char*)randdata.data(), randdata.size()); tempFile.close(); // compares 50001 <= BIP70_MAX_PAYMENTREQUEST_SIZE == false QCOMPARE(PaymentServer::verifySize(tempFile.size()), false); -- cgit v1.2.3 From 2d1cc5093949f8ea9487a68724162c8b39035ad8 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 19 Dec 2018 01:50:36 -0800 Subject: Don't log RandAddSeedPerfmon details These are hard to deal with, as in a follow-up this function can get called before the logging infrastructure is initialized. --- src/random.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index f8ffda136d..7dacc477d4 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -166,13 +166,13 @@ static void RandAddSeedPerfmon() if (ret == ERROR_SUCCESS) { RAND_add(vData.data(), nSize, nSize / 100.0); memory_cleanse(vData.data(), nSize); - LogPrint(BCLog::RAND, "%s: %lu bytes\n", __func__, nSize); } else { - static bool warned = false; // Warn only once - if (!warned) { - LogPrintf("%s: Warning: RegQueryValueExA(HKEY_PERFORMANCE_DATA) failed with code %i\n", __func__, ret); - warned = true; - } + // Performance data is only a best-effort attempt at improving the + // situation when the OS randomness (and other sources) aren't + // adequate. As a result, failure to read it is isn't considered critical, + // so we don't call RandFailure(). + // TODO: Add logging when the logger is made functional before global + // constructors have been invoked. } #endif } -- cgit v1.2.3 From 05fde14e3afe6f7156ebb6df6cd0e3ae12635b89 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Mon, 17 Dec 2018 16:48:21 -0800 Subject: Automatically initialize RNG on first use. --- src/bench/bench_bitcoin.cpp | 2 -- src/random.cpp | 68 +++++++++++++++++++++++++++++++++------------ src/random.h | 7 ++++- src/test/test_bitcoin.cpp | 1 - 4 files changed, 56 insertions(+), 22 deletions(-) diff --git a/src/bench/bench_bitcoin.cpp b/src/bench/bench_bitcoin.cpp index 32faba86b4..b804a84478 100644 --- a/src/bench/bench_bitcoin.cpp +++ b/src/bench/bench_bitcoin.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include #include @@ -67,7 +66,6 @@ int main(int argc, char** argv) const fs::path bench_datadir{SetDataDir()}; SHA256AutoDetect(); - RandomInit(); ECC_Start(); SetupEnvironment(); diff --git a/src/random.cpp b/src/random.cpp index 7dacc477d4..caeb87ff05 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -74,7 +74,6 @@ static inline int64_t GetPerformanceCounter() #endif } - #if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) static std::atomic hwrand_initialized{false}; static bool rdrand_supported = false; @@ -83,13 +82,24 @@ static void RDRandInit() { uint32_t eax, ebx, ecx, edx; if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) && (ecx & CPUID_F1_ECX_RDRAND)) { - LogPrintf("Using RdRand as an additional entropy source\n"); rdrand_supported = true; } hwrand_initialized.store(true); } + +static void RDRandReport() +{ + assert(hwrand_initialized.load(std::memory_order_relaxed)); + if (rdrand_supported) { + // This must be done in a separate function, as HWRandInit() may be indirectly called + // from global constructors, before logging is initialized. + LogPrintf("Using RdRand as an additional entropy source\n"); + } +} + #else static void RDRandInit() {} +static void RDRandReport() {} #endif static bool GetHWRand(unsigned char* ent32) { @@ -279,6 +289,26 @@ void GetRandBytes(unsigned char* buf, int num) } } +namespace { +struct RNGState { + Mutex m_mutex; + unsigned char m_state[32] = {0}; + uint64_t m_counter = 0; + + explicit RNGState() { + RDRandInit(); + } +}; + +RNGState& GetRNGState() +{ + // This C++11 idiom relies on the guarantee that static variable are initialized + // on first call, even when multiple parallel calls are permitted. + static std::unique_ptr g_rng{new RNGState()}; + return *g_rng; +} +} + static void AddDataToRng(void* data, size_t len); void RandAddSeedSleep() @@ -295,29 +325,28 @@ void RandAddSeedSleep() memory_cleanse(&nPerfCounter2, sizeof(nPerfCounter2)); } - -static Mutex cs_rng_state; -static unsigned char rng_state[32] = {0}; -static uint64_t rng_counter = 0; - static void AddDataToRng(void* data, size_t len) { + RNGState& rng = GetRNGState(); + CSHA512 hasher; hasher.Write((const unsigned char*)&len, sizeof(len)); hasher.Write((const unsigned char*)data, len); unsigned char buf[64]; { - WAIT_LOCK(cs_rng_state, lock); - hasher.Write(rng_state, sizeof(rng_state)); - hasher.Write((const unsigned char*)&rng_counter, sizeof(rng_counter)); - ++rng_counter; + WAIT_LOCK(rng.m_mutex, lock); + hasher.Write(rng.m_state, sizeof(rng.m_state)); + hasher.Write((const unsigned char*)&rng.m_counter, sizeof(rng.m_counter)); + ++rng.m_counter; hasher.Finalize(buf); - memcpy(rng_state, buf + 32, 32); + memcpy(rng.m_state, buf + 32, 32); } memory_cleanse(buf, 64); } void GetStrongRandBytes(unsigned char* out, int num) { + RNGState& rng = GetRNGState(); + assert(num <= 32); CSHA512 hasher; unsigned char buf[64]; @@ -338,12 +367,12 @@ void GetStrongRandBytes(unsigned char* out, int num) // Combine with and update state { - WAIT_LOCK(cs_rng_state, lock); - hasher.Write(rng_state, sizeof(rng_state)); - hasher.Write((const unsigned char*)&rng_counter, sizeof(rng_counter)); - ++rng_counter; + WAIT_LOCK(rng.m_mutex, lock); + hasher.Write(rng.m_state, sizeof(rng.m_state)); + hasher.Write((const unsigned char*)&rng.m_counter, sizeof(rng.m_counter)); + ++rng.m_counter; hasher.Finalize(buf); - memcpy(rng_state, buf + 32, 32); + memcpy(rng.m_state, buf + 32, 32); } // Produce output @@ -480,5 +509,8 @@ FastRandomContext& FastRandomContext::operator=(FastRandomContext&& from) noexce void RandomInit() { - RDRandInit(); + // Invoke RNG code to trigger initialization (if not already performed) + GetRNGState(); + + RDRandReport(); } diff --git a/src/random.h b/src/random.h index 00e90abbc5..221545a8ef 100644 --- a/src/random.h +++ b/src/random.h @@ -178,7 +178,12 @@ void GetOSRand(unsigned char *ent32); */ bool Random_SanityCheck(); -/** Initialize the RNG. */ +/** + * Initialize global RNG state and log any CPU features that are used. + * + * Calling this function is optional. RNG state will be initialized when first + * needed if it is not called. + */ void RandomInit(); #endif // BITCOIN_RANDOM_H diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index 858bb512fc..23b2076041 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -49,7 +49,6 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName) : m_path_root(fs::temp_directory_path() / "test_bitcoin" / strprintf("%lu_%i", (unsigned long)GetTime(), (int)(InsecureRandRange(1 << 30)))) { SHA256AutoDetect(); - RandomInit(); ECC_Start(); SetupEnvironment(); SetupNetworking(); -- cgit v1.2.3 From d3f54d1c82b131d817b20cd9daa75f9d3c9475e1 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 16 Jan 2019 15:15:21 -0800 Subject: Rename some hardware RNG related functions --- src/random.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index caeb87ff05..f31d14acf6 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -78,7 +78,7 @@ static inline int64_t GetPerformanceCounter() static std::atomic hwrand_initialized{false}; static bool rdrand_supported = false; static constexpr uint32_t CPUID_F1_ECX_RDRAND = 0x40000000; -static void RDRandInit() +static void InitHardwareRand() { uint32_t eax, ebx, ecx, edx; if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) && (ecx & CPUID_F1_ECX_RDRAND)) { @@ -87,7 +87,7 @@ static void RDRandInit() hwrand_initialized.store(true); } -static void RDRandReport() +static void ReportHardwareRand() { assert(hwrand_initialized.load(std::memory_order_relaxed)); if (rdrand_supported) { @@ -98,11 +98,16 @@ static void RDRandReport() } #else -static void RDRandInit() {} -static void RDRandReport() {} +/* Access to other hardware random number generators could be added here later, + * assuming it is sufficiently fast (in the order of a few hundred CPU cycles). + * Slower sources should probably be invoked separately, and/or only from + * RandAddSeedSleep (which is called during idle background operation). + */ +static void InitHardwareRand() {} +static void ReportHardwareRand() {} #endif -static bool GetHWRand(unsigned char* ent32) { +static bool GetHardwareRand(unsigned char* ent32) { #if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) assert(hwrand_initialized.load(std::memory_order_relaxed)); if (rdrand_supported) { @@ -296,7 +301,7 @@ struct RNGState { uint64_t m_counter = 0; explicit RNGState() { - RDRandInit(); + InitHardwareRand(); } }; @@ -361,7 +366,7 @@ void GetStrongRandBytes(unsigned char* out, int num) hasher.Write(buf, 32); // Third source: HW RNG, if available. - if (GetHWRand(buf)) { + if (GetHardwareRand(buf)) { hasher.Write(buf, 32); } @@ -512,5 +517,5 @@ void RandomInit() // Invoke RNG code to trigger initialization (if not already performed) GetRNGState(); - RDRandReport(); + ReportHardwareRand(); } -- cgit v1.2.3 From aae8b9bf0f4fd2b801ee72cf191588c8b3a67c3c Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Mon, 17 Dec 2018 17:03:30 -0800 Subject: Add thread safety annotations to RNG state --- src/random.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index f31d14acf6..6b7962aa13 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -297,10 +297,11 @@ void GetRandBytes(unsigned char* buf, int num) namespace { struct RNGState { Mutex m_mutex; - unsigned char m_state[32] = {0}; - uint64_t m_counter = 0; + unsigned char m_state[32] GUARDED_BY(m_mutex) = {0}; + uint64_t m_counter GUARDED_BY(m_mutex) = 0; - explicit RNGState() { + RNGState() + { InitHardwareRand(); } }; -- cgit v1.2.3 From 2ccc3d3aa346e96206281a391bc29874cf5ee7f4 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Mon, 17 Dec 2018 16:04:35 -0800 Subject: Abstract out seeding/extracting entropy into RNGState::MixExtract --- src/crypto/sha512.h | 2 +- src/random.cpp | 60 ++++++++++++++++++++++++++++++++--------------------- 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/src/crypto/sha512.h b/src/crypto/sha512.h index cd1023bc85..4118ac1b18 100644 --- a/src/crypto/sha512.h +++ b/src/crypto/sha512.h @@ -17,7 +17,7 @@ private: uint64_t bytes; public: - static const size_t OUTPUT_SIZE = 64; + static constexpr size_t OUTPUT_SIZE = 64; CSHA512(); CSHA512& Write(const unsigned char* data, size_t len); diff --git a/src/random.cpp b/src/random.cpp index 6b7962aa13..c78848d56e 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -304,6 +304,34 @@ struct RNGState { { InitHardwareRand(); } + + /** 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) + { + assert(num <= 32); + unsigned char buf[64]; + static_assert(sizeof(buf) == CSHA512::OUTPUT_SIZE, "Buffer needs to have hasher's output size"); + { + LOCK(m_mutex); + // Write the current state of the RNG into the hasher + hasher.Write(m_state, 32); + // Write a new counter number into the state + hasher.Write((const unsigned char*)&m_counter, sizeof(m_counter)); + ++m_counter; + // Finalize the hasher + hasher.Finalize(buf); + // Store the last 32 bytes of the hash output as new RNG state. + memcpy(m_state, buf + 32, 32); + } + // If desired, copy (up to) the first 32 bytes of the hash output as output. + if (num) { + assert(out != nullptr); + memcpy(out, buf, num); + } + // Best effort cleanup of internal state + hasher.Reset(); + memory_cleanse(buf, 64); + } }; RNGState& GetRNGState() @@ -315,38 +343,29 @@ RNGState& GetRNGState() } } -static void AddDataToRng(void* data, size_t len); +static void AddDataToRng(void* data, size_t len, RNGState& rng); void RandAddSeedSleep() { + RNGState& rng = GetRNGState(); + int64_t nPerfCounter1 = GetPerformanceCounter(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); int64_t nPerfCounter2 = GetPerformanceCounter(); // Combine with and update state - AddDataToRng(&nPerfCounter1, sizeof(nPerfCounter1)); - AddDataToRng(&nPerfCounter2, sizeof(nPerfCounter2)); + AddDataToRng(&nPerfCounter1, sizeof(nPerfCounter1), rng); + AddDataToRng(&nPerfCounter2, sizeof(nPerfCounter2), rng); memory_cleanse(&nPerfCounter1, sizeof(nPerfCounter1)); memory_cleanse(&nPerfCounter2, sizeof(nPerfCounter2)); } -static void AddDataToRng(void* data, size_t len) { - RNGState& rng = GetRNGState(); - +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); - unsigned char buf[64]; - { - WAIT_LOCK(rng.m_mutex, lock); - hasher.Write(rng.m_state, sizeof(rng.m_state)); - hasher.Write((const unsigned char*)&rng.m_counter, sizeof(rng.m_counter)); - ++rng.m_counter; - hasher.Finalize(buf); - memcpy(rng.m_state, buf + 32, 32); - } - memory_cleanse(buf, 64); + rng.MixExtract(nullptr, 0, std::move(hasher)); } void GetStrongRandBytes(unsigned char* out, int num) @@ -372,14 +391,7 @@ void GetStrongRandBytes(unsigned char* out, int num) } // Combine with and update state - { - WAIT_LOCK(rng.m_mutex, lock); - hasher.Write(rng.m_state, sizeof(rng.m_state)); - hasher.Write((const unsigned char*)&rng.m_counter, sizeof(rng.m_counter)); - ++rng.m_counter; - hasher.Finalize(buf); - memcpy(rng.m_state, buf + 32, 32); - } + rng.MixExtract(out, num, std::move(hasher)); // Produce output memcpy(out, buf, num); -- cgit v1.2.3 From 16e40a8b562ad849a5f5e8b21ceb375e46038243 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Tue, 15 Jan 2019 16:03:54 -0800 Subject: Integrate util/system's CInit into RNGState This guarantees that OpenSSL is initialized properly whenever randomness is used, even when that randomness is invoked from global constructors. Note that this patch uses Mutex directly, rather than CCriticalSection. This is because the lock-detection code is not necessarily initialized during global constructors. --- src/random.cpp | 43 +++++++++++++++++++++++++++++++++++++++++++ src/util/system.cpp | 51 --------------------------------------------------- 2 files changed, 43 insertions(+), 51 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index c78848d56e..6699318ee0 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -47,6 +47,7 @@ #include #include +#include [[noreturn]] static void RandFailure() { @@ -294,15 +295,46 @@ void GetRandBytes(unsigned char* buf, int num) } } +void LockingCallbackOpenSSL(int mode, int i, const char* file, int line); + namespace { + struct RNGState { Mutex m_mutex; unsigned char m_state[32] GUARDED_BY(m_mutex) = {0}; uint64_t m_counter GUARDED_BY(m_mutex) = 0; + std::unique_ptr m_mutex_openssl; RNGState() { InitHardwareRand(); + + // Init OpenSSL library multithreading support + m_mutex_openssl.reset(new Mutex[CRYPTO_num_locks()]); + CRYPTO_set_locking_callback(LockingCallbackOpenSSL); + + // OpenSSL can optionally load a config file which lists optional loadable modules and engines. + // We don't use them so we don't require the config. However some of our libs may call functions + // which attempt to load the config file, possibly resulting in an exit() or crash if it is missing + // 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() + { + // Securely erase the memory used by the OpenSSL PRNG + RAND_cleanup(); + // Shutdown OpenSSL library multithreading support + CRYPTO_set_locking_callback(nullptr); } /** Extract up to 32 bytes of entropy from the RNG state, mixing in new entropy from hasher. */ @@ -343,6 +375,17 @@ RNGState& GetRNGState() } } +void LockingCallbackOpenSSL(int mode, int i, const char* file, int line) NO_THREAD_SAFETY_ANALYSIS +{ + RNGState& rng = GetRNGState(); + + if (mode & CRYPTO_LOCK) { + rng.m_mutex_openssl[i].lock(); + } else { + rng.m_mutex_openssl[i].unlock(); + } +} + static void AddDataToRng(void* data, size_t len, RNGState& rng); void RandAddSeedSleep() diff --git a/src/util/system.cpp b/src/util/system.cpp index 8e201ec590..6cd4c46bdb 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -73,9 +73,6 @@ #include #endif -#include -#include -#include #include // Application startup time (used for uptime calculation) @@ -86,54 +83,6 @@ const char * const BITCOIN_PID_FILENAME = "bitcoind.pid"; ArgsManager gArgs; -/** Init OpenSSL library multithreading support */ -static std::unique_ptr ppmutexOpenSSL; -void locking_callback(int mode, int i, const char* file, int line) NO_THREAD_SAFETY_ANALYSIS -{ - if (mode & CRYPTO_LOCK) { - ENTER_CRITICAL_SECTION(ppmutexOpenSSL[i]); - } else { - LEAVE_CRITICAL_SECTION(ppmutexOpenSSL[i]); - } -} - -// Singleton for wrapping OpenSSL setup/teardown. -class CInit -{ -public: - CInit() - { - // Init OpenSSL library multithreading support - ppmutexOpenSSL.reset(new CCriticalSection[CRYPTO_num_locks()]); - CRYPTO_set_locking_callback(locking_callback); - - // OpenSSL can optionally load a config file which lists optional loadable modules and engines. - // We don't use them so we don't require the config. However some of our libs may call functions - // which attempt to load the config file, possibly resulting in an exit() or crash if it is missing - // 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(); - } - ~CInit() - { - // Securely erase the memory used by the PRNG - RAND_cleanup(); - // Shutdown OpenSSL library multithreading support - CRYPTO_set_locking_callback(nullptr); - // Clear the set of locks now to maintain symmetry with the constructor. - ppmutexOpenSSL.reset(); - } -} -instance_of_cinit; - /** A map that contains all the currently held directory locks. After * successful locking, these will be held here until the global destructor * cleans them up and thus automatically unlocks them, or ReleaseDirectoryLocks -- cgit v1.2.3 From 9d7032e4f066777c97c58b1394884716e213790a Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Thu, 13 Dec 2018 18:37:29 -0800 Subject: 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(). --- src/random.cpp | 185 ++++++++++++++++++++++++++++++++++-------------------- src/random.h | 31 +++++---- src/scheduler.cpp | 2 +- 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 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 #include -/* 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 > rlock(lock); - // Use this chance to get a tiny bit more entropy + // Use this chance to get more entropy RandAddSeedSleep(); } while (!shouldStop() && taskQueue.empty()) { -- cgit v1.2.3 From 4ea8e50837a0932b31a241988fd68d6730a2048a Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Mon, 17 Dec 2018 16:22:22 -0800 Subject: Remove hwrand_initialized. All access to hwrand is now gated by GetRNGState, which initializes the hwrand code. --- src/random.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index ca54cc9962..3f2465b2ed 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -76,7 +76,6 @@ static inline int64_t GetPerformanceCounter() } #if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) -static std::atomic hwrand_initialized{false}; static bool rdrand_supported = false; static constexpr uint32_t CPUID_F1_ECX_RDRAND = 0x40000000; static void InitHardwareRand() @@ -85,12 +84,10 @@ static void InitHardwareRand() if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) && (ecx & CPUID_F1_ECX_RDRAND)) { rdrand_supported = true; } - hwrand_initialized.store(true); } static void ReportHardwareRand() { - assert(hwrand_initialized.load(std::memory_order_relaxed)); if (rdrand_supported) { // This must be done in a separate function, as HWRandInit() may be indirectly called // from global constructors, before logging is initialized. @@ -110,7 +107,6 @@ static void ReportHardwareRand() {} static bool GetHardwareRand(unsigned char* ent32) { #if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) - assert(hwrand_initialized.load(std::memory_order_relaxed)); if (rdrand_supported) { uint8_t ok; // Not all assemblers support the rdrand instruction, write it in hex. -- cgit v1.2.3 From a1f252eda87356fa329c838a7bf569808489648f Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Mon, 17 Dec 2018 15:11:33 -0800 Subject: Sprinkle some sweet noexcepts over the RNG code --- src/random.cpp | 49 ++++++++++++++++++++++++++++++++----------------- src/random.h | 28 ++++++++++++++-------------- 2 files changed, 46 insertions(+), 31 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index 3f2465b2ed..e4c23ceac6 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -55,7 +55,7 @@ std::abort(); } -static inline int64_t GetPerformanceCounter() +static inline int64_t GetPerformanceCounter() noexcept { // Read the hardware time stamp counter when available. // See https://en.wikipedia.org/wiki/Time_Stamp_Counter for more information. @@ -105,7 +105,7 @@ static void InitHardwareRand() {} static void ReportHardwareRand() {} #endif -static bool GetHardwareRand(unsigned char* ent32) { +static bool GetHardwareRand(unsigned char* ent32) noexcept { #if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) if (rdrand_supported) { uint8_t ok; @@ -285,7 +285,7 @@ struct RNGState { bool m_strongly_seeded GUARDED_BY(m_mutex) = false; std::unique_ptr m_mutex_openssl; - RNGState() + RNGState() noexcept { InitHardwareRand(); @@ -313,7 +313,7 @@ struct RNGState { * * 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) + bool MixExtract(unsigned char* out, size_t num, CSHA512&& hasher, bool strong_seed) noexcept { assert(num <= 32); unsigned char buf[64]; @@ -344,7 +344,7 @@ struct RNGState { } }; -RNGState& GetRNGState() +RNGState& GetRNGState() noexcept { // This C++11 idiom relies on the guarantee that static variable are initialized // on first call, even when multiple parallel calls are permitted. @@ -364,13 +364,28 @@ void LockingCallbackOpenSSL(int mode, int i, const char* file, int line) NO_THRE } } -static void SeedTimestamp(CSHA512& hasher) +/* A note on the use of noexcept in the seeding functions below: + * + * None of the RNG code should ever throw any exception, with the sole exception + * of MilliSleep in SeedSleep, which can (and does) support interruptions which + * cause a boost::thread_interrupted to be thrown. + * + * This means that SeedSleep, and all functions that invoke it are throwing. + * However, we know that GetRandBytes() and GetStrongRandBytes() never trigger + * this sleeping logic, so they are noexcept. The same is true for all the + * GetRand*() functions that use GetRandBytes() indirectly. + * + * TODO: After moving away from interruptible boost-based thread management, + * everything can become noexcept here. + */ + +static void SeedTimestamp(CSHA512& hasher) noexcept { int64_t perfcounter = GetPerformanceCounter(); hasher.Write((const unsigned char*)&perfcounter, sizeof(perfcounter)); } -static void SeedFast(CSHA512& hasher) +static void SeedFast(CSHA512& hasher) noexcept { unsigned char buffer[32]; @@ -386,7 +401,7 @@ static void SeedFast(CSHA512& hasher) SeedTimestamp(hasher); } -static void SeedSlow(CSHA512& hasher) +static void SeedSlow(CSHA512& hasher) noexcept { unsigned char buffer[32]; @@ -426,7 +441,7 @@ static void SeedSleep(CSHA512& hasher) RandAddSeedPerfmon(hasher); } -static void SeedStartup(CSHA512& hasher) +static void SeedStartup(CSHA512& hasher) noexcept { #ifdef WIN32 RAND_screen(); @@ -482,11 +497,11 @@ static void ProcRand(unsigned char* out, int num, RNGLevel level) } } -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 GetRandBytes(unsigned char* buf, int num) noexcept { ProcRand(buf, num, RNGLevel::FAST); } +void GetStrongRandBytes(unsigned char* buf, int num) noexcept { ProcRand(buf, num, RNGLevel::SLOW); } void RandAddSeedSleep() { ProcRand(nullptr, 0, RNGLevel::SLEEP); } -uint64_t GetRand(uint64_t nMax) +uint64_t GetRand(uint64_t nMax) noexcept { if (nMax == 0) return 0; @@ -501,12 +516,12 @@ uint64_t GetRand(uint64_t nMax) return (nRand % nMax); } -int GetRandInt(int nMax) +int GetRandInt(int nMax) noexcept { return GetRand(nMax); } -uint256 GetRandHash() +uint256 GetRandHash() noexcept { uint256 hash; GetRandBytes((unsigned char*)&hash, sizeof(hash)); @@ -520,7 +535,7 @@ void FastRandomContext::RandomSeed() requires_seed = false; } -uint256 FastRandomContext::rand256() +uint256 FastRandomContext::rand256() noexcept { if (bytebuf_size < 32) { FillByteBuffer(); @@ -541,7 +556,7 @@ std::vector FastRandomContext::randbytes(size_t len) return ret; } -FastRandomContext::FastRandomContext(const uint256& seed) : requires_seed(false), bytebuf_size(0), bitbuf_size(0) +FastRandomContext::FastRandomContext(const uint256& seed) noexcept : requires_seed(false), bytebuf_size(0), bitbuf_size(0) { rng.SetKey(seed.begin(), 32); } @@ -592,7 +607,7 @@ bool Random_SanityCheck() return true; } -FastRandomContext::FastRandomContext(bool fDeterministic) : requires_seed(!fDeterministic), bytebuf_size(0), bitbuf_size(0) +FastRandomContext::FastRandomContext(bool fDeterministic) noexcept : requires_seed(!fDeterministic), bytebuf_size(0), bitbuf_size(0) { if (!fDeterministic) { return; diff --git a/src/random.h b/src/random.h index 592f36a406..038e3ecd78 100644 --- a/src/random.h +++ b/src/random.h @@ -21,10 +21,10 @@ * * Thread-safe. */ -void GetRandBytes(unsigned char* buf, int num); -uint64_t GetRand(uint64_t nMax); -int GetRandInt(int nMax); -uint256 GetRandHash(); +void GetRandBytes(unsigned char* buf, int num) noexcept; +uint64_t GetRand(uint64_t nMax) noexcept; +int GetRandInt(int nMax) noexcept; +uint256 GetRandHash() noexcept; /** * Gather entropy from various sources, feed it into the internal PRNG, and @@ -34,7 +34,7 @@ uint256 GetRandHash(); * * Thread-safe. */ -void GetStrongRandBytes(unsigned char* buf, int num); +void GetStrongRandBytes(unsigned char* buf, int num) noexcept; /** * Sleep for 1ms, gather entropy from various sources, and feed them to the PRNG state. @@ -78,10 +78,10 @@ private: } public: - explicit FastRandomContext(bool fDeterministic = false); + explicit FastRandomContext(bool fDeterministic = false) noexcept; /** Initialize with explicit seed (only for testing) */ - explicit FastRandomContext(const uint256& seed); + explicit FastRandomContext(const uint256& seed) noexcept; // Do not permit copying a FastRandomContext (move it, or create a new one to get reseeded). FastRandomContext(const FastRandomContext&) = delete; @@ -92,7 +92,7 @@ public: FastRandomContext& operator=(FastRandomContext&& from) noexcept; /** Generate a random 64-bit integer. */ - uint64_t rand64() + uint64_t rand64() noexcept { if (bytebuf_size < 8) FillByteBuffer(); uint64_t ret = ReadLE64(bytebuf + 64 - bytebuf_size); @@ -101,7 +101,7 @@ public: } /** Generate a random (bits)-bit integer. */ - uint64_t randbits(int bits) { + uint64_t randbits(int bits) noexcept { if (bits == 0) { return 0; } else if (bits > 32) { @@ -116,7 +116,7 @@ public: } /** Generate a random integer in the range [0..range). */ - uint64_t randrange(uint64_t range) + uint64_t randrange(uint64_t range) noexcept { --range; int bits = CountBits(range); @@ -130,19 +130,19 @@ public: std::vector randbytes(size_t len); /** Generate a random 32-bit integer. */ - uint32_t rand32() { return randbits(32); } + uint32_t rand32() noexcept { return randbits(32); } /** generate a random uint256. */ - uint256 rand256(); + uint256 rand256() noexcept; /** Generate a random boolean. */ - bool randbool() { return randbits(1); } + bool randbool() noexcept { return randbits(1); } // Compatibility with the C++11 UniformRandomBitGenerator concept typedef uint64_t result_type; static constexpr uint64_t min() { return 0; } static constexpr uint64_t max() { return std::numeric_limits::max(); } - inline uint64_t operator()() { return rand64(); } + inline uint64_t operator()() noexcept { return rand64(); } }; /** More efficient than using std::shuffle on a FastRandomContext. -- cgit v1.2.3 From 152146e782d401aa1ce7d989d62306aabc85f22e Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Fri, 4 Jan 2019 02:00:44 -0800 Subject: DRY: Implement GetRand using FastRandomContext::randrange --- src/random.cpp | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index e4c23ceac6..f52b5837aa 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -503,17 +503,7 @@ void RandAddSeedSleep() { ProcRand(nullptr, 0, RNGLevel::SLEEP); } uint64_t GetRand(uint64_t nMax) noexcept { - if (nMax == 0) - return 0; - - // The range of the random source must be a multiple of the modulus - // to give every possible output value an equal possibility - uint64_t nRange = (std::numeric_limits::max() / nMax) * nMax; - uint64_t nRand = 0; - do { - GetRandBytes((unsigned char*)&nRand, sizeof(nRand)); - } while (nRand >= nRange); - return (nRand % nMax); + return FastRandomContext().randrange(nMax); } int GetRandInt(int nMax) noexcept -- cgit v1.2.3 From cddb31bb0a132afa50b5350196cf26f0064fe3e2 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Thu, 10 Jan 2019 18:19:50 -0800 Subject: Encapsulate RNGState better --- src/random.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index f52b5837aa..fe5341ba5f 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -278,13 +278,14 @@ void LockingCallbackOpenSSL(int mode, int i, const char* file, int line); namespace { -struct RNGState { +class 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 m_mutex_openssl; +public: RNGState() noexcept { InitHardwareRand(); @@ -342,6 +343,8 @@ struct RNGState { memory_cleanse(buf, 64); return ret; } + + Mutex& GetOpenSSLMutex(int i) { return m_mutex_openssl[i]; } }; RNGState& GetRNGState() noexcept @@ -358,9 +361,9 @@ void LockingCallbackOpenSSL(int mode, int i, const char* file, int line) NO_THRE RNGState& rng = GetRNGState(); if (mode & CRYPTO_LOCK) { - rng.m_mutex_openssl[i].lock(); + rng.GetOpenSSLMutex(i).lock(); } else { - rng.m_mutex_openssl[i].unlock(); + rng.GetOpenSSLMutex(i).unlock(); } } -- cgit v1.2.3 From f2e60ca98530e0a865ff6c6fd3c5633aec11a515 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Thu, 10 Jan 2019 18:34:17 -0800 Subject: Use secure allocator for RNG state --- src/random.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index fe5341ba5f..4cd6c9ddc1 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -19,6 +19,8 @@ #include #include +#include + #ifndef WIN32 #include #include @@ -351,8 +353,8 @@ RNGState& GetRNGState() noexcept { // This C++11 idiom relies on the guarantee that static variable are initialized // on first call, even when multiple parallel calls are permitted. - static std::unique_ptr g_rng{new RNGState()}; - return *g_rng; + static std::vector> g_rng(1); + return g_rng[0]; } } -- cgit v1.2.3 From 223de8d94d6522f795ec3c2e7db27469f24aa68c Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sun, 13 Jan 2019 10:51:17 -0800 Subject: Document RNG design in random.h --- src/random.cpp | 8 ++++++++ src/random.h | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/random.cpp b/src/random.cpp index 4cd6c9ddc1..3b7f7910b0 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -282,6 +282,14 @@ namespace { class RNGState { Mutex m_mutex; + /* The RNG state consists of 256 bits of entropy, taken from the output of + * one operation's SHA512 output, and fed as input to the next one. + * Carrying 256 bits of entropy should be sufficient to guarantee + * unpredictability as long as any entropy source was ever unpredictable + * to an attacker. To protect against situations where an attacker might + * observe the RNG's state, fresh entropy is always mixed when + * GetStrongRandBytes is called. + */ 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; diff --git a/src/random.h b/src/random.h index 038e3ecd78..4c73f3822a 100644 --- a/src/random.h +++ b/src/random.h @@ -13,6 +13,49 @@ #include #include +/** + * Overall design of the RNG and entropy sources. + * + * We maintain a single global 256-bit RNG state for all high-quality randomness. + * The following (classes of) functions interact with that state by mixing in new + * entropy, and optionally extracting random output from it: + * + * - The GetRand*() class of functions, as well as construction of FastRandomContext objects, + * perform 'fast' seeding, consisting of mixing in: + * - A stack pointer (indirectly committing to calling thread and call stack) + * - A high-precision timestamp (rdtsc when available, c++ high_resolution_clock otherwise) + * - Hardware RNG (rdrand) when available. + * These entropy sources are very fast, and only designed to protect against situations + * where a VM state restore/copy results in multiple systems with the same randomness. + * FastRandomContext on the other hand does not protect against this once created, but + * is even faster (and acceptable to use inside tight loops). + * + * - The GetStrongRand*() class of function perform 'slow' seeding, including everything + * that fast seeding includes, but additionally: + * - OS entropy (/dev/urandom, getrandom(), ...). The application will terminate if + * this entropy source fails. + * - Bytes from OpenSSL's RNG (which itself may be seeded from various sources) + * - Another high-precision timestamp (indirectly committing to a benchmark of all the + * previous sources). + * These entropy sources are slower, but designed to make sure the RNG state contains + * fresh data that is unpredictable to attackers. + * + * - RandAddSeedSleep() seeds everything that fast seeding includes, but additionally: + * - A high-precision timestamp before and after sleeping 1ms. + * - (On Windows) Once every 10 minutes, performance monitoring data from the OS. + * These just exploit the fact the system is idle to improve the quality of the RNG + * slightly. + * + * On first use of the RNG (regardless of what function is called first), all entropy + * sources used in the 'slow' seeder are included, but also: + * - (On Windows) Performance monitoring data from the OS. + * - (On Windows) Through OpenSSL, the screen contents. + * + * When mixing in new entropy, H = SHA512(entropy || old_rng_state) is computed, and + * (up to) the first 32 bytes of H are produced as output, while the last 32 bytes + * become the new RNG state. +*/ + /** * Generate random data via the internal PRNG. * -- cgit v1.2.3