Line data Source code
1 : // Copyright (c) 2011-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 : // Unit tests for denial-of-service detection/prevention code
6 :
7 : #include <arith_uint256.h>
8 : #include <banman.h>
9 : #include <chainparams.h>
10 : #include <net.h>
11 : #include <net_processing.h>
12 : #include <pubkey.h>
13 : #include <script/sign.h>
14 : #include <script/signingprovider.h>
15 : #include <script/standard.h>
16 : #include <serialize.h>
17 : #include <util/memory.h>
18 : #include <util/string.h>
19 : #include <util/system.h>
20 : #include <util/time.h>
21 : #include <validation.h>
22 :
23 : #include <test/util/setup_common.h>
24 :
25 : #include <stdint.h>
26 :
27 : #include <boost/test/unit_test.hpp>
28 :
29 2 : struct CConnmanTest : public CConnman {
30 2 : using CConnman::CConnman;
31 9 : void AddNode(CNode& node)
32 : {
33 9 : LOCK(cs_vNodes);
34 9 : vNodes.push_back(&node);
35 9 : }
36 1 : void ClearNodes()
37 : {
38 1 : LOCK(cs_vNodes);
39 10 : for (CNode* node : vNodes) {
40 9 : delete node;
41 : }
42 1 : vNodes.clear();
43 1 : }
44 : };
45 :
46 : // Tests these internal-to-net_processing.cpp methods:
47 : extern bool AddOrphanTx(const CTransactionRef& tx, NodeId peer);
48 : extern void EraseOrphansFor(NodeId peer);
49 : extern unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans);
50 :
51 : struct COrphanTx {
52 : CTransactionRef tx;
53 : NodeId fromPeer;
54 : int64_t nTimeExpire;
55 : };
56 : extern std::map<uint256, COrphanTx> mapOrphanTransactions GUARDED_BY(g_cs_orphans);
57 :
58 14 : static CService ip(uint32_t i)
59 : {
60 14 : struct in_addr s;
61 14 : s.s_addr = i;
62 14 : return CService(CNetAddr(s), Params().GetDefaultPort());
63 14 : }
64 :
65 : static NodeId id = 0;
66 :
67 : void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds);
68 :
69 89 : BOOST_FIXTURE_TEST_SUITE(denialofservice_tests, TestingSetup)
70 :
71 : // Test eviction of an outbound peer whose chain never advances
72 : // Mock a node connection, and use mocktime to simulate a peer
73 : // which never sends any headers messages. PeerLogic should
74 : // decide to evict that outbound peer, after the appropriate timeouts.
75 : // Note that we protect 4 outbound nodes from being subject to
76 : // this logic; this test takes advantage of that protection only
77 : // being applied to nodes which send headers with sufficient
78 : // work.
79 95 : BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction)
80 : {
81 1 : const CChainParams& chainparams = Params();
82 1 : auto connman = MakeUnique<CConnman>(0x1337, 0x1337);
83 1 : auto peerLogic = MakeUnique<PeerManager>(chainparams, *connman, nullptr, *m_node.scheduler, *m_node.chainman, *m_node.mempool);
84 :
85 : // Mock an outbound peer
86 1 : CAddress addr1(ip(0xa0b0c001), NODE_NONE);
87 1 : CNode dummyNode1(id++, ServiceFlags(NODE_NETWORK | NODE_WITNESS), 0, INVALID_SOCKET, addr1, 0, 0, CAddress(), "", ConnectionType::OUTBOUND_FULL_RELAY);
88 1 : dummyNode1.SetSendVersion(PROTOCOL_VERSION);
89 :
90 1 : peerLogic->InitializeNode(&dummyNode1);
91 1 : dummyNode1.nVersion = 1;
92 1 : dummyNode1.fSuccessfullyConnected = true;
93 :
94 : // This test requires that we have a chain with non-zero work.
95 : {
96 1 : LOCK(cs_main);
97 1 : BOOST_CHECK(::ChainActive().Tip() != nullptr);
98 1 : BOOST_CHECK(::ChainActive().Tip()->nChainWork > 0);
99 1 : }
100 :
101 : // Test starts here
102 : {
103 1 : LOCK(dummyNode1.cs_sendProcessing);
104 1 : BOOST_CHECK(peerLogic->SendMessages(&dummyNode1)); // should result in getheaders
105 1 : }
106 : {
107 1 : LOCK(dummyNode1.cs_vSend);
108 1 : BOOST_CHECK(dummyNode1.vSendMsg.size() > 0);
109 1 : dummyNode1.vSendMsg.clear();
110 1 : }
111 :
112 1 : int64_t nStartTime = GetTime();
113 : // Wait 21 minutes
114 1 : SetMockTime(nStartTime+21*60);
115 : {
116 1 : LOCK(dummyNode1.cs_sendProcessing);
117 1 : BOOST_CHECK(peerLogic->SendMessages(&dummyNode1)); // should result in getheaders
118 1 : }
119 : {
120 1 : LOCK(dummyNode1.cs_vSend);
121 1 : BOOST_CHECK(dummyNode1.vSendMsg.size() > 0);
122 1 : }
123 : // Wait 3 more minutes
124 1 : SetMockTime(nStartTime+24*60);
125 : {
126 1 : LOCK(dummyNode1.cs_sendProcessing);
127 1 : BOOST_CHECK(peerLogic->SendMessages(&dummyNode1)); // should result in disconnect
128 1 : }
129 1 : BOOST_CHECK(dummyNode1.fDisconnect == true);
130 1 : SetMockTime(0);
131 :
132 1 : bool dummy;
133 1 : peerLogic->FinalizeNode(dummyNode1.GetId(), dummy);
134 1 : }
135 :
136 9 : static void AddRandomOutboundPeer(std::vector<CNode *> &vNodes, PeerManager &peerLogic, CConnmanTest* connman)
137 : {
138 9 : CAddress addr(ip(g_insecure_rand_ctx.randbits(32)), NODE_NONE);
139 9 : vNodes.emplace_back(new CNode(id++, ServiceFlags(NODE_NETWORK | NODE_WITNESS), 0, INVALID_SOCKET, addr, 0, 0, CAddress(), "", ConnectionType::OUTBOUND_FULL_RELAY));
140 9 : CNode &node = *vNodes.back();
141 9 : node.SetSendVersion(PROTOCOL_VERSION);
142 :
143 9 : peerLogic.InitializeNode(&node);
144 9 : node.nVersion = 1;
145 9 : node.fSuccessfullyConnected = true;
146 :
147 9 : connman->AddNode(node);
148 9 : }
149 :
150 95 : BOOST_AUTO_TEST_CASE(stale_tip_peer_management)
151 : {
152 1 : const CChainParams& chainparams = Params();
153 1 : auto connman = MakeUnique<CConnmanTest>(0x1337, 0x1337);
154 1 : auto peerLogic = MakeUnique<PeerManager>(chainparams, *connman, nullptr, *m_node.scheduler, *m_node.chainman, *m_node.mempool);
155 :
156 : constexpr int max_outbound_full_relay = MAX_OUTBOUND_FULL_RELAY_CONNECTIONS;
157 1 : CConnman::Options options;
158 1 : options.nMaxConnections = DEFAULT_MAX_PEER_CONNECTIONS;
159 1 : options.m_max_outbound_full_relay = max_outbound_full_relay;
160 1 : options.nMaxFeeler = MAX_FEELER_CONNECTIONS;
161 :
162 1 : connman->Init(options);
163 1 : std::vector<CNode *> vNodes;
164 :
165 : // Mock some outbound peers
166 9 : for (int i=0; i<max_outbound_full_relay; ++i) {
167 8 : AddRandomOutboundPeer(vNodes, *peerLogic, connman.get());
168 : }
169 :
170 1 : peerLogic->CheckForStaleTipAndEvictPeers();
171 :
172 : // No nodes should be marked for disconnection while we have no extra peers
173 9 : for (const CNode *node : vNodes) {
174 8 : BOOST_CHECK(node->fDisconnect == false);
175 : }
176 :
177 1 : SetMockTime(GetTime() + 3 * chainparams.GetConsensus().nPowTargetSpacing + 1);
178 :
179 : // Now tip should definitely be stale, and we should look for an extra
180 : // outbound peer
181 1 : peerLogic->CheckForStaleTipAndEvictPeers();
182 1 : BOOST_CHECK(connman->GetTryNewOutboundPeer());
183 :
184 : // Still no peers should be marked for disconnection
185 9 : for (const CNode *node : vNodes) {
186 8 : BOOST_CHECK(node->fDisconnect == false);
187 : }
188 :
189 : // If we add one more peer, something should get marked for eviction
190 : // on the next check (since we're mocking the time to be in the future, the
191 : // required time connected check should be satisfied).
192 1 : AddRandomOutboundPeer(vNodes, *peerLogic, connman.get());
193 :
194 1 : peerLogic->CheckForStaleTipAndEvictPeers();
195 9 : for (int i = 0; i < max_outbound_full_relay; ++i) {
196 8 : BOOST_CHECK(vNodes[i]->fDisconnect == false);
197 : }
198 : // Last added node should get marked for eviction
199 1 : BOOST_CHECK(vNodes.back()->fDisconnect == true);
200 :
201 1 : vNodes.back()->fDisconnect = false;
202 :
203 : // Update the last announced block time for the last
204 : // peer, and check that the next newest node gets evicted.
205 1 : UpdateLastBlockAnnounceTime(vNodes.back()->GetId(), GetTime());
206 :
207 1 : peerLogic->CheckForStaleTipAndEvictPeers();
208 8 : for (int i = 0; i < max_outbound_full_relay - 1; ++i) {
209 7 : BOOST_CHECK(vNodes[i]->fDisconnect == false);
210 : }
211 1 : BOOST_CHECK(vNodes[max_outbound_full_relay-1]->fDisconnect == true);
212 1 : BOOST_CHECK(vNodes.back()->fDisconnect == false);
213 :
214 1 : bool dummy;
215 10 : for (const CNode *node : vNodes) {
216 9 : peerLogic->FinalizeNode(node->GetId(), dummy);
217 : }
218 :
219 1 : connman->ClearNodes();
220 1 : }
221 :
222 95 : BOOST_AUTO_TEST_CASE(peer_discouragement)
223 : {
224 1 : const CChainParams& chainparams = Params();
225 1 : auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME);
226 1 : auto connman = MakeUnique<CConnman>(0x1337, 0x1337);
227 1 : auto peerLogic = MakeUnique<PeerManager>(chainparams, *connman, banman.get(), *m_node.scheduler, *m_node.chainman, *m_node.mempool);
228 :
229 1 : banman->ClearBanned();
230 1 : CAddress addr1(ip(0xa0b0c001), NODE_NONE);
231 1 : CNode dummyNode1(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr1, 0, 0, CAddress(), "", ConnectionType::INBOUND);
232 1 : dummyNode1.SetSendVersion(PROTOCOL_VERSION);
233 1 : peerLogic->InitializeNode(&dummyNode1);
234 1 : dummyNode1.nVersion = 1;
235 1 : dummyNode1.fSuccessfullyConnected = true;
236 1 : peerLogic->Misbehaving(dummyNode1.GetId(), DISCOURAGEMENT_THRESHOLD, /* message */ ""); // Should be discouraged
237 : {
238 1 : LOCK(dummyNode1.cs_sendProcessing);
239 1 : BOOST_CHECK(peerLogic->SendMessages(&dummyNode1));
240 1 : }
241 1 : BOOST_CHECK(banman->IsDiscouraged(addr1));
242 1 : BOOST_CHECK(!banman->IsDiscouraged(ip(0xa0b0c001|0x0000ff00))); // Different IP, not discouraged
243 :
244 1 : CAddress addr2(ip(0xa0b0c002), NODE_NONE);
245 1 : CNode dummyNode2(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr2, 1, 1, CAddress(), "", ConnectionType::INBOUND);
246 1 : dummyNode2.SetSendVersion(PROTOCOL_VERSION);
247 1 : peerLogic->InitializeNode(&dummyNode2);
248 1 : dummyNode2.nVersion = 1;
249 1 : dummyNode2.fSuccessfullyConnected = true;
250 1 : peerLogic->Misbehaving(dummyNode2.GetId(), DISCOURAGEMENT_THRESHOLD - 1, /* message */ "");
251 : {
252 1 : LOCK(dummyNode2.cs_sendProcessing);
253 1 : BOOST_CHECK(peerLogic->SendMessages(&dummyNode2));
254 1 : }
255 1 : BOOST_CHECK(!banman->IsDiscouraged(addr2)); // 2 not discouraged yet...
256 1 : BOOST_CHECK(banman->IsDiscouraged(addr1)); // ... but 1 still should be
257 1 : peerLogic->Misbehaving(dummyNode2.GetId(), 1, /* message */ ""); // 2 reaches discouragement threshold
258 : {
259 1 : LOCK(dummyNode2.cs_sendProcessing);
260 1 : BOOST_CHECK(peerLogic->SendMessages(&dummyNode2));
261 1 : }
262 1 : BOOST_CHECK(banman->IsDiscouraged(addr1)); // Expect both 1 and 2
263 1 : BOOST_CHECK(banman->IsDiscouraged(addr2)); // to be discouraged now
264 :
265 1 : bool dummy;
266 1 : peerLogic->FinalizeNode(dummyNode1.GetId(), dummy);
267 1 : peerLogic->FinalizeNode(dummyNode2.GetId(), dummy);
268 1 : }
269 :
270 95 : BOOST_AUTO_TEST_CASE(DoS_bantime)
271 : {
272 1 : const CChainParams& chainparams = Params();
273 1 : auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME);
274 1 : auto connman = MakeUnique<CConnman>(0x1337, 0x1337);
275 1 : auto peerLogic = MakeUnique<PeerManager>(chainparams, *connman, banman.get(), *m_node.scheduler, *m_node.chainman, *m_node.mempool);
276 :
277 1 : banman->ClearBanned();
278 1 : int64_t nStartTime = GetTime();
279 1 : SetMockTime(nStartTime); // Overrides future calls to GetTime()
280 :
281 1 : CAddress addr(ip(0xa0b0c001), NODE_NONE);
282 1 : CNode dummyNode(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr, 4, 4, CAddress(), "", ConnectionType::INBOUND);
283 1 : dummyNode.SetSendVersion(PROTOCOL_VERSION);
284 1 : peerLogic->InitializeNode(&dummyNode);
285 1 : dummyNode.nVersion = 1;
286 1 : dummyNode.fSuccessfullyConnected = true;
287 :
288 1 : peerLogic->Misbehaving(dummyNode.GetId(), DISCOURAGEMENT_THRESHOLD, /* message */ "");
289 : {
290 1 : LOCK(dummyNode.cs_sendProcessing);
291 1 : BOOST_CHECK(peerLogic->SendMessages(&dummyNode));
292 1 : }
293 1 : BOOST_CHECK(banman->IsDiscouraged(addr));
294 :
295 1 : bool dummy;
296 1 : peerLogic->FinalizeNode(dummyNode.GetId(), dummy);
297 1 : }
298 :
299 60 : static CTransactionRef RandomOrphan()
300 : {
301 60 : std::map<uint256, COrphanTx>::iterator it;
302 60 : LOCK2(cs_main, g_cs_orphans);
303 60 : it = mapOrphanTransactions.lower_bound(InsecureRand256());
304 60 : if (it == mapOrphanTransactions.end())
305 0 : it = mapOrphanTransactions.begin();
306 60 : return it->second.tx;
307 60 : }
308 :
309 1 : static void MakeNewKeyWithFastRandomContext(CKey& key)
310 : {
311 1 : std::vector<unsigned char> keydata;
312 1 : keydata = g_insecure_rand_ctx.randbytes(32);
313 1 : key.Set(keydata.data(), keydata.data() + keydata.size(), /*fCompressedIn*/ true);
314 1 : assert(key.IsValid());
315 1 : }
316 :
317 95 : BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
318 : {
319 : // This test had non-deterministic coverage due to
320 : // randomly selected seeds.
321 : // This seed is chosen so that all branches of the function
322 : // ecdsa_signature_parse_der_lax are executed during this test.
323 : // Specifically branches that run only when an ECDSA
324 : // signature's R and S values have leading zeros.
325 1 : g_insecure_rand_ctx = FastRandomContext(ArithToUint256(arith_uint256(33)));
326 :
327 1 : CKey key;
328 1 : MakeNewKeyWithFastRandomContext(key);
329 1 : FillableSigningProvider keystore;
330 1 : BOOST_CHECK(keystore.AddKey(key));
331 :
332 : // 50 orphan transactions:
333 51 : for (int i = 0; i < 50; i++)
334 : {
335 50 : CMutableTransaction tx;
336 50 : tx.vin.resize(1);
337 50 : tx.vin[0].prevout.n = 0;
338 50 : tx.vin[0].prevout.hash = InsecureRand256();
339 50 : tx.vin[0].scriptSig << OP_1;
340 50 : tx.vout.resize(1);
341 50 : tx.vout[0].nValue = 1*CENT;
342 50 : tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));
343 :
344 50 : AddOrphanTx(MakeTransactionRef(tx), i);
345 50 : }
346 :
347 : // ... and 50 that depend on other orphans:
348 51 : for (int i = 0; i < 50; i++)
349 : {
350 50 : CTransactionRef txPrev = RandomOrphan();
351 :
352 50 : CMutableTransaction tx;
353 50 : tx.vin.resize(1);
354 50 : tx.vin[0].prevout.n = 0;
355 50 : tx.vin[0].prevout.hash = txPrev->GetHash();
356 50 : tx.vout.resize(1);
357 50 : tx.vout[0].nValue = 1*CENT;
358 50 : tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));
359 50 : BOOST_CHECK(SignSignature(keystore, *txPrev, tx, 0, SIGHASH_ALL));
360 :
361 50 : AddOrphanTx(MakeTransactionRef(tx), i);
362 50 : }
363 :
364 : // This really-big orphan should be ignored:
365 11 : for (int i = 0; i < 10; i++)
366 : {
367 10 : CTransactionRef txPrev = RandomOrphan();
368 :
369 10 : CMutableTransaction tx;
370 10 : tx.vout.resize(1);
371 10 : tx.vout[0].nValue = 1*CENT;
372 10 : tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));
373 10 : tx.vin.resize(2777);
374 27780 : for (unsigned int j = 0; j < tx.vin.size(); j++)
375 : {
376 27770 : tx.vin[j].prevout.n = j;
377 27770 : tx.vin[j].prevout.hash = txPrev->GetHash();
378 : }
379 10 : BOOST_CHECK(SignSignature(keystore, *txPrev, tx, 0, SIGHASH_ALL));
380 : // Re-use same signature for other inputs
381 : // (they don't have to be valid for this test)
382 27770 : for (unsigned int j = 1; j < tx.vin.size(); j++)
383 27760 : tx.vin[j].scriptSig = tx.vin[0].scriptSig;
384 :
385 10 : BOOST_CHECK(!AddOrphanTx(MakeTransactionRef(tx), i));
386 10 : }
387 :
388 1 : LOCK2(cs_main, g_cs_orphans);
389 : // Test EraseOrphansFor:
390 4 : for (NodeId i = 0; i < 3; i++)
391 : {
392 3 : size_t sizeBefore = mapOrphanTransactions.size();
393 3 : EraseOrphansFor(i);
394 3 : BOOST_CHECK(mapOrphanTransactions.size() < sizeBefore);
395 : }
396 :
397 : // Test LimitOrphanTxSize() function:
398 1 : LimitOrphanTxSize(40);
399 1 : BOOST_CHECK(mapOrphanTransactions.size() <= 40);
400 1 : LimitOrphanTxSize(10);
401 1 : BOOST_CHECK(mapOrphanTransactions.size() <= 10);
402 1 : LimitOrphanTxSize(0);
403 1 : BOOST_CHECK(mapOrphanTransactions.empty());
404 1 : }
405 :
406 89 : BOOST_AUTO_TEST_SUITE_END()
|