0
0
mirror of https://github.com/monero-project/monero synced 2025-10-06 00:32:44 +02:00

rpc-fuzz: Add new fuzzers for RPC endpoints

Signed-off-by: Arthur Chan <arthur.chan@adalogics.com>

Add macro definition for fuzzers

Signed-off-by: Arthur Chan <arthur.chan@adalogics.com>

Fix FuzzedDataProvider header missing problem

Signed-off-by: Arthur Chan <arthur.chan@adalogics.com>

Add README

Signed-off-by: Arthur Chan <arthur.chan@adalogics.com>

Provide static FuzzedDataProvider.h

Signed-off-by: Arthur Chan <arthur.chan@adalogics.com>

Update and enhance fuzzer

Signed-off-by: Arthur Chan <arthur.chan@adalogics.com>

Activate UBSan

Signed-off-by: Arthur Chan <arthur.chan@adalogics.com>

Fix fuzz target retrieval

Signed-off-by: Arthur Chan <arthur.chan@adalogics.com>

Remove bias selector and fix protocol lifespan

Signed-off-by: Arthur Chan <arthur.chan@adalogics.com>

Drop SIGALARM handling and fix bug on selectors

Signed-off-by: Arthur Chan <arthur.chan@adalogics.com>

Fix rpc request changes

Signed-off-by: Arthur Chan <arthur.chan@adalogics.com>

Add a new fuzzer profile that catch all expcetions

Signed-off-by: Arthur Chan <arthur.chan@adalogics.com>

Fix typo

Signed-off-by: Arthur Chan <arthur.chan@adalogics.com>

Add warning

Signed-off-by: Arthur Chan <arthur.chan@adalogics.com>
This commit is contained in:
Arthur Chan
2025-07-18 17:08:50 +00:00
parent fbc242d52d
commit 36bdfad073
13 changed files with 2183 additions and 3 deletions

View File

@@ -484,8 +484,8 @@ endif()
option(SANITIZE "Use ASAN memory sanitizer" OFF)
if(SANITIZE)
message(STATUS "Using ASAN")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address,undefined")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined")
endif()
# Set default blockchain storage location:

View File

@@ -90,7 +90,12 @@ namespace tools
el::Level performance_timer_log_level = el::Level::Info;
static __thread std::vector<LoggingPerformanceTimer*> *performance_timers = NULL;
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
#warning "Building with fuzzing mode UNSAFE FOR PRODUCTION!"
__thread std::vector<LoggingPerformanceTimer*> *performance_timers = NULL;
#else
static __thread std::vector<LoggingPerformanceTimer*> *performance_timers = NULL;
#endif
void set_performance_timer_log_level(el::Level level)
{

View File

@@ -26,6 +26,75 @@
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# Add the include path for <fuzzer/FuzzedDataProvider.h>
include_directories(${CMAKE_SOURCE_DIR}/tests/fuzz/include)
# Recompile perf_timer for fuzzing
add_library(fuzz_unsafe_macro OBJECT
${CMAKE_SOURCE_DIR}/src/common/perf_timer.cpp)
target_compile_definitions(fuzz_unsafe_macro
PRIVATE FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
monero_add_minimal_executable(fuzz_rpc
fuzz_rpc/initialisation.cpp
fuzz_rpc/rpc_endpoints.cpp
fuzz_rpc/fuzz_rpc.cpp
$<TARGET_OBJECTS:fuzz_unsafe_macro>)
target_compile_definitions(fuzz_rpc PRIVATE SAFE)
target_link_libraries(fuzz_rpc
PRIVATE
rpc
${CMAKE_THREAD_LIBS_INIT}
${EXTRA_LIBRARIES}
$ENV{LIB_FUZZING_ENGINE})
set_property(TARGET fuzz_rpc
PROPERTY
FOLDER "tests")
monero_add_minimal_executable(fuzz_rpc_full
fuzz_rpc/initialisation.cpp
fuzz_rpc/rpc_endpoints.cpp
fuzz_rpc/fuzz_rpc.cpp
$<TARGET_OBJECTS:fuzz_unsafe_macro>)
target_link_libraries(fuzz_rpc_full
PRIVATE
rpc
${CMAKE_THREAD_LIBS_INIT}
${EXTRA_LIBRARIES}
$ENV{LIB_FUZZING_ENGINE})
set_property(TARGET fuzz_rpc_full
PROPERTY
FOLDER "tests")
monero_add_minimal_executable(fuzz_rpc_full_no_exceptions
fuzz_rpc/initialisation.cpp
fuzz_rpc/rpc_endpoints.cpp
fuzz_rpc/fuzz_rpc.cpp
$<TARGET_OBJECTS:fuzz_unsafe_macro>)
target_compile_definitions(fuzz_rpc_full_no_exceptions PRIVATE CATCH_ALL_EXCEPTIONS)
target_link_libraries(fuzz_rpc_full_no_exceptions
PRIVATE
rpc
${CMAKE_THREAD_LIBS_INIT}
${EXTRA_LIBRARIES}
$ENV{LIB_FUZZING_ENGINE})
set_property(TARGET fuzz_rpc_full_no_exceptions
PROPERTY
FOLDER "tests")
monero_add_minimal_executable(fuzz_zmq
fuzz_rpc/zmq_endpoints.cpp
fuzz_rpc/fuzz_zmq.cpp)
target_link_libraries(fuzz_zmq
PRIVATE
rpc_pub
${CMAKE_THREAD_LIBS_INIT}
${EXTRA_LIBRARIES}
$ENV{LIB_FUZZING_ENGINE})
set_property(TARGET fuzz_zmq
PROPERTY
FOLDER "tests")
monero_add_minimal_executable(block_fuzz_tests block.cpp fuzzer.cpp)
target_link_libraries(block_fuzz_tests
PRIVATE

View File

@@ -0,0 +1,50 @@
# Fuzzing Harness Explanation
The fuzzing harness skips the transport layer and directly initialises a `core_rpc_server` object. It then calls and fuzzes the RPC endpoint function handlers directly, removing the need to start a fake server while still allowing the handler logic for each RPC API to be fuzzed.
## `fuzz_rpc.cpp` / `fuzz_zmq.cpp`
These are the main fuzzing entry points, containing `LLVMFuzzerTestOneInput`, which manages each iteration of the fuzzing process.
## `initialisation.cpp` / `initialisation.h`
This set of source files provides initialisation helper functions to configure the `core_rpc_server` class with dummy protocols, P2P, payment modules, and other components. It also includes functions to generate fake random blocks, miners, and transactions for use in fuzzing.
## `rpc_endpoints.cpp` / `rpc_endpoints.h` / `zmq_endpoints.cpp` / `zmq_endpoints.h`
These source files handle the creation of the necessary request and response objects and the invocation of specific endpoint functions. These simulate real RPC requests / ZMQ requests for the purpose of fuzzing. There are three categories of RPC endpoint functions and only one for ZMQ endpoint functions:
* **Safe**: Considered stable and unlikely to fail.
* **Risky**: More prone to failure and early exits, especially if no valid blockchain is generated or no payment modules are configured.
* **Priority**: Most critical RPC endpoint functions that will at least run once per iteration.
# Building the Fuzzing Harnesses
The same `fuzz_rpc.cpp` is compiled into two versions of the fuzzer, one with the `SAFE` macro defined and one without. Meanwhile, `fuzz_zmq` is compiled into a single version.
* With `SAFE` defined: Only safe endpoint functions are fuzzed.
* Without `SAFE`: Both safe and risky functions are included in the fuzzing process, and payment modules are configured.
# Fuzzing Harness Flow
## Select a Random RPC Endpoint Function / ZMQ Endpoint Function
Random data is used to generate selectors that choose an RPC endpoint function / ZMQ endpoint function to fuzz, either from the safe or risky function maps (depending on whether the `SAFE` macro is defined). A function from the priority category is always included at least once.
## `initialise_rpc_core` / `initialise_rpc_server`
At the start of each fuzzing iteration, a `core_rpc_server` object is created and initialised with dummy protocols, P2P, payment modules, and more.
If the `SAFE` macro is not defined, the payment module will also be initialised.
This step is skipped for `fuzz_zmq`.
## `generate_random_blocks`
The provided random data is used to initialise a set of random blocks, which are added to a dummy blockchain (initialised using `FAKECHAIN`).
If blocks are successfully added, a list of random transactions is then generated and also pushed to the blockchain.
This step is skipped for `fuzz_zmq`.
## Real Fuzzing
The selected RPC endpoint helper function in `rpc_endpoints` or `zmq_endpoints` generates a random request and response object for the specific call, and then invokes the corresponding handler in the `core_rpc_server` object.

View File

@@ -0,0 +1,91 @@
#include "initialisation.h"
#include "rpc_endpoints.h"
#include <fuzzer/FuzzedDataProvider.h>
#include <csignal>
#include <csetjmp>
#include <iostream>
#include <vector>
#include <algorithm>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
// In general an iteration needs a fair amount of data so ensure we have enough
// to work with, otherwise return 0 to skip this iteration.
if (size < 512) {
return 0;
}
// Determine if this iteration should run in safe mode
#ifdef SAFE
constexpr bool is_safe_mode = true;
#else
constexpr bool is_safe_mode = false;
#endif
// Retrieve a list of all fuzz targets
auto fuzz_targets = get_fuzz_targets(is_safe_mode);
// Disable fatal exits for logging.
el::Loggers::addFlag(el::LoggingFlag::DisableApplicationAbortOnFatalLog);
// Prepare base FuzzedDataProvider
FuzzedDataProvider provider(data, size);
// Randomly choose multiple fuzz_targets to fuzz
int rpc_messages_to_send = provider.ConsumeIntegralInRange<int>(1, 16);
std::vector<int> selectors;
if (is_safe_mode) {
selectors.reserve(rpc_messages_to_send);
} else {
selectors.reserve(rpc_messages_to_send + priority_fuzz_targets.size());
for (int i = 0; i < priority_fuzz_targets.size(); ++i) {
selectors.push_back(i);
}
// Randomly shuffle the selectors for priority fuzz targets
for (int i = 0; i < priority_fuzz_targets.size(); i++) {
int target = provider.ConsumeIntegralInRange<int>(0, priority_fuzz_targets.size() - 1);
std::swap(selectors[i], selectors[target]);
}
}
// Randomly select rpc functions to call
for (int i = 0; i < rpc_messages_to_send && provider.remaining_bytes() >= 2; ++i) {
int selector = provider.ConsumeIntegralInRange<int>(0, fuzz_targets.size() - 1);
selectors.push_back(selector);
}
// Initialise core and core_rpc_server
auto core_env = initialise_rpc_core();
auto& dummy_core = core_env->core;
auto rpc_handler = initialise_rpc_server(*dummy_core, provider, !is_safe_mode);
// Generate random blocks/miners/transactions and push to the core blockchains
if (!generate_random_blocks(*dummy_core, provider)) {
// No randomised blocks have been successfully added, skipping this iteration
dummy_core->get_blockchain_storage().get_db().batch_stop();
return 0;
}
// Disable bootstrap daemon
disable_bootstrap_daemon(*rpc_handler->rpc);
for (int selector : selectors) {
try {
// Fuzz the target function
fuzz_targets[selector](*rpc_handler->rpc, provider);
} catch (const std::runtime_error&) {
// Known runtime_error thrown from monero
} catch (const cryptonote::DB_ERROR& e) {
// Known error thrown from monero on internal blockchain DB check
// when fuzzing with random values
#ifdef CATCH_ALL_EXCEPTIONS
} catch (...) {
// Silent all exceptions
#endif
}
}
dummy_core->get_blockchain_storage().get_db().batch_stop();
return 0;
}

View File

@@ -0,0 +1,43 @@
#include <fuzzer/FuzzedDataProvider.h>
#include "zmq_endpoints.h"
#include <vector>
#include <cstring>
using namespace cryptonote;
using namespace cryptonote::listener;
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size < 64) {
return 0;
}
FuzzedDataProvider provider(data, size);
void* ctx = zmq_ctx_new();
if (!ctx) {
return 0;
}
// Randomly choose multiple zmq_targets to fuzz
int to_sent = provider.ConsumeIntegralInRange<int>(1, 8);
std::vector<int> selectors;
selectors.reserve(to_sent);
for (int i = 0; i < to_sent && provider.remaining_bytes() >= 2; ++i) {
uint16_t raw = provider.ConsumeIntegral<uint16_t>();
selectors.push_back(raw % zmq_targets.size());
}
try {
zmq_pub pub(ctx);
for (int selector : selectors) {
zmq_targets[selector](pub, provider);
}
} catch (const std::runtime_error& e) {
// Ignore known runtime_error from checking
}
zmq_ctx_shutdown(ctx);
zmq_ctx_term(ctx);
return 0;
}

View File

@@ -0,0 +1,357 @@
#include "initialisation.h"
#include "crypto/crypto.h"
#include "cryptonote_config.h"
#include "cryptonote_basic/cryptonote_basic.h"
#include "cryptonote_basic/cryptonote_basic_impl.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "cryptonote_basic/tx_extra.h"
#include "cryptonote_core/cryptonote_core.h"
#include "cryptonote_core/cryptonote_tx_utils.h"
#include "cryptonote_core/tx_pool.h"
#include "cryptonote_protocol/cryptonote_protocol_handler.h"
#include "p2p/net_node.h"
#include "p2p/net_node.inl"
#include "ringct/rctTypes.h"
#include "serialization/binary_utils.h"
#include "serialization/variant.h"
#include <boost/program_options/variables_map.hpp>
#include <vector>
#include <string>
#include <functional>
#include <unordered_map>
// For storing valid transaction hashes
std::vector<crypto::hash> cached_tx_hashes;
// Configure a dummy protocol to avoid external requests
bool DummyProtocol::is_synchronized() const {
return true;
}
bool DummyProtocol::relay_transactions(cryptonote::NOTIFY_NEW_TRANSACTIONS::request&, const boost::uuids::uuid&, epee::net_utils::zone, cryptonote::relay_method) {
return true;
}
bool DummyProtocol::relay_block(cryptonote::NOTIFY_NEW_FLUFFY_BLOCK::request&, cryptonote::cryptonote_connection_context&) {
return true;
}
// Function to create and initialise a dummy rpc core object
std::unique_ptr<CoreEnv> initialise_rpc_core() {
auto env = std::make_unique<CoreEnv>();
// Create fresh pointer for dummy protocol and core
env->protocol = std::make_unique<DummyProtocol>();
env->core = std::make_unique<cryptonote::core>(env->protocol.get());
// Configure the rpc core object with default settings
boost::program_options::options_description desc;
cryptonote::core::init_options(desc);
boost::program_options::variables_map vm;
// Add command line argument to configure the rpc core object to use regression testing mode (FAKECHAIN)
// Enabling FAKECHAIN mode allows skipping validation logic of the authors signature and transactions ID
// on valid blocks and transactions while keeping other logic
std::vector<const char*> argv = {"fuzz", "--regtest"};
boost::program_options::store(boost::program_options::parse_command_line(argv.size(), argv.data(), desc), vm);
boost::program_options::notify(vm);
// Initialise the dummy core with all the above settings
env->core->init(vm);
return env;
}
// Build the core_rpc_server handler object with the configured dummy core object
std::unique_ptr<RpcServerBundle> initialise_rpc_server(cryptonote::core& dummy_core, FuzzedDataProvider& provider, bool need_payment) {
// Create rpc server bundle object
auto bundle = std::make_unique<RpcServerBundle>();
// Allocate new protocol and node server then create RPC server object
bundle->proto_handler = std::make_unique<cryptonote::t_cryptonote_protocol_handler<cryptonote::core>>(dummy_core, nullptr, true);
bundle->proto_handler->set_no_sync(false);
bundle->dummy_p2p = std::make_unique<nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core>>>(*bundle->proto_handler);
bundle->rpc = std::make_unique<cryptonote::core_rpc_server>(dummy_core, *bundle->dummy_p2p);
// Set up dummy variable map for rpc initialisation with payment
if (need_payment) {
boost::program_options::variables_map vm;
boost::program_options::options_description desc{"fuzz"};
command_line::add_arg(desc, cryptonote::arg_data_dir);
command_line::add_arg(desc, cryptonote::arg_testnet_on);
command_line::add_arg(desc, cryptonote::arg_stagenet_on);
cryptonote::core_rpc_server::init_options(desc);
// Generate random address and use it if it is a valid address with valid format
std::string address_arg;
if (provider.remaining_bytes() > 95) {
std::string random_str = provider.ConsumeBytesAsString(95);
if (!random_str.empty() && std::all_of(random_str.begin(), random_str.end(), [](char c) {
return isalnum(c);
})) {
address_arg = "--rpc-payment-address=" + random_str;
}
}
// Fall back to default hardcoded address if generated address is invalid
if (address_arg.empty()) {
address_arg = "--rpc-payment-address=44AFFq5kSiGBoZKfRLKFY7bUuS5JxqLkZ3Zf1diYv5ZdfTP7hS5gZtSGdgjNXmYGFzRiV3yTgF8Yf4zrhGcq14D3z8PUnHT";
}
// Provide needed payment related configuration for init of core_rpc_server
std::vector<const char*> argv = {
"fuzz",
address_arg.c_str()
};
boost::program_options::store(boost::program_options::parse_command_line(argv.size(), argv.data(), desc), vm);
boost::program_options::notify(vm);
bool success = bundle->rpc->init(vm, true, "18089", true, "");
if (!success) {
// Revert back to a fresh core_rpc_server if payment module init is failed
bundle->rpc = std::make_unique<cryptonote::core_rpc_server>(dummy_core, *bundle->dummy_p2p);
}
}
return bundle;
}
bool generate_random_blocks(cryptonote::core& core, FuzzedDataProvider& provider) {
static std::vector<cryptonote::block> cached_blocks;
static std::vector<cryptonote::blobdata> cached_txs;
bool use_cached_blocks = provider.ConsumeBool();
bool use_cached_txs = provider.ConsumeBool();
if (!use_cached_blocks || cached_blocks.empty()) {
// Create new cached blocks
cached_blocks.clear();
// Prepare the genesis block (initial base block) in the block chains
cryptonote::block genesis_block;
cryptonote::generate_genesis_block(genesis_block, config::GENESIS_TX, config::GENESIS_NONCE);
cached_blocks.push_back(genesis_block);
// Setup initial values
const size_t median_weight = 300000;
const size_t block_weight = 100000;
const uint8_t version = core.get_blockchain_storage().get_current_hard_fork_version();
// Accumulate coins for genesis block
uint64_t coins = 0;
for (const auto& o : genesis_block.miner_tx.vout) {
coins += o.amount;
}
// Create random number of miners
size_t miner_count = provider.ConsumeIntegralInRange<size_t>(1, 4);
std::vector<cryptonote::account_base> miners(miner_count);
for (auto& miner : miners) {
miner.generate();
}
// Create random number of blocks
size_t total_blocks = provider.ConsumeIntegralInRange<size_t>(2, 15);
for (size_t i = 0; i < total_blocks; ++i) {
// Stop generate blocks if no more bytes left
if (provider.remaining_bytes() <= 0) {
break;
}
// Randomly link a miner identity to this block
cryptonote::block blk;
size_t selected_miner_index = provider.ConsumeIntegralInRange<size_t>(0, miners.size() - 1);
cryptonote::account_base& miner = miners[selected_miner_index];
// Initialise block property with random value
uint64_t height = cached_blocks.size();
crypto::hash prev_id = cached_blocks.back().hash;
blk.major_version = version;
blk.minor_version = version;
blk.timestamp = core.get_blockchain_storage().get_adjusted_time(height);
blk.prev_id = prev_id;
blk.nonce = provider.ConsumeIntegral<uint32_t>();
// Calculate and accumulate reward
uint64_t base_reward = 0, fee = 0;
if (!cryptonote::get_block_reward(median_weight, block_weight, coins, base_reward, version)) {
break;
}
// Link empty miner transaction to block
cryptonote::transaction miner_tx;
cryptonote::construct_miner_tx(
height, median_weight, coins, block_weight,
fee, miner.get_keys().m_account_address,
miner_tx, cryptonote::blobdata(),
1, blk.major_version
);
for (const auto& o : miner_tx.vout) {
coins += o.amount;
}
miner_tx.version = 2;
blk.miner_tx = miner_tx;
// Add the block to the cache
cached_blocks.push_back(blk);
}
}
if (cached_blocks.empty()) {
// No random block generated, exit early to next iteration
return false;
}
if (!use_cached_txs || cached_txs.empty()) {
// Create new cache transactions
cached_txs.clear();
cached_tx_hashes.clear();
// Random number of transactions to generate
const size_t tx_count = provider.ConsumeIntegralInRange<size_t>(1, 4);
for (size_t i = 0; i < tx_count; ++i) {
// Stop generate transactions if no more bytes left
if (provider.remaining_bytes() <= 0) {
break;
}
// Initialise variables
cryptonote::transaction tx;
std::vector<cryptonote::tx_source_entry> sources;
std::vector<cryptonote::tx_destination_entry> destinations;
std::vector<uint8_t> extra;
// Generate sender account
cryptonote::account_base sender;
sender.generate();
// Generate receiver account
cryptonote::account_base receiver;
receiver.generate();
// Generate random transfer amount
uint64_t transfer_amount = provider.ConsumeIntegralInRange<uint64_t>(10000, 1000000000);
uint64_t input_amount = transfer_amount + 10000;
// Prepare source entry
cryptonote::tx_source_entry src{};
src.amount = input_amount;
src.rct = false;
src.real_output = 0;
src.real_output_in_tx_index = 0;
// Derive valid public key
hw::device &hwdev = sender.get_keys().get_device();
cryptonote::keypair tx_key = cryptonote::keypair::generate(hwdev);
crypto::key_derivation derivation;
if (!crypto::generate_key_derivation(sender.get_keys().m_account_address.m_view_public_key, tx_key.sec, derivation)) {
continue;
}
crypto::public_key out_pub_key;
if (!crypto::derive_public_key(derivation, 0, sender.get_keys().m_account_address.m_spend_public_key, out_pub_key)) {
continue;
}
src.real_out_tx_key = tx_key.pub;
// Configure output entry for source_entry
for (size_t j = 0; j < 10; ++j) {
cryptonote::tx_source_entry::output_entry oe;
oe.first = j;
crypto::public_key pk;
if (j == src.real_output) {
pk = out_pub_key;
} else {
cryptonote::keypair fake = cryptonote::keypair::generate(hwdev);
pk = fake.pub;
}
crypto::view_tag vtag{};
vtag.data = static_cast<uint8_t>(provider.ConsumeIntegral<uint8_t>());
cryptonote::txout_to_tagged_key tagged{};
tagged.key = pk;
tagged.view_tag = vtag;
oe.second.dest = *reinterpret_cast<const rct::key*>(&tagged.key);
oe.second.mask = rct::identity();
src.outputs.push_back(oe);
}
sources.push_back(src);
// Prepare destination entry
cryptonote::tx_destination_entry dst;
dst.amount = transfer_amount;
dst.addr = receiver.get_keys().m_account_address;
dst.is_subaddress = false;
destinations.push_back(dst);
// Construct the transaction
crypto::secret_key tx_secret_key;
std::vector<crypto::secret_key> additional_tx_keys;
std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
subaddresses[sender.get_keys().m_account_address.m_spend_public_key] = {0, 0};
std::vector<cryptonote::tx_destination_entry> destinations_copy = destinations;
// Construct transaction objects
bool success = cryptonote::construct_tx_and_get_tx_key(
sender.get_keys(),
subaddresses,
sources,
destinations_copy,
boost::none,
extra,
tx,
tx_secret_key,
additional_tx_keys,
true,
{rct::RangeProofPaddedBulletproof, 0},
true
);
if (!success) {
continue;
}
// Force version and unlock_time
tx.version = 2;
tx.unlock_time = 0;
// Serialise the transaction
cryptonote::blobdata tx_blob;
if (!cryptonote::t_serializable_object_to_blob(tx, tx_blob)) {
continue;
}
// Add the transaction to cache
cached_txs.push_back(tx_blob);
}
}
core.get_blockchain_storage().get_db().batch_start();
bool added_block = false;
for (const auto& blk : cached_blocks) {
cryptonote::block_verification_context bvc{};
if (core.get_blockchain_storage().add_new_block(blk, bvc)) {
added_block = true;
}
}
bool added_txs = false;
for (const auto& tx_blob : cached_txs) {
cryptonote::tx_verification_context tvc;
bool accepted = core.handle_incoming_tx(tx_blob, tvc, cryptonote::relay_method::block, true);
if (accepted || tvc.m_added_to_pool) {
added_txs = true;
// Store legit hashes
cryptonote::transaction tx;
if (cryptonote::parse_and_validate_tx_from_blob(tx_blob, tx)) {
cached_tx_hashes.push_back(cryptonote::get_transaction_hash(tx));
}
}
}
return added_block;
}

View File

@@ -0,0 +1,31 @@
#pragma once
#include "crypto/hash.h"
#include "rpc/core_rpc_server.h"
#include <fuzzer/FuzzedDataProvider.h>
// Define templates for dummy protocol object
template class nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core>>;
struct DummyProtocol : public cryptonote::i_cryptonote_protocol {
public:
bool is_synchronized() const;
bool relay_transactions(cryptonote::NOTIFY_NEW_TRANSACTIONS::request&, const boost::uuids::uuid&, epee::net_utils::zone, cryptonote::relay_method);
bool relay_block(cryptonote::NOTIFY_NEW_FLUFFY_BLOCK::request&, cryptonote::cryptonote_connection_context&);
};
struct RpcServerBundle {
std::unique_ptr<cryptonote::t_cryptonote_protocol_handler<cryptonote::core>> proto_handler;
std::unique_ptr<nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core>>> dummy_p2p;
std::unique_ptr<cryptonote::core_rpc_server> rpc;
};
struct CoreEnv {
std::unique_ptr<DummyProtocol> protocol;
std::unique_ptr<cryptonote::core> core;
};
std::unique_ptr<CoreEnv> initialise_rpc_core();
std::unique_ptr<RpcServerBundle> initialise_rpc_server(cryptonote::core&, FuzzedDataProvider&, bool);
bool generate_random_blocks(cryptonote::core&, FuzzedDataProvider&);
extern std::vector<crypto::hash> cached_tx_hashes;

View File

@@ -0,0 +1,939 @@
#include "rpc_endpoints.h"
#include "initialisation.h"
#include "rpc/rpc_payment_signature.h"
#include <cstring>
#include <map>
// Initialising common objects for calling RPC endpoint functions
cryptonote::core_rpc_server::connection_context ctx;
epee::json_rpc::error error_resp;
// Helper function to disable bootstrap daemon
void disable_bootstrap_daemon(cryptonote::core_rpc_server& rpc) {
cryptonote::COMMAND_RPC_SET_BOOTSTRAP_DAEMON::request req;
cryptonote::COMMAND_RPC_SET_BOOTSTRAP_DAEMON::response res;
req.address = "";
req.username = "";
req.password = "";
req.proxy = "";
rpc.on_set_bootstrap_daemon(req, res, &ctx);
}
// Retrieve fuzz targets base on SAFE settings
std::map<int, std::function<void(cryptonote::core_rpc_server& rpc, FuzzedDataProvider&)>> get_fuzz_targets(bool safe) {
std::map<int, std::function<void(cryptonote::core_rpc_server& rpc, FuzzedDataProvider&)>> results;
if (safe) {
// Only return safe and stable fuzz targets after re-indexing
for (const auto& kv : safe_fuzz_targets) {
results[kv.first - 14] = kv.second;
}
} else {
// Return the full list of fuzz targets
results.insert(priority_fuzz_targets.begin(), priority_fuzz_targets.end());
results.insert(safe_fuzz_targets.begin(), safe_fuzz_targets.end());
results.insert(risky_fuzz_targets.begin(), risky_fuzz_targets.end());
}
return results;
}
// Fuzzing functions for different RPC endpoint functions
void fuzz_get_height(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_HEIGHT::request req;
cryptonote::COMMAND_RPC_GET_HEIGHT::response res;
rpc.on_get_height(req, res, &ctx);
}
void fuzz_get_blocks(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::request req;
cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::response res;
req.client = "fuzz";
req.requested_info = provider.ConsumeIntegralInRange<uint8_t>(0, 2);
size_t count = provider.ConsumeIntegralInRange<size_t>(0, 16);
for (size_t i = 0; i < count; ++i) {
std::string hash_data = provider.ConsumeBytesAsString(sizeof(crypto::hash));
if (hash_data.size() != sizeof(crypto::hash)) break;
crypto::hash h;
std::memcpy(&h, hash_data.data(), sizeof(h));
req.block_ids.push_back(h);
}
req.start_height = provider.ConsumeIntegral<uint64_t>();
req.pool_info_since = provider.ConsumeIntegral<uint64_t>();
req.max_block_count = provider.ConsumeIntegral<uint64_t>();
req.prune = provider.ConsumeBool();
req.no_miner_tx = provider.ConsumeBool();
rpc.on_get_blocks(req, res, &ctx);
}
void fuzz_get_blocks_by_height(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::request req;
cryptonote::COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::response res;
req.client = "fuzz";
size_t count = provider.ConsumeIntegralInRange<size_t>(0, 16);
for (size_t i = 0; i < count; ++i) {
uint64_t height = provider.ConsumeIntegral<uint64_t>();
req.heights.push_back(height);
}
rpc.on_get_blocks_by_height(req, res, &ctx);
}
void fuzz_get_hashes(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_HASHES_FAST::request req;
cryptonote::COMMAND_RPC_GET_HASHES_FAST::response res;
req.client = "fuzz";
size_t count = provider.ConsumeIntegralInRange<size_t>(0, 16);
for (size_t i = 0; i < count; ++i) {
std::string hash_data = provider.ConsumeBytesAsString(sizeof(crypto::hash));
if (hash_data.size() != sizeof(crypto::hash)) break;
crypto::hash h;
std::memcpy(&h, hash_data.data(), sizeof(h));
req.block_ids.push_back(h);
}
req.start_height = provider.ConsumeIntegral<uint64_t>();
rpc.on_get_hashes(req, res, &ctx);
}
void fuzz_get_indexes(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::request req;
cryptonote::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response res;
req.client = "fuzz";
std::string txid_data = provider.ConsumeBytesAsString(sizeof(crypto::hash));
if (txid_data.size() == sizeof(crypto::hash)) {
std::memcpy(&req.txid, txid_data.data(), sizeof(req.txid));
}
rpc.on_get_indexes(req, res, &ctx);
}
void fuzz_get_outs_bin(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_OUTPUTS_BIN::request req;
cryptonote::COMMAND_RPC_GET_OUTPUTS_BIN::response res;
req.client = "fuzz";
size_t count = provider.ConsumeIntegralInRange<size_t>(0, 16);
for (size_t i = 0; i < count; ++i) {
cryptonote::get_outputs_out out;
out.amount = provider.ConsumeIntegral<uint64_t>();
out.index = provider.ConsumeIntegral<uint64_t>();
req.outputs.push_back(out);
}
req.get_txid = provider.ConsumeBool();
rpc.on_get_outs_bin(req, res, &ctx);
}
void fuzz_get_transactions(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req;
cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res;
req.client = "fuzz";
// Cached valid transaction hashes
size_t real_count = provider.ConsumeIntegralInRange<size_t>(0, cached_tx_hashes.size());
for (size_t i = 0; i < real_count; ++i) {
const crypto::hash& h = cached_tx_hashes[i];
req.txs_hashes.emplace_back(epee::string_tools::pod_to_hex(h));
}
// Random transaction hashes (that still need 64 bytes hex
size_t junk_count = provider.ConsumeIntegralInRange<size_t>(0, 3);
for (size_t i = 0; i < junk_count; ++i) {
std::string junk;
for (size_t j = 0; j < 64; ++j) {
junk += "0123456789abcdef"[provider.ConsumeIntegralInRange<int>(0, 15)];
}
req.txs_hashes.emplace_back(std::move(junk));
}
req.decode_as_json = provider.ConsumeBool();
req.prune = provider.ConsumeBool();
req.split = provider.ConsumeBool();
rpc.on_get_transactions(req, res, &ctx);
}
void fuzz_get_alt_blocks_hashes(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_ALT_BLOCKS_HASHES::request req;
cryptonote::COMMAND_RPC_GET_ALT_BLOCKS_HASHES::response res;
req.client = "fuzz";
rpc.on_get_alt_blocks_hashes(req, res, &ctx);
}
void fuzz_is_key_image_spent(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_IS_KEY_IMAGE_SPENT::request req;
cryptonote::COMMAND_RPC_IS_KEY_IMAGE_SPENT::response res;
req.client = "fuzz";
size_t count = provider.ConsumeIntegralInRange<size_t>(0, 16);
for (size_t i = 0; i < count; ++i) {
req.key_images.push_back(provider.ConsumeRandomLengthString(64));
}
rpc.on_is_key_image_spent(req, res, &ctx);
}
void fuzz_send_raw_tx(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_SEND_RAW_TX::request req;
cryptonote::COMMAND_RPC_SEND_RAW_TX::response res;
req.client = "fuzz";
const std::string raw_tx_blob = provider.ConsumeRandomLengthString(512);
req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(raw_tx_blob);
req.do_not_relay = provider.ConsumeBool();
req.do_sanity_checks = provider.ConsumeBool();
rpc.on_send_raw_tx(req, res, &ctx);
}
void fuzz_start_mining(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_START_MINING::request req;
cryptonote::COMMAND_RPC_START_MINING::response res;
req.miner_address = provider.ConsumeRandomLengthString(128);
req.threads_count = provider.ConsumeIntegralInRange<uint64_t>(0, 256);
req.do_background_mining = provider.ConsumeBool();
req.ignore_battery = provider.ConsumeBool();
rpc.on_start_mining(req, res, &ctx);
}
void fuzz_stop_mining(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_STOP_MINING::request req;
cryptonote::COMMAND_RPC_STOP_MINING::response res;
rpc.on_stop_mining(req, res, &ctx);
}
void fuzz_mining_status(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_MINING_STATUS::request req;
cryptonote::COMMAND_RPC_MINING_STATUS::response res;
rpc.on_mining_status(req, res, &ctx);
}
void fuzz_save_bc(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_SAVE_BC::request req;
cryptonote::COMMAND_RPC_SAVE_BC::response res;
rpc.on_save_bc(req, res, &ctx);
}
void fuzz_get_peer_list(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_PEER_LIST::request req;
cryptonote::COMMAND_RPC_GET_PEER_LIST::response res;
req.public_only = provider.ConsumeBool();
req.include_blocked = provider.ConsumeBool();
rpc.on_get_peer_list(req, res, &ctx);
}
void fuzz_get_public_nodes(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_PUBLIC_NODES::request req;
cryptonote::COMMAND_RPC_GET_PUBLIC_NODES::response res;
req.gray = provider.ConsumeBool();
req.white = provider.ConsumeBool();
req.include_blocked = provider.ConsumeBool();
rpc.on_get_public_nodes(req, res, &ctx);
}
void fuzz_set_log_hash_rate(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_SET_LOG_HASH_RATE::request req;
cryptonote::COMMAND_RPC_SET_LOG_HASH_RATE::response res;
req.visible = provider.ConsumeBool();
rpc.on_set_log_hash_rate(req, res, &ctx);
}
void fuzz_set_log_level(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_SET_LOG_LEVEL::request req;
cryptonote::COMMAND_RPC_SET_LOG_LEVEL::response res;
req.level = provider.ConsumeIntegral<int8_t>();
rpc.on_set_log_level(req, res, &ctx);
}
void fuzz_set_log_categories(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_SET_LOG_CATEGORIES::request req;
cryptonote::COMMAND_RPC_SET_LOG_CATEGORIES::response res;
req.categories = provider.ConsumeRandomLengthString(32);
rpc.on_set_log_categories(req, res, &ctx);
}
void fuzz_get_transaction_pool(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL::request req;
cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL::response res;
req.client = "fuzz";
rpc.on_get_transaction_pool(req, res, &ctx);
}
void fuzz_get_transaction_pool_hashes_bin(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN::request req;
cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN::response res;
req.client = "fuzz";
rpc.on_get_transaction_pool_hashes_bin(req, res, &ctx);
}
void fuzz_get_transaction_pool_hashes(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::request req;
cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::response res;
req.client = "fuzz";
rpc.on_get_transaction_pool_hashes(req, res, &ctx);
}
void fuzz_get_transaction_pool_stats(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_STATS::request req;
cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_STATS::response res;
req.client = "fuzz";
rpc.on_get_transaction_pool_stats(req, res, &ctx);
}
void fuzz_set_bootstrap_daemon(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_SET_BOOTSTRAP_DAEMON::request req;
cryptonote::COMMAND_RPC_SET_BOOTSTRAP_DAEMON::response res;
req.address = provider.ConsumeRandomLengthString(64);
req.username = provider.ConsumeRandomLengthString(32);
req.password = provider.ConsumeRandomLengthString(32);
req.proxy = provider.ConsumeRandomLengthString(32);
rpc.on_set_bootstrap_daemon(req, res, &ctx);
// Immediate reset bootstrap daemon to avoid affecting other fuzzing with external calls
disable_bootstrap_daemon(rpc);
}
void fuzz_stop_daemon(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_STOP_DAEMON::request req;
cryptonote::COMMAND_RPC_STOP_DAEMON::response res;
rpc.on_stop_daemon(req, res, &ctx);
}
void fuzz_get_info(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_INFO::request req;
cryptonote::COMMAND_RPC_GET_INFO::response res;
req.client = "fuzz";
rpc.on_get_info(req, res, &ctx);
}
void fuzz_get_net_stats(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_NET_STATS::request req;
cryptonote::COMMAND_RPC_GET_NET_STATS::response res;
rpc.on_get_net_stats(req, res, &ctx);
}
void fuzz_get_limit(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_LIMIT::request req;
cryptonote::COMMAND_RPC_GET_LIMIT::response res;
rpc.on_get_limit(req, res, &ctx);
}
void fuzz_set_limit(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_SET_LIMIT::request req;
cryptonote::COMMAND_RPC_SET_LIMIT::response res;
req.limit_down = provider.ConsumeIntegral<int64_t>();
req.limit_up = provider.ConsumeIntegral<int64_t>();
rpc.on_set_limit(req, res, &ctx);
}
void fuzz_out_peers(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_OUT_PEERS::request req;
cryptonote::COMMAND_RPC_OUT_PEERS::response res;
req.set = provider.ConsumeBool();
req.out_peers = provider.ConsumeIntegral<uint32_t>();
rpc.on_out_peers(req, res, &ctx);
}
void fuzz_in_peers(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_IN_PEERS::request req;
cryptonote::COMMAND_RPC_IN_PEERS::response res;
req.set = provider.ConsumeBool();
req.in_peers = provider.ConsumeIntegral<uint32_t>();
rpc.on_in_peers(req, res, &ctx);
}
void fuzz_get_outs(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_OUTPUTS::request req;
cryptonote::COMMAND_RPC_GET_OUTPUTS::response res;
req.client = "fuzz";
size_t count = provider.ConsumeIntegralInRange<size_t>(0, 5);
for (size_t i = 0; i < count; ++i) {
cryptonote::get_outputs_out out;
out.amount = provider.ConsumeIntegral<uint64_t>();
out.index = provider.ConsumeIntegral<uint64_t>();
req.outputs.push_back(out);
}
req.get_txid = provider.ConsumeBool();
rpc.on_get_outs(req, res, &ctx);
}
void fuzz_update(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_UPDATE::request req;
cryptonote::COMMAND_RPC_UPDATE::response res;
req.command = provider.ConsumeRandomLengthString(16);
req.path = provider.ConsumeRandomLengthString(32);
rpc.on_update(req, res, &ctx);
}
void fuzz_get_output_distribution_bin(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request req;
cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response res;
req.client = "fuzz";
req.amounts = {0};
req.from_height = provider.ConsumeIntegral<uint64_t>();
req.to_height = req.from_height + provider.ConsumeIntegralInRange<uint64_t>(0, 1000);
req.cumulative = provider.ConsumeBool();
req.binary = provider.ConsumeBool();
req.compress = provider.ConsumeBool();
rpc.on_get_output_distribution_bin(req, res, &ctx);
}
void fuzz_pop_blocks(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_POP_BLOCKS::request req;
cryptonote::COMMAND_RPC_POP_BLOCKS::response res;
req.nblocks = provider.ConsumeIntegral<uint64_t>();
rpc.on_pop_blocks(req, res, &ctx);
}
void fuzz_getblockcount(cryptonote::core_rpc_server& rpc, FuzzedDataProvider&) {
cryptonote::COMMAND_RPC_GETBLOCKCOUNT::request req;
cryptonote::COMMAND_RPC_GETBLOCKCOUNT::response res;
rpc.on_getblockcount(req, res, &ctx);
}
// Fuzzing functions for RPC JSONAPI endpoint functions
void fuzz_getblockhash(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GETBLOCKHASH::request req;
cryptonote::COMMAND_RPC_GETBLOCKHASH::response res;
size_t count = provider.ConsumeIntegralInRange<size_t>(0, 10);
for (size_t i = 0; i < count; ++i) {
req.push_back(provider.ConsumeIntegral<uint64_t>());
}
rpc.on_getblockhash(req, res, error_resp, &ctx);
}
void fuzz_getblocktemplate(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GETBLOCKTEMPLATE::request req;
cryptonote::COMMAND_RPC_GETBLOCKTEMPLATE::response res;
if (provider.ConsumeBool()) {
req.wallet_address = "9uVsvEryzpN8WH2t1WWhFFCG5tS8cBNdmJYNRuckLENFimfauV5pZKeS1P2CbxGkSDTUPHXWwiYE5ZGSXDAGbaZgDxobqDN";
} else {
req.wallet_address = provider.ConsumeRandomLengthString(128);
}
req.reserve_size = provider.ConsumeIntegral<uint64_t>();
req.prev_block = provider.ConsumeRandomLengthString(64);
req.extra_nonce = provider.ConsumeRandomLengthString(32);
rpc.on_getblocktemplate(req, res, error_resp, &ctx);
}
void fuzz_getminerdata(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GETMINERDATA::request req;
cryptonote::COMMAND_RPC_GETMINERDATA::response res;
rpc.on_getminerdata(req, res, error_resp, &ctx);
}
void fuzz_calcpow(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_CALCPOW::request req;
cryptonote::COMMAND_RPC_CALCPOW::response res;
req.major_version = provider.ConsumeIntegral<uint8_t>();
req.height = provider.ConsumeIntegral<uint64_t>();
req.block_blob = provider.ConsumeRandomLengthString(128);
req.seed_hash = provider.ConsumeRandomLengthString(64);
rpc.on_calcpow(req, res, error_resp, &ctx);
}
void fuzz_add_aux_pow(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_ADD_AUX_POW::request req;
cryptonote::COMMAND_RPC_ADD_AUX_POW::response res;
req.blocktemplate_blob = provider.ConsumeRandomLengthString(128);
size_t count = provider.ConsumeIntegralInRange<size_t>(0, 4);
for (size_t i = 0; i < count; ++i) {
cryptonote::COMMAND_RPC_ADD_AUX_POW::aux_pow_t aux;
aux.id = provider.ConsumeRandomLengthString(32);
aux.hash = provider.ConsumeRandomLengthString(64);
req.aux_pow.push_back(aux);
}
rpc.on_add_aux_pow(req, res, error_resp, &ctx);
}
void fuzz_submitblock(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_SUBMITBLOCK::request req;
cryptonote::COMMAND_RPC_SUBMITBLOCK::response res;
std::string hash_data = provider.ConsumeBytesAsString(sizeof(crypto::hash));
if (hash_data.size() != sizeof(crypto::hash)) return;
crypto::hash h;
std::memcpy(&h, hash_data.data(), sizeof(h));
std::string hex_str = epee::string_tools::pod_to_hex(h);
req.push_back(hex_str);
rpc.on_submitblock(req, res, error_resp, &ctx);
}
void fuzz_generateblocks(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GENERATEBLOCKS::request req;
cryptonote::COMMAND_RPC_GENERATEBLOCKS::response res;
req.amount_of_blocks = provider.ConsumeIntegral<uint64_t>();
req.wallet_address = provider.ConsumeRandomLengthString(128);
req.prev_block = provider.ConsumeRandomLengthString(128);
req.starting_nonce = provider.ConsumeIntegral<uint32_t>();
rpc.on_generateblocks(req, res, error_resp, &ctx);
}
void fuzz_get_last_block_header(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_LAST_BLOCK_HEADER::request req;
cryptonote::COMMAND_RPC_GET_LAST_BLOCK_HEADER::response res;
req.client = "fuzz";
req.fill_pow_hash = provider.ConsumeBool();
rpc.on_get_last_block_header(req, res, error_resp, &ctx);
}
void fuzz_get_block_header_by_hash(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH::request req;
cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH::response res;
req.client = "fuzz";
req.hash = provider.ConsumeRandomLengthString(64);
size_t count = provider.ConsumeIntegralInRange<size_t>(0, 3);
for (size_t i = 0; i < count; ++i) {
req.hashes.push_back(provider.ConsumeRandomLengthString(64));
}
req.fill_pow_hash = provider.ConsumeBool();
rpc.on_get_block_header_by_hash(req, res, error_resp, &ctx);
}
void fuzz_get_block_header_by_height(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::request req;
cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::response res;
req.client = "fuzz";
req.height = provider.ConsumeIntegral<uint64_t>();
req.fill_pow_hash = provider.ConsumeBool();
rpc.on_get_block_header_by_height(req, res, error_resp, &ctx);
}
void fuzz_get_block_headers_range(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::request req;
cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::response res;
req.client = "fuzz";
req.start_height = provider.ConsumeIntegral<uint64_t>();
req.end_height = provider.ConsumeIntegral<uint64_t>();
req.fill_pow_hash = provider.ConsumeBool();
rpc.on_get_block_headers_range(req, res, error_resp, &ctx);
}
void fuzz_get_block(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_BLOCK::request req;
cryptonote::COMMAND_RPC_GET_BLOCK::response res;
req.client = "fuzz";
req.hash = provider.ConsumeRandomLengthString(64);
req.height = provider.ConsumeIntegral<uint64_t>();
req.fill_pow_hash = provider.ConsumeBool();
rpc.on_get_block(req, res, error_resp, &ctx);
}
void fuzz_get_connections(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_CONNECTIONS::request req;
cryptonote::COMMAND_RPC_GET_CONNECTIONS::response res;
rpc.on_get_connections(req, res, error_resp, &ctx);
}
void fuzz_get_info_json(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_INFO::request req;
cryptonote::COMMAND_RPC_GET_INFO::response res;
req.client = "fuzz";
rpc.on_get_info_json(req, res, error_resp, &ctx);
}
void fuzz_hard_fork_info(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_HARD_FORK_INFO::request req;
cryptonote::COMMAND_RPC_HARD_FORK_INFO::response res;
req.version = provider.ConsumeIntegral<uint8_t>();
rpc.on_hard_fork_info(req, res, error_resp, &ctx);
}
void fuzz_set_bans(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_SETBANS::request req;
cryptonote::COMMAND_RPC_SETBANS::response res;
int count = provider.ConsumeIntegralInRange<int>(0, 4);
for (int i = 0; i < count; ++i) {
cryptonote::COMMAND_RPC_SETBANS::ban entry;
entry.host = provider.ConsumeRandomLengthString(32);
entry.ip = provider.ConsumeIntegral<uint32_t>();
entry.ban = provider.ConsumeBool();
entry.seconds = provider.ConsumeIntegral<uint32_t>();
req.bans.push_back(entry);
}
rpc.on_set_bans(req, res, error_resp, &ctx);
}
void fuzz_get_bans(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GETBANS::request req;
cryptonote::COMMAND_RPC_GETBANS::response res;
rpc.on_get_bans(req, res, error_resp, &ctx);
}
void fuzz_banned(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_BANNED::request req;
cryptonote::COMMAND_RPC_BANNED::response res;
req.address = provider.ConsumeRandomLengthString(32);
rpc.on_banned(req, res, error_resp, &ctx);
}
void fuzz_flush_txpool(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_FLUSH_TRANSACTION_POOL::request req;
cryptonote::COMMAND_RPC_FLUSH_TRANSACTION_POOL::response res;
int count = provider.ConsumeIntegralInRange<int>(0, 8);
for (int i = 0; i < count; ++i) {
req.txids.push_back(provider.ConsumeRandomLengthString(64));
}
rpc.on_flush_txpool(req, res, error_resp, &ctx);
}
void fuzz_get_output_histogram(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request req;
cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response res;
req.client = "fuzz";
int amount_count = provider.ConsumeIntegralInRange<int>(0, 5);
for (int i = 0; i < amount_count; ++i) {
req.amounts.push_back(provider.ConsumeIntegral<uint64_t>());
}
req.min_count = provider.ConsumeIntegral<uint64_t>();
req.max_count = provider.ConsumeIntegral<uint64_t>();
req.unlocked = provider.ConsumeBool();
req.recent_cutoff = provider.ConsumeIntegral<uint64_t>();
rpc.on_get_output_histogram(req, res, error_resp, &ctx);
}
void fuzz_get_version(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_VERSION::request req;
cryptonote::COMMAND_RPC_GET_VERSION::response res;
rpc.on_get_version(req, res, error_resp, &ctx);
}
void fuzz_get_coinbase_tx_sum(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_COINBASE_TX_SUM::request req;
cryptonote::COMMAND_RPC_GET_COINBASE_TX_SUM::response res;
req.client = "fuzz";
req.height = provider.ConsumeIntegral<uint64_t>();
req.count = provider.ConsumeIntegral<uint64_t>();
rpc.on_get_coinbase_tx_sum(req, res, error_resp, &ctx);
}
void fuzz_get_base_fee_estimate(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_BASE_FEE_ESTIMATE::request req;
cryptonote::COMMAND_RPC_GET_BASE_FEE_ESTIMATE::response res;
req.client = "fuzz";
req.grace_blocks = provider.ConsumeIntegral<uint64_t>();
rpc.on_get_base_fee_estimate(req, res, error_resp, &ctx);
}
void fuzz_get_alternate_chains(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_ALTERNATE_CHAINS::request req;
cryptonote::COMMAND_RPC_GET_ALTERNATE_CHAINS::response res;
rpc.on_get_alternate_chains(req, res, error_resp, &ctx);
}
void fuzz_relay_tx(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_RELAY_TX::request req;
cryptonote::COMMAND_RPC_RELAY_TX::response res;
req.client = "fuzz";
size_t count = provider.ConsumeIntegralInRange<size_t>(0, 4);
for (size_t i = 0; i < count; ++i) {
std::string hash_data = provider.ConsumeBytesAsString(sizeof(crypto::hash));
if (hash_data.size() != sizeof(crypto::hash)) break;
crypto::hash h;
std::memcpy(&h, hash_data.data(), sizeof(h));
std::string hex_str = epee::string_tools::pod_to_hex(h);
req.txids.push_back(hex_str);
}
epee::json_rpc::error error_resp;
rpc.on_relay_tx(req, res, error_resp, &ctx);
}
void fuzz_sync_info(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_SYNC_INFO::request req;
cryptonote::COMMAND_RPC_SYNC_INFO::response res;
req.client = "fuzz";
rpc.on_sync_info(req, res, error_resp, &ctx);
}
void fuzz_get_txpool_backlog(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::request req;
cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::response res;
req.client = "fuzz";
rpc.on_get_txpool_backlog(req, res, error_resp, &ctx);
}
void fuzz_get_output_distribution(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request req;
cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response res;
req.client = "fuzz";
req.amounts = {0};
req.from_height = provider.ConsumeIntegral<uint64_t>();
req.to_height = req.from_height + provider.ConsumeIntegralInRange<uint64_t>(0, 1000);
req.cumulative = provider.ConsumeBool();
req.binary = provider.ConsumeBool();
req.compress = provider.ConsumeBool();
rpc.on_get_output_distribution(req, res, error_resp, &ctx);
}
void fuzz_prune_blockchain(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_PRUNE_BLOCKCHAIN::request req;
cryptonote::COMMAND_RPC_PRUNE_BLOCKCHAIN::response res;
req.check = provider.ConsumeBool();
rpc.on_prune_blockchain(req, res, error_resp, &ctx);
}
void fuzz_flush_cache(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_FLUSH_CACHE::request req;
cryptonote::COMMAND_RPC_FLUSH_CACHE::response res;
req.bad_blocks = provider.ConsumeBool();
rpc.on_flush_cache(req, res, error_resp, &ctx);
}
void fuzz_get_txids_loose(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_GET_TXIDS_LOOSE::request req;
cryptonote::COMMAND_RPC_GET_TXIDS_LOOSE::response res;
req.txid_template = provider.ConsumeRandomLengthString(64);
req.num_matching_bits = provider.ConsumeIntegral<uint32_t>();
rpc.on_get_txids_loose(req, res, error_resp, &ctx);
}
void fuzz_rpc_access_info(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_ACCESS_INFO::request req;
cryptonote::COMMAND_RPC_ACCESS_INFO::response res;
req.client = "fuzz";
rpc.on_rpc_access_info(req, res, error_resp, &ctx);
}
void fuzz_rpc_access_submit_nonce(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_ACCESS_SUBMIT_NONCE::request req;
cryptonote::COMMAND_RPC_ACCESS_SUBMIT_NONCE::response res;
req.client = "fuzz";
req.nonce = provider.ConsumeIntegral<uint32_t>();
req.cookie = provider.ConsumeIntegral<uint32_t>();
rpc.on_rpc_access_submit_nonce(req, res, error_resp, &ctx);
}
void fuzz_rpc_access_pay(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_ACCESS_PAY::request req;
cryptonote::COMMAND_RPC_ACCESS_PAY::response res;
req.client = "fuzz";
req.paying_for = provider.ConsumeRandomLengthString(32);
req.payment = provider.ConsumeIntegral<uint64_t>();
rpc.on_rpc_access_pay(req, res, error_resp, &ctx);
}
void fuzz_rpc_access_tracking(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_ACCESS_TRACKING::request req;
cryptonote::COMMAND_RPC_ACCESS_TRACKING::response res;
req.clear = provider.ConsumeBool();
rpc.on_rpc_access_tracking(req, res, error_resp, &ctx);
}
void fuzz_rpc_access_data(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_ACCESS_DATA::request req;
cryptonote::COMMAND_RPC_ACCESS_DATA::response res;
rpc.on_rpc_access_data(req, res, error_resp, &ctx);
}
void fuzz_rpc_access_account(cryptonote::core_rpc_server& rpc, FuzzedDataProvider& provider) {
cryptonote::COMMAND_RPC_ACCESS_ACCOUNT::request req;
cryptonote::COMMAND_RPC_ACCESS_ACCOUNT::response res;
req.client = "fuzz";
req.delta_balance = provider.ConsumeIntegral<int64_t>();
rpc.on_rpc_access_account(req, res, error_resp, &ctx);
}
// Maps storing all fuzzing functions
std::map<int, std::function<void(cryptonote::core_rpc_server&, FuzzedDataProvider&)>> priority_fuzz_targets = {
{0, fuzz_get_blocks},
{1, fuzz_get_blocks_by_height},
{2, fuzz_get_hashes},
{3, fuzz_get_outs_bin},
{4, fuzz_get_transactions},
{5, fuzz_is_key_image_spent},
{6, fuzz_send_raw_tx},
{7, fuzz_get_output_distribution_bin},
{8, fuzz_pop_blocks},
{9, fuzz_getblocktemplate},
{10, fuzz_submitblock},
{11, fuzz_generateblocks},
{12, fuzz_relay_tx},
{13, fuzz_get_output_distribution},
};
std::map<int, std::function<void(cryptonote::core_rpc_server&, FuzzedDataProvider&)>> safe_fuzz_targets = {
{14, fuzz_get_height},
{15, fuzz_get_indexes},
{16, fuzz_get_alt_blocks_hashes},
{17, fuzz_get_peer_list},
{18, fuzz_get_public_nodes},
{19, fuzz_set_log_hash_rate},
{20, fuzz_set_log_level},
{21, fuzz_set_log_categories},
{22, fuzz_get_transaction_pool},
{23, fuzz_get_transaction_pool_hashes_bin},
{24, fuzz_get_transaction_pool_hashes},
{25, fuzz_get_transaction_pool_stats},
{26, fuzz_get_info},
{27, fuzz_get_net_stats},
{28, fuzz_get_limit},
{29, fuzz_set_limit},
{30, fuzz_out_peers},
{31, fuzz_in_peers},
{32, fuzz_get_outs},
{33, fuzz_getblockcount},
{34, fuzz_getblockhash},
{35, fuzz_getminerdata},
{36, fuzz_calcpow},
{37, fuzz_get_last_block_header},
{38, fuzz_get_block_header_by_hash},
{39, fuzz_get_block_header_by_height},
{40, fuzz_get_block_headers_range},
{41, fuzz_get_block},
{42, fuzz_get_connections},
{43, fuzz_get_info_json},
{44, fuzz_hard_fork_info},
{45, fuzz_set_bans},
{46, fuzz_get_bans},
{47, fuzz_banned},
{48, fuzz_get_output_histogram},
{49, fuzz_get_version},
{50, fuzz_get_coinbase_tx_sum},
{51, fuzz_get_base_fee_estimate},
{52, fuzz_get_alternate_chains},
{53, fuzz_sync_info},
{54, fuzz_get_txpool_backlog},
};
std::map<int, std::function<void(cryptonote::core_rpc_server&, FuzzedDataProvider&)>> risky_fuzz_targets = {
{55, fuzz_start_mining},
{56, fuzz_stop_mining},
{57, fuzz_mining_status},
{58, fuzz_save_bc},
{59, fuzz_set_bootstrap_daemon},
{60, fuzz_stop_daemon},
{61, fuzz_update},
{62, fuzz_add_aux_pow},
{63, fuzz_flush_txpool},
{64, fuzz_flush_cache},
{65, fuzz_get_txids_loose},
{66, fuzz_rpc_access_info},
{67, fuzz_rpc_access_submit_nonce},
{68, fuzz_rpc_access_pay},
{69, fuzz_rpc_access_tracking},
{70, fuzz_rpc_access_data},
{71, fuzz_rpc_access_account},
// {72, fuzz_prune_blockchain},
};

View File

@@ -0,0 +1,92 @@
#pragma once
#include "common/perf_timer.h"
#include "rpc/core_rpc_server.h"
#include "fuzzer/FuzzedDataProvider.h"
#include <functional>
#include <vector>
namespace tools {
extern __thread std::vector<LoggingPerformanceTimer*> *performance_timers;
}
void disable_bootstrap_daemon(cryptonote::core_rpc_server& rpc);
std::map<int, std::function<void(cryptonote::core_rpc_server& rpc, FuzzedDataProvider&)>> get_fuzz_targets(bool safe);
void fuzz_get_height(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_blocks(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_blocks_by_height(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_hashes(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_indexes(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_outs_bin(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_transactions(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_alt_blocks_hashes(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_is_key_image_spent(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_send_raw_tx(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_start_mining(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_stop_mining(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_mining_status(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_save_bc(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_peer_list(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_public_nodes(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_set_log_hash_rate(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_set_log_level(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_set_log_categories(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_transaction_pool(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_transaction_pool_hashes_bin(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_transaction_pool_hashes(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_transaction_pool_stats(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_set_bootstrap_daemon(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_stop_daemon(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_info(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_net_stats(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_limit(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_set_limit(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_out_peers(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_in_peers(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_outs(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_update(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_output_distribution_bin(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_pop_blocks(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_getblockcount(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_getblockhash(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_getblocktemplate(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_getminerdata(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_calcpow(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_add_aux_pow(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_submitblock(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_generateblocks(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_last_block_header(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_block_header_by_hash(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_block_header_by_height(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_block_headers_range(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_block(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_connections(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_info_json(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_hard_fork_info(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_set_bans(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_bans(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_banned(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_flush_txpool(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_output_histogram(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_version(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_coinbase_tx_sum(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_base_fee_estimate(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_alternate_chains(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_relay_tx(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_sync_info(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_txpool_backlog(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_output_distribution(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_prune_blockchain(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_flush_cache(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_txids_loose(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_rpc_access_info(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_rpc_access_submit_nonce(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_rpc_access_pay(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_rpc_access_tracking(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_rpc_access_data(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_rpc_access_account(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
void fuzz_get_height(cryptonote::core_rpc_server&, FuzzedDataProvider& provider);
extern std::map<int, std::function<void(cryptonote::core_rpc_server&, FuzzedDataProvider&)>> priority_fuzz_targets;
extern std::map<int, std::function<void(cryptonote::core_rpc_server&, FuzzedDataProvider&)>> safe_fuzz_targets;
extern std::map<int, std::function<void(cryptonote::core_rpc_server&, FuzzedDataProvider&)>> risky_fuzz_targets;

View File

@@ -0,0 +1,87 @@
#include "cryptonote_basic/events.h"
#include "cryptonote_core/tx_pool.h"
#include "rpc/zmq_pub.h"
#include "rpc_endpoints.h"
#include <cstring>
#include <map>
using namespace cryptonote;
using namespace cryptonote::listener;
void fuzz_sub_request(zmq_pub& pub, FuzzedDataProvider& provider) {
std::string sub = provider.ConsumeRandomLengthString(64);
pub.sub_request(boost::string_ref(sub));
}
void fuzz_send_chain_main(zmq_pub& pub, FuzzedDataProvider& provider) {
uint64_t height = provider.ConsumeIntegral<uint64_t>();
size_t blk_count = provider.ConsumeIntegralInRange<size_t>(0, 4);
std::vector<block> blocks;
for (size_t i = 0; i < blk_count; ++i) {
block b{};
b.major_version = provider.ConsumeIntegral<uint8_t>();
b.minor_version = provider.ConsumeIntegral<uint8_t>();
b.timestamp = provider.ConsumeIntegral<uint64_t>();
b.prev_id = crypto::null_hash;
b.nonce = provider.ConsumeIntegral<uint32_t>();
blocks.push_back(b);
}
pub.send_chain_main(height, epee::span<const block>(blocks.data(), blocks.size()));
}
void fuzz_send_miner_data(zmq_pub& pub, FuzzedDataProvider& provider) {
uint8_t major = provider.ConsumeIntegral<uint8_t>();
uint64_t h = provider.ConsumeIntegral<uint64_t>();
crypto::hash prev_id, seed_hash;
std::memset(&prev_id, 0x01, sizeof(prev_id));
std::memset(&seed_hash, 0x02, sizeof(seed_hash));
difficulty_type diff = provider.ConsumeIntegral<uint64_t>();
uint64_t median_weight = provider.ConsumeIntegral<uint64_t>();
uint64_t coins = provider.ConsumeIntegral<uint64_t>();
std::vector<tx_block_template_backlog_entry> backlog;
size_t count = provider.ConsumeIntegralInRange<size_t>(0, 3);
for (size_t i = 0; i < count; ++i) {
tx_block_template_backlog_entry entry;
entry.weight = provider.ConsumeIntegral<uint64_t>();
entry.fee = provider.ConsumeIntegral<uint64_t>();
backlog.push_back(entry);
}
pub.send_miner_data(major, h, prev_id, seed_hash, diff, median_weight, coins, backlog);
}
void fuzz_send_txpool_add(zmq_pub& pub, FuzzedDataProvider& provider) {
size_t count = provider.ConsumeIntegralInRange<size_t>(0, 3);
std::vector<txpool_event> events;
for (size_t i = 0; i < count; ++i) {
txpool_event evt{};
evt.res = provider.ConsumeBool();
auto blob = provider.ConsumeRandomLengthString(128);
if (!parse_and_validate_tx_from_blob(blob, evt.tx)) {
continue;
}
evt.hash = get_transaction_hash(evt.tx);
evt.blob_size = blob.size();
evt.weight = provider.ConsumeIntegral<uint64_t>();
events.push_back(std::move(evt));
}
pub.send_txpool_add(std::move(events));
}
// Map all zmq targets
std::map<int, std::function<void(zmq_pub&, FuzzedDataProvider&)>> zmq_targets = {
{0, fuzz_sub_request},
{1, fuzz_send_chain_main},
{2, fuzz_send_miner_data},
{3, fuzz_send_txpool_add},
};

View File

@@ -0,0 +1,19 @@
#include "rpc/zmq_pub.h"
#include "cryptonote_basic/cryptonote_basic.h"
#include "crypto/crypto.h"
#include "crypto/hash.h"
#include "span.h"
#include <fuzzer/FuzzedDataProvider.h>
#include <zmq.h>
#include <functional>
#include <vector>
using namespace cryptonote;
using namespace cryptonote::listener;
void fuzz_sub_request(zmq_pub&, FuzzedDataProvider&);
void fuzz_send_chain_main(zmq_pub&, FuzzedDataProvider&);
void fuzz_send_miner_data(zmq_pub&, FuzzedDataProvider&);
void fuzz_send_txpool_add(zmq_pub&, FuzzedDataProvider&);
extern std::map<int, std::function<void(zmq_pub&, FuzzedDataProvider&)>> zmq_targets;

View File

@@ -0,0 +1,397 @@
//===- FuzzedDataProvider.h - Utility header for fuzz targets ---*- C++ -* ===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// A single header library providing an utility class to break up an array of
// bytes. Whenever run on the same input, provides the same output, as long as
// its methods are called in the same order, with the same arguments.
//===----------------------------------------------------------------------===//
#ifndef LLVM_FUZZER_FUZZED_DATA_PROVIDER_H_
#define LLVM_FUZZER_FUZZED_DATA_PROVIDER_H_
#include <algorithm>
#include <array>
#include <climits>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <initializer_list>
#include <limits>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
// In addition to the comments below, the API is also briefly documented at
// https://github.com/google/fuzzing/blob/master/docs/split-inputs.md#fuzzed-data-provider
class FuzzedDataProvider {
public:
// |data| is an array of length |size| that the FuzzedDataProvider wraps to
// provide more granular access. |data| must outlive the FuzzedDataProvider.
FuzzedDataProvider(const uint8_t *data, size_t size)
: data_ptr_(data), remaining_bytes_(size) {}
~FuzzedDataProvider() = default;
// See the implementation below (after the class definition) for more verbose
// comments for each of the methods.
// Methods returning std::vector of bytes. These are the most popular choice
// when splitting fuzzing input into pieces, as every piece is put into a
// separate buffer (i.e. ASan would catch any under-/overflow) and the memory
// will be released automatically.
template <typename T> std::vector<T> ConsumeBytes(size_t num_bytes);
template <typename T>
std::vector<T> ConsumeBytesWithTerminator(size_t num_bytes, T terminator = 0);
template <typename T> std::vector<T> ConsumeRemainingBytes();
// Methods returning strings. Use only when you need a std::string or a null
// terminated C-string. Otherwise, prefer the methods returning std::vector.
std::string ConsumeBytesAsString(size_t num_bytes);
std::string ConsumeRandomLengthString(size_t max_length);
std::string ConsumeRandomLengthString();
std::string ConsumeRemainingBytesAsString();
// Methods returning integer values.
template <typename T> T ConsumeIntegral();
template <typename T> T ConsumeIntegralInRange(T min, T max);
// Methods returning floating point values.
template <typename T> T ConsumeFloatingPoint();
template <typename T> T ConsumeFloatingPointInRange(T min, T max);
// 0 <= return value <= 1.
template <typename T> T ConsumeProbability();
bool ConsumeBool();
// Returns a value chosen from the given enum.
template <typename T> T ConsumeEnum();
// Returns a value from the given array.
template <typename T, size_t size> T PickValueInArray(const T (&array)[size]);
template <typename T, size_t size>
T PickValueInArray(const std::array<T, size> &array);
template <typename T> T PickValueInArray(std::initializer_list<const T> list);
// Writes data to the given destination and returns number of bytes written.
size_t ConsumeData(void *destination, size_t num_bytes);
// Reports the remaining bytes available for fuzzed input.
size_t remaining_bytes() { return remaining_bytes_; }
private:
FuzzedDataProvider(const FuzzedDataProvider &) = delete;
FuzzedDataProvider &operator=(const FuzzedDataProvider &) = delete;
void CopyAndAdvance(void *destination, size_t num_bytes);
void Advance(size_t num_bytes);
template <typename T>
std::vector<T> ConsumeBytes(size_t size, size_t num_bytes);
template <typename TS, typename TU> TS ConvertUnsignedToSigned(TU value);
const uint8_t *data_ptr_;
size_t remaining_bytes_;
};
// Returns a std::vector containing |num_bytes| of input data. If fewer than
// |num_bytes| of data remain, returns a shorter std::vector containing all
// of the data that's left. Can be used with any byte sized type, such as
// char, unsigned char, uint8_t, etc.
template <typename T>
std::vector<T> FuzzedDataProvider::ConsumeBytes(size_t num_bytes) {
num_bytes = std::min(num_bytes, remaining_bytes_);
return ConsumeBytes<T>(num_bytes, num_bytes);
}
// Similar to |ConsumeBytes|, but also appends the terminator value at the end
// of the resulting vector. Useful, when a mutable null-terminated C-string is
// needed, for example. But that is a rare case. Better avoid it, if possible,
// and prefer using |ConsumeBytes| or |ConsumeBytesAsString| methods.
template <typename T>
std::vector<T> FuzzedDataProvider::ConsumeBytesWithTerminator(size_t num_bytes,
T terminator) {
num_bytes = std::min(num_bytes, remaining_bytes_);
std::vector<T> result = ConsumeBytes<T>(num_bytes + 1, num_bytes);
result.back() = terminator;
return result;
}
// Returns a std::vector containing all remaining bytes of the input data.
template <typename T>
std::vector<T> FuzzedDataProvider::ConsumeRemainingBytes() {
return ConsumeBytes<T>(remaining_bytes_);
}
// Returns a std::string containing |num_bytes| of input data. Using this and
// |.c_str()| on the resulting string is the best way to get an immutable
// null-terminated C string. If fewer than |num_bytes| of data remain, returns
// a shorter std::string containing all of the data that's left.
inline std::string FuzzedDataProvider::ConsumeBytesAsString(size_t num_bytes) {
static_assert(sizeof(std::string::value_type) == sizeof(uint8_t),
"ConsumeBytesAsString cannot convert the data to a string.");
num_bytes = std::min(num_bytes, remaining_bytes_);
std::string result(
reinterpret_cast<const std::string::value_type *>(data_ptr_), num_bytes);
Advance(num_bytes);
return result;
}
// Returns a std::string of length from 0 to |max_length|. When it runs out of
// input data, returns what remains of the input. Designed to be more stable
// with respect to a fuzzer inserting characters than just picking a random
// length and then consuming that many bytes with |ConsumeBytes|.
inline std::string
FuzzedDataProvider::ConsumeRandomLengthString(size_t max_length) {
// Reads bytes from the start of |data_ptr_|. Maps "\\" to "\", and maps "\"
// followed by anything else to the end of the string. As a result of this
// logic, a fuzzer can insert characters into the string, and the string
// will be lengthened to include those new characters, resulting in a more
// stable fuzzer than picking the length of a string independently from
// picking its contents.
std::string result;
// Reserve the anticipated capacity to prevent several reallocations.
result.reserve(std::min(max_length, remaining_bytes_));
for (size_t i = 0; i < max_length && remaining_bytes_ != 0; ++i) {
char next = ConvertUnsignedToSigned<char>(data_ptr_[0]);
Advance(1);
if (next == '\\' && remaining_bytes_ != 0) {
next = ConvertUnsignedToSigned<char>(data_ptr_[0]);
Advance(1);
if (next != '\\')
break;
}
result += next;
}
result.shrink_to_fit();
return result;
}
// Returns a std::string of length from 0 to |remaining_bytes_|.
inline std::string FuzzedDataProvider::ConsumeRandomLengthString() {
return ConsumeRandomLengthString(remaining_bytes_);
}
// Returns a std::string containing all remaining bytes of the input data.
// Prefer using |ConsumeRemainingBytes| unless you actually need a std::string
// object.
inline std::string FuzzedDataProvider::ConsumeRemainingBytesAsString() {
return ConsumeBytesAsString(remaining_bytes_);
}
// Returns a number in the range [Type's min, Type's max]. The value might
// not be uniformly distributed in the given range. If there's no input data
// left, always returns |min|.
template <typename T> T FuzzedDataProvider::ConsumeIntegral() {
return ConsumeIntegralInRange(std::numeric_limits<T>::min(),
std::numeric_limits<T>::max());
}
// Returns a number in the range [min, max] by consuming bytes from the
// input data. The value might not be uniformly distributed in the given
// range. If there's no input data left, always returns |min|. |min| must
// be less than or equal to |max|.
template <typename T>
T FuzzedDataProvider::ConsumeIntegralInRange(T min, T max) {
static_assert(std::is_integral_v<T>, "An integral type is required.");
static_assert(sizeof(T) <= sizeof(uint64_t), "Unsupported integral type.");
if (min > max)
abort();
// Use the biggest type possible to hold the range and the result.
uint64_t range = static_cast<uint64_t>(max) - static_cast<uint64_t>(min);
uint64_t result = 0;
size_t offset = 0;
while (offset < sizeof(T) * CHAR_BIT && (range >> offset) > 0 &&
remaining_bytes_ != 0) {
// Pull bytes off the end of the seed data. Experimentally, this seems to
// allow the fuzzer to more easily explore the input space. This makes
// sense, since it works by modifying inputs that caused new code to run,
// and this data is often used to encode length of data read by
// |ConsumeBytes|. Separating out read lengths makes it easier modify the
// contents of the data that is actually read.
--remaining_bytes_;
result = (result << CHAR_BIT) | data_ptr_[remaining_bytes_];
offset += CHAR_BIT;
}
// Avoid division by 0, in case |range + 1| results in overflow.
if (range != std::numeric_limits<decltype(range)>::max())
result = result % (range + 1);
return static_cast<T>(static_cast<uint64_t>(min) + result);
}
// Returns a floating point value in the range [Type's lowest, Type's max] by
// consuming bytes from the input data. If there's no input data left, always
// returns approximately 0.
template <typename T> T FuzzedDataProvider::ConsumeFloatingPoint() {
return ConsumeFloatingPointInRange<T>(std::numeric_limits<T>::lowest(),
std::numeric_limits<T>::max());
}
// Returns a floating point value in the given range by consuming bytes from
// the input data. If there's no input data left, returns |min|. Note that
// |min| must be less than or equal to |max|.
template <typename T>
T FuzzedDataProvider::ConsumeFloatingPointInRange(T min, T max) {
if (min > max)
abort();
T range = .0;
T result = min;
constexpr T zero(.0);
if (max > zero && min < zero && max > min + std::numeric_limits<T>::max()) {
// The diff |max - min| would overflow the given floating point type. Use
// the half of the diff as the range and consume a bool to decide whether
// the result is in the first of the second part of the diff.
range = (max / 2.0) - (min / 2.0);
if (ConsumeBool()) {
result += range;
}
} else {
range = max - min;
}
return result + range * ConsumeProbability<T>();
}
// Returns a floating point number in the range [0.0, 1.0]. If there's no
// input data left, always returns 0.
template <typename T> T FuzzedDataProvider::ConsumeProbability() {
static_assert(std::is_floating_point_v<T>,
"A floating point type is required.");
// Use different integral types for different floating point types in order
// to provide better density of the resulting values.
using IntegralType =
typename std::conditional_t<(sizeof(T) <= sizeof(uint32_t)), uint32_t,
uint64_t>;
T result = static_cast<T>(ConsumeIntegral<IntegralType>());
result /= static_cast<T>(std::numeric_limits<IntegralType>::max());
return result;
}
// Reads one byte and returns a bool, or false when no data remains.
inline bool FuzzedDataProvider::ConsumeBool() {
return 1 & ConsumeIntegral<uint8_t>();
}
// Returns an enum value. The enum must start at 0 and be contiguous. It must
// also contain |kMaxValue| aliased to its largest (inclusive) value. Such as:
// enum class Foo { SomeValue, OtherValue, kMaxValue = OtherValue };
template <typename T> T FuzzedDataProvider::ConsumeEnum() {
static_assert(std::is_enum_v<T>, "|T| must be an enum type.");
return static_cast<T>(
ConsumeIntegralInRange<uint32_t>(0, static_cast<uint32_t>(T::kMaxValue)));
}
// Returns a copy of the value selected from the given fixed-size |array|.
template <typename T, size_t size>
T FuzzedDataProvider::PickValueInArray(const T (&array)[size]) {
static_assert(size > 0, "The array must be non empty.");
return array[ConsumeIntegralInRange<size_t>(0, size - 1)];
}
template <typename T, size_t size>
T FuzzedDataProvider::PickValueInArray(const std::array<T, size> &array) {
static_assert(size > 0, "The array must be non empty.");
return array[ConsumeIntegralInRange<size_t>(0, size - 1)];
}
template <typename T>
T FuzzedDataProvider::PickValueInArray(std::initializer_list<const T> list) {
if (!list.size())
abort();
return *(list.begin() + ConsumeIntegralInRange<size_t>(0, list.size() - 1));
}
// Writes |num_bytes| of input data to the given destination pointer. If there
// is not enough data left, writes all remaining bytes. Return value is the
// number of bytes written.
// In general, it's better to avoid using this function, but it may be useful
// in cases when it's necessary to fill a certain buffer or object with
// fuzzing data.
inline size_t FuzzedDataProvider::ConsumeData(void *destination,
size_t num_bytes) {
num_bytes = std::min(num_bytes, remaining_bytes_);
CopyAndAdvance(destination, num_bytes);
return num_bytes;
}
// Private methods.
inline void FuzzedDataProvider::CopyAndAdvance(void *destination,
size_t num_bytes) {
std::memcpy(destination, data_ptr_, num_bytes);
Advance(num_bytes);
}
inline void FuzzedDataProvider::Advance(size_t num_bytes) {
if (num_bytes > remaining_bytes_)
abort();
data_ptr_ += num_bytes;
remaining_bytes_ -= num_bytes;
}
template <typename T>
std::vector<T> FuzzedDataProvider::ConsumeBytes(size_t size, size_t num_bytes) {
static_assert(sizeof(T) == sizeof(uint8_t), "Incompatible data type.");
// The point of using the size-based constructor below is to increase the
// odds of having a vector object with capacity being equal to the length.
// That part is always implementation specific, but at least both libc++ and
// libstdc++ allocate the requested number of bytes in that constructor,
// which seems to be a natural choice for other implementations as well.
// To increase the odds even more, we also call |shrink_to_fit| below.
std::vector<T> result(size);
if (size == 0) {
if (num_bytes != 0)
abort();
return result;
}
CopyAndAdvance(result.data(), num_bytes);
// Even though |shrink_to_fit| is also implementation specific, we expect it
// to provide an additional assurance in case vector's constructor allocated
// a buffer which is larger than the actual amount of data we put inside it.
result.shrink_to_fit();
return result;
}
template <typename TS, typename TU>
TS FuzzedDataProvider::ConvertUnsignedToSigned(TU value) {
static_assert(sizeof(TS) == sizeof(TU), "Incompatible data types.");
static_assert(!std::numeric_limits<TU>::is_signed,
"Source type must be unsigned.");
// TODO(Dor1s): change to `if constexpr` once C++17 becomes mainstream.
if (std::numeric_limits<TS>::is_modulo)
return static_cast<TS>(value);
// Avoid using implementation-defined unsigned to signed conversions.
// To learn more, see https://stackoverflow.com/questions/13150449.
if (value <= std::numeric_limits<TS>::max()) {
return static_cast<TS>(value);
} else {
constexpr auto TS_min = std::numeric_limits<TS>::min();
return TS_min + static_cast<TS>(value - TS_min);
}
}
#endif // LLVM_FUZZER_FUZZED_DATA_PROVIDER_H_