Skip to content

Commit

Permalink
time: add runtime sanity check
Browse files Browse the repository at this point in the history
Summary:
`std::chrono::system_clock.time_since_epoch` and `time_t(0)` are not guaranteed to use the Unix epoch timestamp, 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.

Do a quick runtime check to verify that `time_t(0) == std::chrono::system_clock`'s epoch time `==` unix epoch.

Co-authored-by: Anthony Towns <aj@erisian.com.au>

This is a backport of [[ bitcoin/bitcoin#21110 | core#21110]] [1/2]
bitcoin/bitcoin@3c2e16b

Note: see D5448 for usage of `#ifdef _WIN32` to determine `gmtime_s` availability. It this turns out to not work in the future, we can try to backport [[bitcoin/bitcoin#18358 | core#18358]]

Test Plan: `ninja all check-all`

Reviewers: #bitcoin_abc, Fabien

Reviewed By: #bitcoin_abc, Fabien

Subscribers: Fabien

Differential Revision: https://reviews.bitcoinabc.org/D11789
  • Loading branch information
theuni authored and PiRK committed Jul 22, 2022
1 parent db165ec commit 7ba2b08
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1532,6 +1532,10 @@ static bool InitSanityCheck() {
"OS cryptographic RNG sanity check failure. Aborting."));
}

if (!ChronoSanityCheck()) {
return InitError(Untranslated("Clock epoch mismatch. Aborting."));
}

return true;
}

Expand Down
2 changes: 2 additions & 0 deletions src/test/sanity_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <key.h>

#include <test/util/setup_common.h>
#include <util/time.h>

#include <boost/test/unit_test.hpp>

Expand All @@ -15,6 +16,7 @@ BOOST_FIXTURE_TEST_SUITE(sanity_tests, BasicTestingSetup)
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()
41 changes: 41 additions & 0 deletions src/util/time.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,47 @@ int64_t GetTime() {
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<std::chrono::seconds>(
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 <typename T> T GetTime() {
const std::chrono::seconds mocktime{
nMockTime.load(std::memory_order_relaxed)};
Expand Down
3 changes: 3 additions & 0 deletions src/util/time.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,7 @@ struct timeval MillisToTimeval(int64_t nTimeout);
*/
struct timeval MillisToTimeval(std::chrono::milliseconds ms);

/** Sanity check epoch match normal Unix epoch */
bool ChronoSanityCheck();

#endif // BITCOIN_UTIL_TIME_H

0 comments on commit 7ba2b08

Please sign in to comment.