Skip to content

Commit 3c2e16b

Browse files
theuniajtowns
authored andcommitted
time: add runtime sanity check
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 <[email protected]>
1 parent 36be9b8 commit 3c2e16b

File tree

4 files changed

+52
-0
lines changed

4 files changed

+52
-0
lines changed

src/init.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,10 @@ static bool InitSanityCheck()
773773
return InitError(Untranslated("OS cryptographic RNG sanity check failure. Aborting."));
774774
}
775775

776+
if (!ChronoSanityCheck()) {
777+
return InitError(Untranslated("Clock epoch mismatch. Aborting."));
778+
}
779+
776780
return true;
777781
}
778782

src/test/sanity_tests.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <compat/sanity.h>
66
#include <key.h>
77
#include <test/util/setup_common.h>
8+
#include <util/time.h>
89

910
#include <boost/test/unit_test.hpp>
1011

@@ -15,6 +16,7 @@ BOOST_AUTO_TEST_CASE(basic_sanity)
1516
BOOST_CHECK_MESSAGE(glibc_sanity_test() == true, "libc sanity test");
1617
BOOST_CHECK_MESSAGE(glibcxx_sanity_test() == true, "stdlib sanity test");
1718
BOOST_CHECK_MESSAGE(ECC_InitSanityCheck() == true, "secp256k1 sanity test");
19+
BOOST_CHECK_MESSAGE(ChronoSanityCheck() == true, "chrono epoch test");
1820
}
1921

2022
BOOST_AUTO_TEST_SUITE_END()

src/util/time.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,49 @@ int64_t GetTime()
3333
return now;
3434
}
3535

36+
bool ChronoSanityCheck()
37+
{
38+
// std::chrono::system_clock.time_since_epoch and time_t(0) are not guaranteed
39+
// to use the Unix epoch timestamp, prior to C++20, but in practice they almost
40+
// certainly will. Any differing behavior will be assumed to be an error, unless
41+
// certain platforms prove to consistently deviate, at which point we'll cope
42+
// with it by adding offsets.
43+
44+
// Create a new clock from time_t(0) and make sure that it represents 0
45+
// seconds from the system_clock's time_since_epoch. Then convert that back
46+
// to a time_t and verify that it's the same as before.
47+
const time_t time_t_epoch{};
48+
auto clock = std::chrono::system_clock::from_time_t(time_t_epoch);
49+
if (std::chrono::duration_cast<std::chrono::seconds>(clock.time_since_epoch()).count() != 0) {
50+
return false;
51+
}
52+
53+
time_t time_val = std::chrono::system_clock::to_time_t(clock);
54+
if (time_val != time_t_epoch) {
55+
return false;
56+
}
57+
58+
// Check that the above zero time is actually equal to the known unix timestamp.
59+
struct tm epoch;
60+
#ifdef HAVE_GMTIME_R
61+
if (gmtime_r(&time_val, &epoch) == nullptr) {
62+
#else
63+
if (gmtime_s(&epoch, &time_val) != 0) {
64+
#endif
65+
return false;
66+
}
67+
68+
if ((epoch.tm_sec != 0) ||
69+
(epoch.tm_min != 0) ||
70+
(epoch.tm_hour != 0) ||
71+
(epoch.tm_mday != 1) ||
72+
(epoch.tm_mon != 0) ||
73+
(epoch.tm_year != 70)) {
74+
return false;
75+
}
76+
return true;
77+
}
78+
3679
template <typename T>
3780
T GetTime()
3881
{

src/util/time.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,7 @@ struct timeval MillisToTimeval(int64_t nTimeout);
7070
*/
7171
struct timeval MillisToTimeval(std::chrono::milliseconds ms);
7272

73+
/** Sanity check epoch match normal Unix epoch */
74+
bool ChronoSanityCheck();
75+
7376
#endif // BITCOIN_UTIL_TIME_H

0 commit comments

Comments
 (0)