diff --git a/src/test/fuzz/CMakeLists.txt b/src/test/fuzz/CMakeLists.txt index 3a23bdecc..2b28ffc48 100644 --- a/src/test/fuzz/CMakeLists.txt +++ b/src/test/fuzz/CMakeLists.txt @@ -1,209 +1,210 @@ # Fuzzer test harness add_custom_target(bitcoin-fuzzers) define_property(GLOBAL PROPERTY FUZZ_TARGETS BRIEF_DOCS "List of fuzz targets" FULL_DOCS "A list of the fuzz targets" ) set_property(GLOBAL APPEND PROPERTY FUZZ_TARGETS bitcoin-fuzzers) include(InstallationHelper) macro(add_fuzz_target TARGET EXE_NAME) add_executable(${TARGET} EXCLUDE_FROM_ALL fuzz.cpp ${ARGN} ) set_target_properties(${TARGET} PROPERTIES OUTPUT_NAME ${EXE_NAME}) target_link_libraries(${TARGET} server testutil rpcclient) add_dependencies(bitcoin-fuzzers ${TARGET}) set_property(GLOBAL APPEND PROPERTY FUZZ_TARGETS ${TARGET}) install_target(${TARGET} COMPONENT fuzzer EXCLUDE_FROM_ALL ) endmacro() function(add_regular_fuzz_targets) foreach(_fuzz_test_name ${ARGN}) sanitize_target_name("fuzz-" ${_fuzz_test_name} _fuzz_target_name) add_fuzz_target( ${_fuzz_target_name} ${_fuzz_test_name} # Sources "${_fuzz_test_name}.cpp" ) endforeach() endfunction() include(SanitizeHelper) function(add_deserialize_fuzz_targets) foreach(_fuzz_test_name ${ARGN}) sanitize_target_name("fuzz-" ${_fuzz_test_name} _fuzz_target_name) add_fuzz_target( ${_fuzz_target_name} ${_fuzz_test_name} # Sources deserialize.cpp ) sanitize_c_cxx_definition("" ${_fuzz_test_name} _target_definition) string(TOUPPER ${_target_definition} _target_definition) target_compile_definitions(${_fuzz_target_name} PRIVATE ${_target_definition}) endforeach() endfunction() function(add_process_message_fuzz_targets) foreach(_fuzz_test_name ${ARGN}) sanitize_target_name("fuzz-process_message_" ${_fuzz_test_name} _fuzz_target_name) add_fuzz_target( ${_fuzz_target_name} process_message_${_fuzz_test_name} # Sources process_message.cpp ) target_compile_definitions(${_fuzz_target_name} PRIVATE MESSAGE_TYPE=${_fuzz_test_name}) endforeach() endfunction() add_regular_fuzz_targets( addition_overflow addrdb asmap asmap_direct base_encode_decode block block_header blockfilter bloom_filter cashaddr chain checkqueue + coins_view cuckoocache descriptor_parse eval_script fee_rate fees flatfile float golomb_rice hex http_request integer key key_io kitchen_sink locale merkleblock message multiplication_overflow net_permissions netaddress p2p_transport_deserializer parse_hd_keypath parse_iso8601 parse_numbers parse_script parse_univalue prevector pow primitives_transaction process_message process_messages protocol psbt random rolling_bloom_filter script script_flags script_ops scriptnum_ops signature_checker span spanparsing string strprintf system timedata transaction tx_in tx_out ) add_deserialize_fuzz_targets( addr_info_deserialize address_deserialize addrman_deserialize banentry_deserialize block_deserialize block_file_info_deserialize block_filter_deserialize block_header_and_short_txids_deserialize blockheader_deserialize blocklocator_deserialize blockmerkleroot blocktransactions_deserialize blocktransactionsrequest_deserialize blockundo_deserialize bloomfilter_deserialize coins_deserialize diskblockindex_deserialize fee_rate_deserialize flat_file_pos_deserialize inv_deserialize key_origin_info_deserialize merkle_block_deserialize messageheader_deserialize netaddr_deserialize out_point_deserialize partial_merkle_tree_deserialize partially_signed_transaction_deserialize prefilled_transaction_deserialize psbt_input_deserialize psbt_output_deserialize pub_key_deserialize script_deserialize service_deserialize snapshotmetadata_deserialize sub_net_deserialize tx_in_deserialize txoutcompressor_deserialize txundo_deserialize uint160_deserialize uint256_deserialize ) add_process_message_fuzz_targets( addr block blocktxn cmpctblock feefilter filteradd filterclear filterload getaddr getblocks getblocktxn getdata getheaders headers inv mempool notfound ping pong sendcmpct sendheaders tx verack version ) diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp new file mode 100644 index 000000000..d91449de6 --- /dev/null +++ b/src/test/fuzz/coins_view.cpp @@ -0,0 +1,314 @@ +// Copyright (c) 2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace { +const Coin EMPTY_COIN{}; + +bool operator==(const Coin &a, const Coin &b) { + if (a.IsSpent() && b.IsSpent()) { + return true; + } + return a.IsCoinBase() == b.IsCoinBase() && a.GetHeight() == b.GetHeight() && + a.GetTxOut() == b.GetTxOut(); +} +} // namespace + +void initialize() { + static const ECCVerifyHandle ecc_verify_handle; + ECC_Start(); + SelectParams(CBaseChainParams::REGTEST); +} + +void test_one_input(const std::vector &buffer) { + FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; + CCoinsView backend_coins_view; + CCoinsViewCache coins_view_cache{&backend_coins_view}; + COutPoint random_out_point; + Coin random_coin; + CMutableTransaction random_mutable_transaction; + while (fuzzed_data_provider.ConsumeBool()) { + switch (fuzzed_data_provider.ConsumeIntegralInRange(0, 9)) { + case 0: { + if (random_coin.IsSpent()) { + break; + } + Coin coin = random_coin; + bool expected_code_path = false; + const bool possible_overwrite = + fuzzed_data_provider.ConsumeBool(); + try { + coins_view_cache.AddCoin(random_out_point, std::move(coin), + possible_overwrite); + expected_code_path = true; + } catch (const std::logic_error &e) { + if (e.what() == + std::string{"Attempted to overwrite an unspent coin " + "(when possible_overwrite is false)"}) { + assert(!possible_overwrite); + expected_code_path = true; + } + } + assert(expected_code_path); + break; + } + case 1: { + (void)coins_view_cache.Flush(); + break; + } + case 2: { + coins_view_cache.SetBestBlock( + BlockHash(ConsumeUInt256(fuzzed_data_provider))); + break; + } + case 3: { + Coin move_to; + (void)coins_view_cache.SpendCoin( + random_out_point, + fuzzed_data_provider.ConsumeBool() ? &move_to : nullptr); + break; + } + case 4: { + coins_view_cache.Uncache(random_out_point); + break; + } + case 5: { + if (fuzzed_data_provider.ConsumeBool()) { + backend_coins_view = CCoinsView{}; + } + coins_view_cache.SetBackend(backend_coins_view); + break; + } + case 6: { + const std::optional opt_out_point = + ConsumeDeserializable(fuzzed_data_provider); + if (!opt_out_point) { + break; + } + random_out_point = *opt_out_point; + break; + } + case 7: { + const std::optional opt_coin = + ConsumeDeserializable(fuzzed_data_provider); + if (!opt_coin) { + break; + } + random_coin = *opt_coin; + break; + } + case 8: { + const std::optional + opt_mutable_transaction = + ConsumeDeserializable( + fuzzed_data_provider); + if (!opt_mutable_transaction) { + break; + } + random_mutable_transaction = *opt_mutable_transaction; + break; + } + case 9: { + CCoinsMap coins_map; + while (fuzzed_data_provider.ConsumeBool()) { + CCoinsCacheEntry coins_cache_entry; + coins_cache_entry.flags = + fuzzed_data_provider.ConsumeIntegral(); + if (fuzzed_data_provider.ConsumeBool()) { + coins_cache_entry.coin = random_coin; + } else { + const std::optional opt_coin = + ConsumeDeserializable(fuzzed_data_provider); + if (!opt_coin) { + break; + } + coins_cache_entry.coin = *opt_coin; + } + coins_map.emplace(random_out_point, + std::move(coins_cache_entry)); + } + bool expected_code_path = false; + try { + coins_view_cache.BatchWrite( + coins_map, + fuzzed_data_provider.ConsumeBool() + ? BlockHash(ConsumeUInt256(fuzzed_data_provider)) + : coins_view_cache.GetBestBlock()); + expected_code_path = true; + } catch (const std::logic_error &e) { + if (e.what() == + std::string{"FRESH flag misapplied to coin that exists " + "in parent cache"}) { + expected_code_path = true; + } + } + assert(expected_code_path); + break; + } + } + } + + { + const Coin &coin_using_access_coin = + coins_view_cache.AccessCoin(random_out_point); + const bool exists_using_access_coin = + !(coin_using_access_coin == EMPTY_COIN); + const bool exists_using_have_coin = + coins_view_cache.HaveCoin(random_out_point); + const bool exists_using_have_coin_in_cache = + coins_view_cache.HaveCoinInCache(random_out_point); + Coin coin_using_get_coin; + const bool exists_using_get_coin = + coins_view_cache.GetCoin(random_out_point, coin_using_get_coin); + if (exists_using_get_coin) { + assert(coin_using_get_coin == coin_using_access_coin); + } + assert((exists_using_access_coin && exists_using_have_coin_in_cache && + exists_using_have_coin && exists_using_get_coin) || + (!exists_using_access_coin && !exists_using_have_coin_in_cache && + !exists_using_have_coin && !exists_using_get_coin)); + const bool exists_using_have_coin_in_backend = + backend_coins_view.HaveCoin(random_out_point); + if (exists_using_have_coin_in_backend) { + assert(exists_using_have_coin); + } + Coin coin_using_backend_get_coin; + if (backend_coins_view.GetCoin(random_out_point, + coin_using_backend_get_coin)) { + assert(exists_using_have_coin_in_backend); + assert(coin_using_get_coin == coin_using_backend_get_coin); + } else { + assert(!exists_using_have_coin_in_backend); + } + } + + { + bool expected_code_path = false; + try { + (void)coins_view_cache.Cursor(); + } catch (const std::logic_error &) { + expected_code_path = true; + } + assert(expected_code_path); + (void)coins_view_cache.DynamicMemoryUsage(); + (void)coins_view_cache.EstimateSize(); + (void)coins_view_cache.GetBestBlock(); + (void)coins_view_cache.GetCacheSize(); + (void)coins_view_cache.GetHeadBlocks(); + (void)coins_view_cache.HaveInputs( + CTransaction{random_mutable_transaction}); + } + + { + const CCoinsViewCursor *coins_view_cursor = backend_coins_view.Cursor(); + assert(coins_view_cursor == nullptr); + (void)backend_coins_view.EstimateSize(); + (void)backend_coins_view.GetBestBlock(); + (void)backend_coins_view.GetHeadBlocks(); + } + + if (fuzzed_data_provider.ConsumeBool()) { + switch (fuzzed_data_provider.ConsumeIntegralInRange(0, 3)) { + case 0: { + const CTransaction transaction{random_mutable_transaction}; + bool is_spent = false; + for (const CTxOut &tx_out : transaction.vout) { + if (Coin{tx_out, 0, transaction.IsCoinBase()}.IsSpent()) { + is_spent = true; + } + } + if (is_spent) { + // Avoid: + // coins.cpp:69: void CCoinsViewCache::AddCoin(const + // COutPoint &, Coin &&, bool): Assertion `!coin.IsSpent()' + // failed. + break; + } + bool expected_code_path = false; + const int height = fuzzed_data_provider.ConsumeIntegral(); + const bool possible_overwrite = + fuzzed_data_provider.ConsumeBool(); + try { + AddCoins(coins_view_cache, transaction, height, + possible_overwrite); + expected_code_path = true; + } catch (const std::logic_error &e) { + if (e.what() == + std::string{"Attempted to overwrite an unspent coin " + "(when possible_overwrite is false)"}) { + assert(!possible_overwrite); + expected_code_path = true; + } + } + assert(expected_code_path); + break; + } + case 1: { + uint32_t flags = + fuzzed_data_provider.ConsumeIntegral(); + (void)AreInputsStandard( + CTransaction{random_mutable_transaction}, coins_view_cache, + flags); + break; + } + case 2: { + TxValidationState state; + Amount tx_fee_out; + const CTransaction transaction{random_mutable_transaction}; + if (ContainsSpentInput(transaction, coins_view_cache)) { + // Avoid: + // consensus/tx_verify.cpp:171: bool + // Consensus::CheckTxInputs(const CTransaction &, + // TxValidationState &, const CCoinsViewCache &, int, + // CAmount &): Assertion `!coin.IsSpent()' failed. + break; + } + try { + (void)Consensus::CheckTxInputs( + transaction, state, coins_view_cache, + fuzzed_data_provider.ConsumeIntegralInRange( + 0, std::numeric_limits::max()), + tx_fee_out); + assert(MoneyRange(tx_fee_out)); + } catch (const std::runtime_error &) { + } + break; + } + case 3: { + CCoinsStats stats; + bool expected_code_path = false; + try { + (void)GetUTXOStats(&coins_view_cache, stats); + } catch (const std::logic_error &) { + expected_code_path = true; + } + assert(expected_code_path); + break; + } + } + } +} diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h index 36ffd0284..cb680131a 100644 --- a/src/test/fuzz/util.h +++ b/src/test/fuzz/util.h @@ -1,149 +1,162 @@ // Copyright (c) 2009-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_TEST_FUZZ_UTIL_H #define BITCOIN_TEST_FUZZ_UTIL_H #include #include #include +#include #include