LCOV - code coverage report
Current view: top level - src/test - denialofservice_tests.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 235 236 99.6 %
Date: 2020-09-26 01:30:44 Functions: 47 47 100.0 %

          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()

Generated by: LCOV version 1.15