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 <amount.h> +#include <chainparams.h> +#include <chainparamsbase.h> +#include <coins.h> +#include <consensus/tx_verify.h> +#include <consensus/validation.h> +#include <key.h> +#include <node/coinstats.h> +#include <policy/policy.h> +#include <primitives/blockhash.h> +#include <primitives/transaction.h> +#include <pubkey.h> +#include <validation.h> + +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <test/fuzz/util.h> + +#include <cstdint> +#include <limits> +#include <optional> +#include <string> +#include <vector> + +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<uint8_t> &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<int>(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<COutPoint> opt_out_point = + ConsumeDeserializable<COutPoint>(fuzzed_data_provider); + if (!opt_out_point) { + break; + } + random_out_point = *opt_out_point; + break; + } + case 7: { + const std::optional<Coin> opt_coin = + ConsumeDeserializable<Coin>(fuzzed_data_provider); + if (!opt_coin) { + break; + } + random_coin = *opt_coin; + break; + } + case 8: { + const std::optional<CMutableTransaction> + opt_mutable_transaction = + ConsumeDeserializable<CMutableTransaction>( + 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<uint8_t>(); + if (fuzzed_data_provider.ConsumeBool()) { + coins_cache_entry.coin = random_coin; + } else { + const std::optional<Coin> opt_coin = + ConsumeDeserializable<Coin>(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<int>(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<int>(); + 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<uint32_t>(); + (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<int>( + 0, std::numeric_limits<int>::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 <amount.h> #include <arith_uint256.h> #include <attributes.h> +#include <coins.h> #include <script/script.h> #include <serialize.h> #include <streams.h> #include <uint256.h> #include <version.h> #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <cstdint> #include <string> #include <vector> NODISCARD inline std::vector<uint8_t> ConsumeRandomLengthByteVector(FuzzedDataProvider &fuzzed_data_provider, const size_t max_length = 4096) noexcept { const std::string s = fuzzed_data_provider.ConsumeRandomLengthString(max_length); return {s.begin(), s.end()}; } NODISCARD inline std::vector<std::string> ConsumeRandomLengthStringVector(FuzzedDataProvider &fuzzed_data_provider, const size_t max_vector_size = 16, const size_t max_string_length = 16) noexcept { const size_t n_elements = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, max_vector_size); std::vector<std::string> r; for (size_t i = 0; i < n_elements; ++i) { r.push_back( fuzzed_data_provider.ConsumeRandomLengthString(max_string_length)); } return r; } template <typename T> NODISCARD inline std::vector<T> ConsumeRandomLengthIntegralVector(FuzzedDataProvider &fuzzed_data_provider, const size_t max_vector_size = 16) noexcept { const size_t n_elements = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, max_vector_size); std::vector<T> r; for (size_t i = 0; i < n_elements; ++i) { r.push_back(fuzzed_data_provider.ConsumeIntegral<T>()); } return r; } template <typename T> NODISCARD inline std::optional<T> ConsumeDeserializable(FuzzedDataProvider &fuzzed_data_provider, const size_t max_length = 4096) noexcept { const std::vector<uint8_t> buffer = ConsumeRandomLengthByteVector(fuzzed_data_provider, max_length); CDataStream ds{buffer, SER_NETWORK, INIT_PROTO_VERSION}; T obj; try { ds >> obj; } catch (const std::ios_base::failure &) { return std::nullopt; } return obj; } NODISCARD inline opcodetype ConsumeOpcodeType(FuzzedDataProvider &fuzzed_data_provider) noexcept { return static_cast<opcodetype>( fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, MAX_OPCODE)); } NODISCARD inline Amount ConsumeMoney(FuzzedDataProvider &fuzzed_data_provider) noexcept { // FIXME find a better way to avoid duplicating the MAX_MONEY definition int64_t maxMoneyAsInt = int64_t(21000000) * int64_t(100000000); return fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, maxMoneyAsInt) * SATOSHI; } NODISCARD inline CScript ConsumeScript(FuzzedDataProvider &fuzzed_data_provider) noexcept { const std::vector<uint8_t> b = ConsumeRandomLengthByteVector(fuzzed_data_provider); return {b.begin(), b.end()}; } NODISCARD inline CScriptNum ConsumeScriptNum(FuzzedDataProvider &fuzzed_data_provider) noexcept { return CScriptNum{fuzzed_data_provider.ConsumeIntegral<int64_t>()}; } NODISCARD inline uint256 ConsumeUInt256(FuzzedDataProvider &fuzzed_data_provider) noexcept { const std::vector<uint8_t> v256 = fuzzed_data_provider.ConsumeBytes<uint8_t>(sizeof(uint256)); if (v256.size() != sizeof(uint256)) { return {}; } return uint256{v256}; } NODISCARD inline arith_uint256 ConsumeArithUInt256(FuzzedDataProvider &fuzzed_data_provider) noexcept { return UintToArith256(ConsumeUInt256(fuzzed_data_provider)); } template <typename T> NODISCARD bool MultiplicationOverflow(const T i, const T j) noexcept { static_assert(std::is_integral<T>::value, "Integral required."); if (std::numeric_limits<T>::is_signed) { if (i > 0) { if (j > 0) { return i > (std::numeric_limits<T>::max() / j); } else { return j < (std::numeric_limits<T>::min() / i); } } else { if (j > 0) { return i < (std::numeric_limits<T>::min() / j); } else { return i != 0 && (j < (std::numeric_limits<T>::max() / i)); } } } else { return j != 0 && i > std::numeric_limits<T>::max() / j; } } template <class T> NODISCARD bool AdditionOverflow(const T i, const T j) noexcept { static_assert(std::is_integral<T>::value, "Integral required."); if (std::numeric_limits<T>::is_signed) { return (i > 0 && j > std::numeric_limits<T>::max() - i) || (i < 0 && j < std::numeric_limits<T>::min() - i); } return std::numeric_limits<T>::max() - i < j; } +NODISCARD inline bool +ContainsSpentInput(const CTransaction &tx, + const CCoinsViewCache &inputs) noexcept { + for (const CTxIn &tx_in : tx.vin) { + const Coin &coin = inputs.AccessCoin(tx_in.prevout); + if (coin.IsSpent()) { + return true; + } + } + return false; +} + #endif // BITCOIN_TEST_FUZZ_UTIL_H