1 // Copyright (c) 2014-2020 The Bitcoin Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 
5 #if defined(HAVE_CONFIG_H)
6 #include <config/bitcoin-config.h>
7 #endif
8 
9 #include <timedata.h>
10 
11 #include <netaddress.h>
12 #include <node/ui_interface.h>
13 #include <sync.h>
14 #include <util/system.h>
15 #include <util/translation.h>
16 #include <warnings.h>
17 
18 static Mutex g_timeoffset_mutex;
19 static int64_t nTimeOffset GUARDED_BY(g_timeoffset_mutex) = 0;
20 
21 /**
22  * "Never go to sea with two chronometers; take one or three."
23  * Our three time sources are:
24  *  - System clock
25  *  - Median of other nodes clocks
26  *  - The user (asking the user to fix the system clock if the first two disagree)
27  */
GetTimeOffset()28 int64_t GetTimeOffset()
29 {
30     LOCK(g_timeoffset_mutex);
31     return nTimeOffset;
32 }
33 
GetAdjustedTime()34 int64_t GetAdjustedTime()
35 {
36     return GetTime() + GetTimeOffset();
37 }
38 
39 #define BITCOIN_TIMEDATA_MAX_SAMPLES 200
40 
AddTimeData(const CNetAddr & ip,int64_t nOffsetSample)41 void AddTimeData(const CNetAddr& ip, int64_t nOffsetSample)
42 {
43     LOCK(g_timeoffset_mutex);
44     // Ignore duplicates
45     static std::set<CNetAddr> setKnown;
46     if (setKnown.size() == BITCOIN_TIMEDATA_MAX_SAMPLES)
47         return;
48     if (!setKnown.insert(ip).second)
49         return;
50 
51     // Add data
52     static CMedianFilter<int64_t> vTimeOffsets(BITCOIN_TIMEDATA_MAX_SAMPLES, 0);
53     vTimeOffsets.input(nOffsetSample);
54     LogPrint(BCLog::NET, "added time data, samples %d, offset %+d (%+d minutes)\n", vTimeOffsets.size(), nOffsetSample, nOffsetSample / 60);
55 
56     // There is a known issue here (see issue #4521):
57     //
58     // - The structure vTimeOffsets contains up to 200 elements, after which
59     // any new element added to it will not increase its size, replacing the
60     // oldest element.
61     //
62     // - The condition to update nTimeOffset includes checking whether the
63     // number of elements in vTimeOffsets is odd, which will never happen after
64     // there are 200 elements.
65     //
66     // But in this case the 'bug' is protective against some attacks, and may
67     // actually explain why we've never seen attacks which manipulate the
68     // clock offset.
69     //
70     // So we should hold off on fixing this and clean it up as part of
71     // a timing cleanup that strengthens it in a number of other ways.
72     //
73     if (vTimeOffsets.size() >= 5 && vTimeOffsets.size() % 2 == 1) {
74         int64_t nMedian = vTimeOffsets.median();
75         std::vector<int64_t> vSorted = vTimeOffsets.sorted();
76         // Only let other nodes change our time by so much
77         int64_t max_adjustment = std::max<int64_t>(0, gArgs.GetArg("-maxtimeadjustment", DEFAULT_MAX_TIME_ADJUSTMENT));
78         if (nMedian >= -max_adjustment && nMedian <= max_adjustment) {
79             nTimeOffset = nMedian;
80         } else {
81             nTimeOffset = 0;
82 
83             static bool fDone;
84             if (!fDone) {
85                 // If nobody has a time different than ours but within 5 minutes of ours, give a warning
86                 bool fMatch = false;
87                 for (const int64_t nOffset : vSorted) {
88                     if (nOffset != 0 && nOffset > -5 * 60 && nOffset < 5 * 60) fMatch = true;
89                 }
90 
91                 if (!fMatch) {
92                     fDone = true;
93                     bilingual_str strMessage = strprintf(_("Please check that your computer's date and time are correct! If your clock is wrong, %s will not work properly."), PACKAGE_NAME);
94                     SetMiscWarning(strMessage);
95                     uiInterface.ThreadSafeMessageBox(strMessage, "", CClientUIInterface::MSG_WARNING);
96                 }
97             }
98         }
99 
100         if (LogAcceptCategory(BCLog::NET)) {
101             for (const int64_t n : vSorted) {
102                 LogPrint(BCLog::NET, "%+d  ", n); /* Continued */
103             }
104             LogPrint(BCLog::NET, "|  "); /* Continued */
105             LogPrint(BCLog::NET, "nTimeOffset = %+d  (%+d minutes)\n", nTimeOffset, nTimeOffset / 60);
106         }
107     }
108 }
109