Skip to content

Commit

Permalink
Merge bitcoin#28815: fuzz: Avoid timeout and bloat in fuzz targets
Browse files Browse the repository at this point in the history
fabb504 fuzz: Avoid timeout and bloat in fuzz targets (MarcoFalke)

Pull request description:

  If the fuzz input contains invalid data *in a loop*, abort early. This will teach the fuzz engine to look for useful data and avoids bloating the fuzz input folder with useless (repeated) data.

ACKs for top commit:
  dergoegge:
    utACK fabb504
  brunoerg:
    crACK fabb504

Tree-SHA512: 26da100d7558ae6fdd5292fb146d8858b2af8f78c546ca2509b9d27b33a33e9462ecb6035de142f9f36dd5de32f8cbad099d6c7a697902d23e1bb621cd27dc88
  • Loading branch information
fanquake committed Nov 8, 2023
2 parents 9ad19fc + fabb504 commit f1f3f2d
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 28 deletions.
10 changes: 7 additions & 3 deletions src/test/fuzz/bloom_filter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,22 @@
#include <uint256.h>

#include <cassert>
#include <cstdint>
#include <limits>
#include <optional>
#include <string>
#include <vector>

FUZZ_TARGET(bloom_filter)
{
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
bool good_data{true};

CBloomFilter bloom_filter{
fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(1, 10000000),
1.0 / fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(1, std::numeric_limits<unsigned int>::max()),
fuzzed_data_provider.ConsumeIntegral<unsigned int>(),
static_cast<unsigned char>(fuzzed_data_provider.PickValueInArray({BLOOM_UPDATE_NONE, BLOOM_UPDATE_ALL, BLOOM_UPDATE_P2PUBKEY_ONLY, BLOOM_UPDATE_MASK}))};
LIMITED_WHILE(fuzzed_data_provider.remaining_bytes() > 0, 10000) {
LIMITED_WHILE(good_data && fuzzed_data_provider.remaining_bytes() > 0, 10'000)
{
CallOneOf(
fuzzed_data_provider,
[&] {
Expand All @@ -37,6 +38,7 @@ FUZZ_TARGET(bloom_filter)
[&] {
const std::optional<COutPoint> out_point = ConsumeDeserializable<COutPoint>(fuzzed_data_provider);
if (!out_point) {
good_data = false;
return;
}
(void)bloom_filter.contains(*out_point);
Expand All @@ -47,6 +49,7 @@ FUZZ_TARGET(bloom_filter)
[&] {
const std::optional<uint256> u256 = ConsumeDeserializable<uint256>(fuzzed_data_provider);
if (!u256) {
good_data = false;
return;
}
(void)bloom_filter.contains(*u256);
Expand All @@ -57,6 +60,7 @@ FUZZ_TARGET(bloom_filter)
[&] {
const std::optional<CMutableTransaction> mut_tx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider);
if (!mut_tx) {
good_data = false;
return;
}
const CTransaction tx{*mut_tx};
Expand Down
22 changes: 16 additions & 6 deletions src/test/fuzz/coins_view.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,28 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <chainparams.h>
#include <coins.h>
#include <consensus/amount.h>
#include <consensus/tx_check.h>
#include <consensus/tx_verify.h>
#include <consensus/validation.h>
#include <key.h>
#include <policy/policy.h>
#include <primitives/transaction.h>
#include <pubkey.h>
#include <script/interpreter.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
#include <test/util/setup_common.h>
#include <validation.h>
#include <util/hasher.h>

#include <cassert>
#include <cstdint>
#include <limits>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>

namespace {
Expand All @@ -44,12 +46,15 @@ void initialize_coins_view()
FUZZ_TARGET(coins_view, .init = initialize_coins_view)
{
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
bool good_data{true};

CCoinsView backend_coins_view;
CCoinsViewCache coins_view_cache{&backend_coins_view, /*deterministic=*/true};
COutPoint random_out_point;
Coin random_coin;
CMutableTransaction random_mutable_transaction;
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) {
LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 10'000)
{
CallOneOf(
fuzzed_data_provider,
[&] {
Expand Down Expand Up @@ -95,35 +100,40 @@ FUZZ_TARGET(coins_view, .init = initialize_coins_view)
[&] {
const std::optional<COutPoint> opt_out_point = ConsumeDeserializable<COutPoint>(fuzzed_data_provider);
if (!opt_out_point) {
good_data = false;
return;
}
random_out_point = *opt_out_point;
},
[&] {
const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider);
if (!opt_coin) {
good_data = false;
return;
}
random_coin = *opt_coin;
},
[&] {
const std::optional<CMutableTransaction> opt_mutable_transaction = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider);
if (!opt_mutable_transaction) {
good_data = false;
return;
}
random_mutable_transaction = *opt_mutable_transaction;
},
[&] {
CCoinsMapMemoryResource resource;
CCoinsMap coins_map{0, SaltedOutpointHasher{/*deterministic=*/true}, CCoinsMap::key_equal{}, &resource};
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) {
LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 10'000)
{
CCoinsCacheEntry coins_cache_entry;
coins_cache_entry.flags = fuzzed_data_provider.ConsumeIntegral<unsigned char>();
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) {
good_data = false;
return;
}
coins_cache_entry.coin = *opt_coin;
Expand Down
5 changes: 5 additions & 0 deletions src/test/fuzz/fuzz.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
/**
* Can be used to limit a theoretically unbounded loop. This caps the runtime
* to avoid timeouts or OOMs.
*
* This can be used in combination with a check in the condition to confirm
* whether the fuzz engine provided "good" data. If the fuzz input contains
* invalid data, the loop aborts early. This will teach the fuzz engine to look
* for useful data and avoids bloating the fuzz input folder with useless data.
*/
#define LIMITED_WHILE(condition, limit) \
for (unsigned _count{limit}; (condition) && _count; --_count)
Expand Down
14 changes: 9 additions & 5 deletions src/test/fuzz/policy_estimator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,14 @@
#include <policy/fees.h>
#include <policy/fees_args.h>
#include <primitives/transaction.h>
#include <streams.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
#include <test/fuzz/util/mempool.h>
#include <test/util/setup_common.h>
#include <txmempool.h>

#include <cstdint>
#include <optional>
#include <string>
#include <vector>

namespace {
Expand All @@ -31,13 +29,17 @@ void initialize_policy_estimator()
FUZZ_TARGET(policy_estimator, .init = initialize_policy_estimator)
{
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
bool good_data{true};

CBlockPolicyEstimator block_policy_estimator{FeeestPath(*g_setup->m_node.args), DEFAULT_ACCEPT_STALE_FEE_ESTIMATES};
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) {
LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 10'000)
{
CallOneOf(
fuzzed_data_provider,
[&] {
const std::optional<CMutableTransaction> mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider);
if (!mtx) {
good_data = false;
return;
}
const CTransaction tx{*mtx};
Expand All @@ -48,9 +50,11 @@ FUZZ_TARGET(policy_estimator, .init = initialize_policy_estimator)
},
[&] {
std::vector<CTxMemPoolEntry> mempool_entries;
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) {
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000)
{
const std::optional<CMutableTransaction> mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider);
if (!mtx) {
good_data = false;
break;
}
const CTransaction tx{*mtx};
Expand Down
37 changes: 23 additions & 14 deletions src/test/fuzz/rpc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,38 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <base58.h>
#include <core_io.h>
#include <key.h>
#include <key_io.h>
#include <node/context.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
#include <psbt.h>
#include <rpc/blockchain.h>
#include <rpc/client.h>
#include <rpc/request.h>
#include <rpc/server.h>
#include <rpc/util.h>
#include <span.h>
#include <streams.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
#include <test/util/setup_common.h>
#include <tinyformat.h>
#include <uint256.h>
#include <univalue.h>
#include <util/chaintype.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/time.h>

#include <algorithm>
#include <cassert>
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <vector>
enum class ChainType;

namespace {
struct RPCFuzzTestingSetup : public TestingSetup {
Expand Down Expand Up @@ -184,7 +184,7 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{
"waitfornewblock",
};

std::string ConsumeScalarRPCArgument(FuzzedDataProvider& fuzzed_data_provider)
std::string ConsumeScalarRPCArgument(FuzzedDataProvider& fuzzed_data_provider, bool& good_data)
{
const size_t max_string_length = 4096;
const size_t max_base58_bytes_length{64};
Expand Down Expand Up @@ -251,6 +251,7 @@ std::string ConsumeScalarRPCArgument(FuzzedDataProvider& fuzzed_data_provider)
// hex encoded block
std::optional<CBlock> opt_block = ConsumeDeserializable<CBlock>(fuzzed_data_provider);
if (!opt_block) {
good_data = false;
return;
}
CDataStream data_stream{SER_NETWORK, PROTOCOL_VERSION};
Expand All @@ -261,6 +262,7 @@ std::string ConsumeScalarRPCArgument(FuzzedDataProvider& fuzzed_data_provider)
// hex encoded block header
std::optional<CBlockHeader> opt_block_header = ConsumeDeserializable<CBlockHeader>(fuzzed_data_provider);
if (!opt_block_header) {
good_data = false;
return;
}
DataStream data_stream{};
Expand All @@ -271,6 +273,7 @@ std::string ConsumeScalarRPCArgument(FuzzedDataProvider& fuzzed_data_provider)
// hex encoded tx
std::optional<CMutableTransaction> opt_tx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider);
if (!opt_tx) {
good_data = false;
return;
}
CDataStream data_stream{SER_NETWORK, fuzzed_data_provider.ConsumeBool() ? PROTOCOL_VERSION : (PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS)};
Expand All @@ -281,6 +284,7 @@ std::string ConsumeScalarRPCArgument(FuzzedDataProvider& fuzzed_data_provider)
// base64 encoded psbt
std::optional<PartiallySignedTransaction> opt_psbt = ConsumeDeserializable<PartiallySignedTransaction>(fuzzed_data_provider);
if (!opt_psbt) {
good_data = false;
return;
}
CDataStream data_stream{SER_NETWORK, PROTOCOL_VERSION};
Expand All @@ -291,6 +295,7 @@ std::string ConsumeScalarRPCArgument(FuzzedDataProvider& fuzzed_data_provider)
// base58 encoded key
CKey key = ConsumePrivateKey(fuzzed_data_provider);
if (!key.IsValid()) {
good_data = false;
return;
}
r = EncodeSecret(key);
Expand All @@ -299,25 +304,27 @@ std::string ConsumeScalarRPCArgument(FuzzedDataProvider& fuzzed_data_provider)
// hex encoded pubkey
CKey key = ConsumePrivateKey(fuzzed_data_provider);
if (!key.IsValid()) {
good_data = false;
return;
}
r = HexStr(key.GetPubKey());
});
return r;
}

std::string ConsumeArrayRPCArgument(FuzzedDataProvider& fuzzed_data_provider)
std::string ConsumeArrayRPCArgument(FuzzedDataProvider& fuzzed_data_provider, bool& good_data)
{
std::vector<std::string> scalar_arguments;
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100) {
scalar_arguments.push_back(ConsumeScalarRPCArgument(fuzzed_data_provider));
LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 100)
{
scalar_arguments.push_back(ConsumeScalarRPCArgument(fuzzed_data_provider, good_data));
}
return "[\"" + Join(scalar_arguments, "\",\"") + "\"]";
}

std::string ConsumeRPCArgument(FuzzedDataProvider& fuzzed_data_provider)
std::string ConsumeRPCArgument(FuzzedDataProvider& fuzzed_data_provider, bool& good_data)
{
return fuzzed_data_provider.ConsumeBool() ? ConsumeScalarRPCArgument(fuzzed_data_provider) : ConsumeArrayRPCArgument(fuzzed_data_provider);
return fuzzed_data_provider.ConsumeBool() ? ConsumeScalarRPCArgument(fuzzed_data_provider, good_data) : ConsumeArrayRPCArgument(fuzzed_data_provider, good_data);
}

RPCFuzzTestingSetup* InitializeRPCFuzzTestingSetup()
Expand Down Expand Up @@ -353,6 +360,7 @@ void initialize_rpc()
FUZZ_TARGET(rpc, .init = initialize_rpc)
{
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
bool good_data{true};
SetMockTime(ConsumeTime(fuzzed_data_provider));
const std::string rpc_command = fuzzed_data_provider.ConsumeRandomLengthString(64);
if (!g_limit_to_rpc_command.empty() && rpc_command != g_limit_to_rpc_command) {
Expand All @@ -363,8 +371,9 @@ FUZZ_TARGET(rpc, .init = initialize_rpc)
return;
}
std::vector<std::string> arguments;
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100) {
arguments.push_back(ConsumeRPCArgument(fuzzed_data_provider));
LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 100)
{
arguments.push_back(ConsumeRPCArgument(fuzzed_data_provider, good_data));
}
try {
rpc_testing_setup->CallRPC(rpc_command, arguments);
Expand Down

0 comments on commit f1f3f2d

Please sign in to comment.