Skip to content

Commit 8d4c625

Browse files
committed
Merge pull request #10004
36bdfad rpc-fuzz: Add new fuzzers for RPC endpoints (Arthur Chan)
2 parents d885347 + 36bdfad commit 8d4c625

File tree

13 files changed

+2183
-3
lines changed

13 files changed

+2183
-3
lines changed

CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -484,8 +484,8 @@ endif()
484484
option(SANITIZE "Use ASAN memory sanitizer" OFF)
485485
if(SANITIZE)
486486
message(STATUS "Using ASAN")
487-
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address")
488-
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
487+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address,undefined")
488+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined")
489489
endif()
490490

491491
# Set default blockchain storage location:

src/common/perf_timer.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,12 @@ namespace tools
9090

9191
el::Level performance_timer_log_level = el::Level::Info;
9292

93-
static __thread std::vector<LoggingPerformanceTimer*> *performance_timers = NULL;
93+
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
94+
#warning "Building with fuzzing mode UNSAFE FOR PRODUCTION!"
95+
__thread std::vector<LoggingPerformanceTimer*> *performance_timers = NULL;
96+
#else
97+
static __thread std::vector<LoggingPerformanceTimer*> *performance_timers = NULL;
98+
#endif
9499

95100
void set_performance_timer_log_level(el::Level level)
96101
{

tests/fuzz/CMakeLists.txt

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,75 @@
2626
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
2727
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2828

29+
# Add the include path for <fuzzer/FuzzedDataProvider.h>
30+
include_directories(${CMAKE_SOURCE_DIR}/tests/fuzz/include)
31+
32+
# Recompile perf_timer for fuzzing
33+
add_library(fuzz_unsafe_macro OBJECT
34+
${CMAKE_SOURCE_DIR}/src/common/perf_timer.cpp)
35+
target_compile_definitions(fuzz_unsafe_macro
36+
PRIVATE FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
37+
38+
monero_add_minimal_executable(fuzz_rpc
39+
fuzz_rpc/initialisation.cpp
40+
fuzz_rpc/rpc_endpoints.cpp
41+
fuzz_rpc/fuzz_rpc.cpp
42+
$<TARGET_OBJECTS:fuzz_unsafe_macro>)
43+
target_compile_definitions(fuzz_rpc PRIVATE SAFE)
44+
target_link_libraries(fuzz_rpc
45+
PRIVATE
46+
rpc
47+
${CMAKE_THREAD_LIBS_INIT}
48+
${EXTRA_LIBRARIES}
49+
$ENV{LIB_FUZZING_ENGINE})
50+
set_property(TARGET fuzz_rpc
51+
PROPERTY
52+
FOLDER "tests")
53+
54+
monero_add_minimal_executable(fuzz_rpc_full
55+
fuzz_rpc/initialisation.cpp
56+
fuzz_rpc/rpc_endpoints.cpp
57+
fuzz_rpc/fuzz_rpc.cpp
58+
$<TARGET_OBJECTS:fuzz_unsafe_macro>)
59+
target_link_libraries(fuzz_rpc_full
60+
PRIVATE
61+
rpc
62+
${CMAKE_THREAD_LIBS_INIT}
63+
${EXTRA_LIBRARIES}
64+
$ENV{LIB_FUZZING_ENGINE})
65+
set_property(TARGET fuzz_rpc_full
66+
PROPERTY
67+
FOLDER "tests")
68+
69+
monero_add_minimal_executable(fuzz_rpc_full_no_exceptions
70+
fuzz_rpc/initialisation.cpp
71+
fuzz_rpc/rpc_endpoints.cpp
72+
fuzz_rpc/fuzz_rpc.cpp
73+
$<TARGET_OBJECTS:fuzz_unsafe_macro>)
74+
target_compile_definitions(fuzz_rpc_full_no_exceptions PRIVATE CATCH_ALL_EXCEPTIONS)
75+
target_link_libraries(fuzz_rpc_full_no_exceptions
76+
PRIVATE
77+
rpc
78+
${CMAKE_THREAD_LIBS_INIT}
79+
${EXTRA_LIBRARIES}
80+
$ENV{LIB_FUZZING_ENGINE})
81+
set_property(TARGET fuzz_rpc_full_no_exceptions
82+
PROPERTY
83+
FOLDER "tests")
84+
85+
monero_add_minimal_executable(fuzz_zmq
86+
fuzz_rpc/zmq_endpoints.cpp
87+
fuzz_rpc/fuzz_zmq.cpp)
88+
target_link_libraries(fuzz_zmq
89+
PRIVATE
90+
rpc_pub
91+
${CMAKE_THREAD_LIBS_INIT}
92+
${EXTRA_LIBRARIES}
93+
$ENV{LIB_FUZZING_ENGINE})
94+
set_property(TARGET fuzz_zmq
95+
PROPERTY
96+
FOLDER "tests")
97+
2998
monero_add_minimal_executable(block_fuzz_tests block.cpp fuzzer.cpp)
3099
target_link_libraries(block_fuzz_tests
31100
PRIVATE

tests/fuzz/fuzz_rpc/README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Fuzzing Harness Explanation
2+
3+
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.
4+
5+
6+
## `fuzz_rpc.cpp` / `fuzz_zmq.cpp`
7+
8+
These are the main fuzzing entry points, containing `LLVMFuzzerTestOneInput`, which manages each iteration of the fuzzing process.
9+
10+
## `initialisation.cpp` / `initialisation.h`
11+
12+
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.
13+
14+
## `rpc_endpoints.cpp` / `rpc_endpoints.h` / `zmq_endpoints.cpp` / `zmq_endpoints.h`
15+
16+
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:
17+
18+
* **Safe**: Considered stable and unlikely to fail.
19+
* **Risky**: More prone to failure and early exits, especially if no valid blockchain is generated or no payment modules are configured.
20+
* **Priority**: Most critical RPC endpoint functions that will at least run once per iteration.
21+
22+
# Building the Fuzzing Harnesses
23+
24+
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.
25+
26+
* With `SAFE` defined: Only safe endpoint functions are fuzzed.
27+
* Without `SAFE`: Both safe and risky functions are included in the fuzzing process, and payment modules are configured.
28+
29+
# Fuzzing Harness Flow
30+
31+
## Select a Random RPC Endpoint Function / ZMQ Endpoint Function
32+
33+
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.
34+
35+
36+
## `initialise_rpc_core` / `initialise_rpc_server`
37+
38+
At the start of each fuzzing iteration, a `core_rpc_server` object is created and initialised with dummy protocols, P2P, payment modules, and more.
39+
If the `SAFE` macro is not defined, the payment module will also be initialised.
40+
This step is skipped for `fuzz_zmq`.
41+
42+
## `generate_random_blocks`
43+
44+
The provided random data is used to initialise a set of random blocks, which are added to a dummy blockchain (initialised using `FAKECHAIN`).
45+
If blocks are successfully added, a list of random transactions is then generated and also pushed to the blockchain.
46+
This step is skipped for `fuzz_zmq`.
47+
48+
## Real Fuzzing
49+
50+
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.

tests/fuzz/fuzz_rpc/fuzz_rpc.cpp

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#include "initialisation.h"
2+
#include "rpc_endpoints.h"
3+
#include <fuzzer/FuzzedDataProvider.h>
4+
5+
#include <csignal>
6+
#include <csetjmp>
7+
#include <iostream>
8+
#include <vector>
9+
#include <algorithm>
10+
11+
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
12+
// In general an iteration needs a fair amount of data so ensure we have enough
13+
// to work with, otherwise return 0 to skip this iteration.
14+
if (size < 512) {
15+
return 0;
16+
}
17+
18+
// Determine if this iteration should run in safe mode
19+
#ifdef SAFE
20+
constexpr bool is_safe_mode = true;
21+
#else
22+
constexpr bool is_safe_mode = false;
23+
#endif
24+
25+
// Retrieve a list of all fuzz targets
26+
auto fuzz_targets = get_fuzz_targets(is_safe_mode);
27+
28+
// Disable fatal exits for logging.
29+
el::Loggers::addFlag(el::LoggingFlag::DisableApplicationAbortOnFatalLog);
30+
31+
// Prepare base FuzzedDataProvider
32+
FuzzedDataProvider provider(data, size);
33+
34+
// Randomly choose multiple fuzz_targets to fuzz
35+
int rpc_messages_to_send = provider.ConsumeIntegralInRange<int>(1, 16);
36+
std::vector<int> selectors;
37+
if (is_safe_mode) {
38+
selectors.reserve(rpc_messages_to_send);
39+
} else {
40+
selectors.reserve(rpc_messages_to_send + priority_fuzz_targets.size());
41+
for (int i = 0; i < priority_fuzz_targets.size(); ++i) {
42+
selectors.push_back(i);
43+
}
44+
45+
// Randomly shuffle the selectors for priority fuzz targets
46+
for (int i = 0; i < priority_fuzz_targets.size(); i++) {
47+
int target = provider.ConsumeIntegralInRange<int>(0, priority_fuzz_targets.size() - 1);
48+
std::swap(selectors[i], selectors[target]);
49+
}
50+
}
51+
52+
// Randomly select rpc functions to call
53+
for (int i = 0; i < rpc_messages_to_send && provider.remaining_bytes() >= 2; ++i) {
54+
int selector = provider.ConsumeIntegralInRange<int>(0, fuzz_targets.size() - 1);
55+
selectors.push_back(selector);
56+
}
57+
58+
// Initialise core and core_rpc_server
59+
auto core_env = initialise_rpc_core();
60+
auto& dummy_core = core_env->core;
61+
auto rpc_handler = initialise_rpc_server(*dummy_core, provider, !is_safe_mode);
62+
63+
// Generate random blocks/miners/transactions and push to the core blockchains
64+
if (!generate_random_blocks(*dummy_core, provider)) {
65+
// No randomised blocks have been successfully added, skipping this iteration
66+
dummy_core->get_blockchain_storage().get_db().batch_stop();
67+
return 0;
68+
}
69+
70+
// Disable bootstrap daemon
71+
disable_bootstrap_daemon(*rpc_handler->rpc);
72+
73+
for (int selector : selectors) {
74+
try {
75+
// Fuzz the target function
76+
fuzz_targets[selector](*rpc_handler->rpc, provider);
77+
} catch (const std::runtime_error&) {
78+
// Known runtime_error thrown from monero
79+
} catch (const cryptonote::DB_ERROR& e) {
80+
// Known error thrown from monero on internal blockchain DB check
81+
// when fuzzing with random values
82+
#ifdef CATCH_ALL_EXCEPTIONS
83+
} catch (...) {
84+
// Silent all exceptions
85+
#endif
86+
}
87+
}
88+
89+
dummy_core->get_blockchain_storage().get_db().batch_stop();
90+
return 0;
91+
}

tests/fuzz/fuzz_rpc/fuzz_zmq.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#include <fuzzer/FuzzedDataProvider.h>
2+
#include "zmq_endpoints.h"
3+
#include <vector>
4+
#include <cstring>
5+
6+
using namespace cryptonote;
7+
using namespace cryptonote::listener;
8+
9+
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
10+
if (size < 64) {
11+
return 0;
12+
}
13+
14+
FuzzedDataProvider provider(data, size);
15+
16+
void* ctx = zmq_ctx_new();
17+
if (!ctx) {
18+
return 0;
19+
}
20+
21+
// Randomly choose multiple zmq_targets to fuzz
22+
int to_sent = provider.ConsumeIntegralInRange<int>(1, 8);
23+
std::vector<int> selectors;
24+
selectors.reserve(to_sent);
25+
for (int i = 0; i < to_sent && provider.remaining_bytes() >= 2; ++i) {
26+
uint16_t raw = provider.ConsumeIntegral<uint16_t>();
27+
selectors.push_back(raw % zmq_targets.size());
28+
}
29+
30+
try {
31+
zmq_pub pub(ctx);
32+
for (int selector : selectors) {
33+
zmq_targets[selector](pub, provider);
34+
}
35+
} catch (const std::runtime_error& e) {
36+
// Ignore known runtime_error from checking
37+
}
38+
39+
zmq_ctx_shutdown(ctx);
40+
zmq_ctx_term(ctx);
41+
42+
return 0;
43+
}

0 commit comments

Comments
 (0)