net: Fix an uninitialized read in ProcessMessage(…, “tx”, …) when receiving a transaction we already have #17624

pull practicalswift wants to merge 1 commits into bitcoin:master from practicalswift:uninitialized-read-in-ProcessMessage changing 1 files +2 −2
  1. practicalswift commented at 10:14 pm on November 27, 2019: contributor

    Fix an uninitialized read in ProcessMessage(…, "tx", …) when receiving a transaction we already have.

    The uninitialized value is read and used on L2526 in the case of AlreadyHave(inv) == true.

    Proof of concept being run against a bitcoind built with MemorySanitizer (-fsanitize=memory):

    0$ ./p2p-uninit-read-in-conditional-poc.py
    1Usage: ./p2p-uninit-read-in-conditional-poc.py <dstaddr> <dstport> <net>
    2$ bitcoind -regtest &
    3$ ./p2p-uninit-read-in-conditional-poc.py 127.0.0.1 18444 regtest
    4SUMMARY: MemorySanitizer: use-of-uninitialized-value
    5[1]+  Exit 77                 bitcoind -regtest
    6$
    

    Proof of concept being run against a bitcoind running under Valgrind (valgrind --exit-on-first-error):

    0$ valgrind -q --exit-on-first-error=yes --error-exitcode=1 bitcoind -regtest &
    1$ ./p2p-uninit-read-in-conditional-poc.py 127.0.0.1 18444 regtest
    2==27351== Conditional jump or move depends on uninitialised value(s)
    3[1]+  Exit 1                  valgrind -q --exit-on-first-error=yes --error-exitcode=1 bitcoind -regtest
    4$ 
    

    Proof of concept script:

     0#!/usr/bin/env python3
     1
     2import sys
     3
     4from test_framework.mininode import NetworkThread
     5from test_framework.mininode import P2PDataStore
     6from test_framework.messages import CTransaction, CTxIn, CTxOut, msg_tx
     7
     8
     9def send_duplicate_tx(dstaddr="127.0.0.1", dstport=18444, net="regtest"):
    10    network_thread = NetworkThread()
    11    network_thread.start()
    12
    13    node = P2PDataStore()
    14    node.peer_connect(dstaddr=dstaddr, dstport=dstport, net=net)()
    15    node.wait_for_verack()
    16
    17    tx = CTransaction()
    18    tx.vin.append(CTxIn())
    19    tx.vout.append(CTxOut())
    20    node.send_message(msg_tx(tx))
    21    node.send_message(msg_tx(tx))
    22    node.peer_disconnect()
    23    network_thread.close()
    24
    25
    26if __name__ == "__main__":
    27    if len(sys.argv) != 4:
    28        print("Usage: {} <dstaddr> <dstport> <net>".format(sys.argv[0]))
    29        sys.exit(0)
    30    send_duplicate_tx(sys.argv[1], int(sys.argv[2]), sys.argv[3])
    

    Note that the transaction in the proof of concept is the simplest possible, but really any transaction can be used. It does not have to be a valid transaction.

    This bug was introduced in #15921 (“validation: Tidy up ValidationState interface”) which was merged in to master 28 days ago.

    Luckily this bug was caught before being part of any Bitcoin Core release :)

  2. net: Fix uninitialized read in ProcessMessage(...) 73b96c94cb
  3. DrahtBot added the label P2P on Nov 27, 2019
  4. DrahtBot commented at 10:33 pm on November 27, 2019: member

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #17557 (util: Refactor message hashing into a utility function by jkczyz)
    • #17399 (validation: Templatize ValidationState instead of subclassing by jkczyz)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  5. jnewbery commented at 3:40 am on November 28, 2019: member

    utACK 73b96c94cb6c2afdee7f151768a96944ecaf9d9b

    Great catch @practicalswift . Are the else if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) and else branches really not covered by any of our tests? If so, that seems like a big hole in our testing. We should add tests for duplicate TX messages received.

  6. laanwj commented at 8:56 am on November 28, 2019: member

    ACK 73b96c94cb6c2afdee7f151768a96944ecaf9d9b, thanks for discovering and reporting this before it ended up in a release.

    (travis fail is unrelated, restarted …)

  7. practicalswift commented at 9:39 am on November 28, 2019: contributor

    @jnewbery

    Both branches are covered by our functional tests (specifically test/functional/p2p_segwit.py), but unfortunately we’re not running the functional tests under MemorySanitizer or Valgrind :(

    I found this issue after observing some weirdness on a mainnet when running a live node with MemorySanitizer instrumentation enabled. Receiving duplicate transactions on mainnet happens quite regularly. I then reproduced the issue on regtest.

    Since discovering the issue on mainnet I’ve “rediscovered” it 1.) using static analysis tooling (Coverity is one example), 2.) by running the ordinary functional tests under MemorySanitizer, and 3.) by running the ordinary functional tests under Valgrind.

    My personal view is that we really really underuse the excellent modern tooling that is typically used in security critical C++ projects to guard against introduction of bugs like this. I find that a bit surprising and I promise to do my best to help improve that situation going forward :)


    Rediscovery 1. Finding the issue using static analysis (Coverity in this example)

     0________________________________________________________________________________________________________
     1*** CID 350378:  Uninitialized members  (UNINIT_CTOR)
     2/src/consensus/validation.h: 117 in TxValidationState::TxValidationState()()
     3111     };
     4112     
     5113     inline ValidationState::~ValidationState() {};
     6114     
     7115     class TxValidationState : public ValidationState {
     8116     private:
     9>>>     CID 350378:  Uninitialized members  (UNINIT_CTOR)
    10>>>     The compiler-generated constructor for this class does not initialize "m_result".
    11117         TxValidationResult m_result;
    12118     public:
    13119         bool Invalid(TxValidationResult result,
    14120                      const std::string &reject_reason="",
    15121                      const std::string &debug_message="")
    16122         {
    17** CID 350377:  Uninitialized variables  (UNINIT)
    18________________________________________________________________________________________________________
    19*** CID 350377:  Uninitialized variables  (UNINIT)
    20/src/net_processing.cpp: 2526 in ProcessMessage(CNode *, const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> &, CDataStream &, long, const CChainParams &, CConnman *, BanMan *, const std::atomic<bool> &)()
    212520                     tx.GetHash().ToString(),
    222521                     mempool.size(), mempool.DynamicMemoryUsage() / 1000);
    232522     
    242523                 // Recursively process any orphan transactions that depended on this one
    252524                 ProcessOrphanTx(connman, pfrom->orphan_work_set, lRemovedTxn);
    262525             }
    27>>>     CID 350377:  Uninitialized variables  (UNINIT)
    28>>>     Using uninitialized value "state.m_result" when calling "GetResult".
    292526             else if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS)
    302527             {
    312528                 bool fRejectedParents = false; // It may be the case that the orphans parents have all been rejected
    322529                 for (const CTxIn& txin : tx.vin) {
    332530                     if (recentRejects->contains(txin.prevout.hash)) {
    342531                         fRejectedParents = true;
    

    Rediscovery 2. Finding the issue using dynamic analysis (MemorySanitizer)

    Running test/functional/test_runner.py with bitcoind compiled with MSAN instrumentation:

    0$ test/functional/test_runner.py
    122020-01-02T04:05:06.123456Z TestFramework (INFO): Subtest: test_non_standard_witness_blinding (Segwit active = True)
    32020-01-02T04:05:06.123456Z TestFramework (ERROR): Assertion failed
    45SUMMARY: MemorySanitizer: use-of-uninitialized-value
    

    Rediscovery 3. Finding the issue using dynamic analysis (Valgrind)

    Running BITCOIND=bitcoind_valgrind test/functional/test_runner.py where bitcoind_valgrind is a shell script wrapper doing valgrind -q --exit-on-first-error=yes --error-exitcode=1 --gen-suppressions=all --leak-check=full --show-leak-kinds=all --suppressions=contrib/valgrind.supp src/bitcoind "$@":

    0$ BITCOIND=bitcoind_valgrind test/functional/test_runner.py
    122020-01-02T04:05:06.123456Z TestFramework (INFO): Subtest: test_non_standard_witness_blinding (Segwit active = True)
    32020-01-02T04:05:06.123456Z TestFramework (ERROR): Assertion failed
    45==47281== Conditional jump or move depends on uninitialised value(s)
    
  8. laanwj referenced this in commit 114e89e596 on Nov 28, 2019
  9. laanwj merged this on Nov 28, 2019
  10. laanwj closed this on Nov 28, 2019

  11. MarcoFalke referenced this in commit 3d6752779f on Dec 10, 2019
  12. sidhujag referenced this in commit 6e0bed9cbe on Dec 10, 2019
  13. jasonbcox referenced this in commit 0704a943d9 on Jul 14, 2020
  14. sidhujag referenced this in commit bcf410ff00 on Nov 10, 2020
  15. practicalswift deleted the branch on Apr 10, 2021
  16. PastaPastaPasta referenced this in commit c5ad6c618d on Jun 4, 2021
  17. vijaydasmp referenced this in commit 82e8a6b2fd on Apr 1, 2022
  18. vijaydasmp referenced this in commit 53fee2a01c on Apr 2, 2022
  19. vijaydasmp referenced this in commit 77f106ebf5 on Apr 3, 2022
  20. kittywhiskers referenced this in commit 6d1ac9b459 on Apr 5, 2022
  21. kittywhiskers referenced this in commit 09b0205903 on Apr 5, 2022
  22. DrahtBot locked this on Aug 16, 2022

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2024-07-01 10:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me