diff --git a/src/init.cpp b/src/init.cpp --- a/src/init.cpp +++ b/src/init.cpp @@ -1532,6 +1532,10 @@ "OS cryptographic RNG sanity check failure. Aborting.")); } + if (!ChronoSanityCheck()) { + return InitError(Untranslated("Clock epoch mismatch. Aborting.")); + } + return true; } diff --git a/src/test/sanity_tests.cpp b/src/test/sanity_tests.cpp --- a/src/test/sanity_tests.cpp +++ b/src/test/sanity_tests.cpp @@ -7,6 +7,7 @@ #include #include +#include #include @@ -15,6 +16,7 @@ BOOST_AUTO_TEST_CASE(basic_sanity) { BOOST_CHECK_MESSAGE(glibcxx_sanity_test() == true, "stdlib sanity test"); BOOST_CHECK_MESSAGE(ECC_InitSanityCheck() == true, "secp256k1 sanity test"); + BOOST_CHECK_MESSAGE(ChronoSanityCheck() == true, "chrono epoch test"); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/util/time.h b/src/util/time.h --- a/src/util/time.h +++ b/src/util/time.h @@ -86,4 +86,7 @@ */ struct timeval MillisToTimeval(std::chrono::milliseconds ms); +/** Sanity check epoch match normal Unix epoch */ +bool ChronoSanityCheck(); + #endif // BITCOIN_UTIL_TIME_H diff --git a/src/util/time.cpp b/src/util/time.cpp --- a/src/util/time.cpp +++ b/src/util/time.cpp @@ -36,6 +36,47 @@ return now; } +bool ChronoSanityCheck() { + // std::chrono::system_clock.time_since_epoch and time_t(0) are not + // guaranteed to use the Unix epoch timestamp, prior to C++20, but in + // practice they almost certainly will. Any differing behavior will be + // assumed to be an error, unless certain platforms prove to consistently + // deviate, at which point we'll cope with it by adding offsets. + + // Create a new clock from time_t(0) and make sure that it represents 0 + // seconds from the system_clock's time_since_epoch. Then convert that back + // to a time_t and verify that it's the same as before. + const time_t time_t_epoch{}; + auto clock = std::chrono::system_clock::from_time_t(time_t_epoch); + if (std::chrono::duration_cast( + clock.time_since_epoch()) + .count() != 0) { + return false; + } + + time_t time_val = std::chrono::system_clock::to_time_t(clock); + if (time_val != time_t_epoch) { + return false; + } + + // Check that the above zero time is actually equal to the known unix + // timestamp. + struct tm epoch; +#ifdef _WIN32 + if (gmtime_s(&epoch, &time_val) != 0) { +#else + if (gmtime_r(&time_val, &epoch) == nullptr) { +#endif + return false; + } + + if ((epoch.tm_sec != 0) || (epoch.tm_min != 0) || (epoch.tm_hour != 0) || + (epoch.tm_mday != 1) || (epoch.tm_mon != 0) || (epoch.tm_year != 70)) { + return false; + } + return true; +} + template T GetTime() { const std::chrono::seconds mocktime{ nMockTime.load(std::memory_order_relaxed)};