From 4626c2e230e2094004b0ff6d3d9a101fb5182c90 Mon Sep 17 00:00:00 2001 From: iphydf Date: Wed, 5 Feb 2025 11:57:32 +0000 Subject: [PATCH] test: Add a Net_Crypto fuzz test. --- testing/fuzzing/CMakeLists.txt | 1 + testing/fuzzing/bootstrap_fuzz_test.cc | 6 ++ testing/fuzzing/e2e_fuzz_test.cc | 1 - testing/fuzzing/fuzz_support.cc | 39 +++++++++-- testing/fuzzing/fuzz_support.hh | 38 ++++++++++- testing/fuzzing/protodump.cc | 32 +++++---- testing/fuzzing/rebuild_protodump | 6 +- toxcore/BUILD.bazel | 17 +++++ toxcore/net_crypto.h | 8 +++ toxcore/net_crypto_fuzz_test.cc | 93 ++++++++++++++++++++++++++ toxcore/onion_client.c | 1 + 11 files changed, 214 insertions(+), 28 deletions(-) create mode 100644 toxcore/net_crypto_fuzz_test.cc diff --git a/testing/fuzzing/CMakeLists.txt b/testing/fuzzing/CMakeLists.txt index 3ab9ecd90e..e306112ff2 100644 --- a/testing/fuzzing/CMakeLists.txt +++ b/testing/fuzzing/CMakeLists.txt @@ -26,4 +26,5 @@ fuzz_test(DHT ../../toxcore) fuzz_test(forwarding ../../toxcore) fuzz_test(group_announce ../../toxcore) fuzz_test(group_moderation ../../toxcore) +fuzz_test(net_crypto ../../toxcore) fuzz_test(tox_events ../../toxcore) diff --git a/testing/fuzzing/bootstrap_fuzz_test.cc b/testing/fuzzing/bootstrap_fuzz_test.cc index d127f0f1b9..386da1c787 100644 --- a/testing/fuzzing/bootstrap_fuzz_test.cc +++ b/testing/fuzzing/bootstrap_fuzz_test.cc @@ -161,6 +161,7 @@ void TestBootstrap(Fuzz_Data &input) assert(dispatch != nullptr); setup_callbacks(dispatch); + size_t input_size = input.size(); while (!input.empty()) { Tox_Err_Events_Iterate error_iterate; Tox_Events *events = tox_events_iterate(tox, true, &error_iterate); @@ -170,6 +171,11 @@ void TestBootstrap(Fuzz_Data &input) // Move the clock forward a decent amount so all the time-based checks // trigger more quickly. sys.clock += 200; + + // If no input was consumed, something went wrong. + assert(input_size != input.size()); + + input_size = input.size(); } tox_dispatch_free(dispatch); diff --git a/testing/fuzzing/e2e_fuzz_test.cc b/testing/fuzzing/e2e_fuzz_test.cc index 5a6c55b198..45af343b70 100644 --- a/testing/fuzzing/e2e_fuzz_test.cc +++ b/testing/fuzzing/e2e_fuzz_test.cc @@ -5,7 +5,6 @@ #include #include -#include #include #include "../../toxcore/crypto_core.h" diff --git a/testing/fuzzing/fuzz_support.cc b/testing/fuzzing/fuzz_support.cc index a031190e7d..904d542494 100644 --- a/testing/fuzzing/fuzz_support.cc +++ b/testing/fuzzing/fuzz_support.cc @@ -175,12 +175,39 @@ static constexpr Network_Funcs fuzz_network_funcs = { static constexpr Random_Funcs fuzz_random_funcs = { /* .random_bytes = */ ![](Fuzz_System *self, uint8_t *bytes, size_t length) { - // Amount of data is limited - const size_t bytes_read = std::min(length, self->data.size()); - // Initialize everything to make MSAN and others happy - std::memset(bytes, 0, length); - CONSUME_OR_ABORT(const uint8_t *data, self->data, bytes_read); - std::copy(data, data + bytes_read, bytes); + // Initialize the buffer with zeros in case there's no randomness left. + std::fill_n(bytes, length, 0); + + // For integers, we copy bytes directly, because we want to control the + // exact values. + if (length == sizeof(uint8_t) || length == sizeof(uint16_t) || length == sizeof(uint32_t) + || length == sizeof(uint64_t)) { + CONSUME_OR_RETURN(const uint8_t *data, self->data, length); + std::copy(data, data + length, bytes); + if (Fuzz_Data::FUZZ_DEBUG) { + if (length == 1) { + std::printf("rng: %d (0x%02x)\n", bytes[0], bytes[0]); + } else { + std::printf("rng: %02x..%02x[%zu]\n", bytes[0], bytes[length - 1], length); + } + } + return; + } + + // For nonces and keys, we fill the buffer with the same 1-2 bytes + // repeated. We only need these to be different enough to not often be + // the same. + assert(length == 24 || length == 32); + // We must cover the case of having only 1 byte left in the input. In + // that case, we will use the same byte for all the bytes in the output. + const size_t chunk_size = std::max(self->data.size(), static_cast(2)); + CONSUME_OR_RETURN(const uint8_t *chunk, self->data, chunk_size); + if (chunk_size == 2) { + std::fill_n(bytes, length / 2, chunk[0]); + std::fill_n(bytes + length / 2, length / 2, chunk[1]); + } else { + std::fill_n(bytes, length, chunk[0]); + } if (Fuzz_Data::FUZZ_DEBUG) { if (length == 1) { std::printf("rng: %d (0x%02x)\n", bytes[0], bytes[0]); diff --git a/testing/fuzzing/fuzz_support.hh b/testing/fuzzing/fuzz_support.hh index 2cba3f09b2..1e03abf5b8 100644 --- a/testing/fuzzing/fuzz_support.hh +++ b/testing/fuzzing/fuzz_support.hh @@ -5,17 +5,17 @@ #ifndef C_TOXCORE_TESTING_FUZZING_FUZZ_SUPPORT_H #define C_TOXCORE_TESTING_FUZZING_FUZZ_SUPPORT_H +#include +#include #include #include #include #include #include #include -#include #include #include -#include "../../toxcore/tox.h" #include "../../toxcore/tox_private.h" struct Fuzz_Data { @@ -256,6 +256,38 @@ struct Null_System : System { Null_System(); }; +template +class int_map { +public: + struct iterator { + std::pair pair; + + bool operator==(const iterator &rhs) const { return pair.first == rhs.pair.first; } + bool operator!=(const iterator &rhs) const { return pair.first != rhs.pair.first; } + + std::pair operator*() const { return pair; } + const std::pair *operator->() const { return &pair; } + }; + + int_map() = default; + ~int_map() = default; + + iterator find(uint16_t key) const + { + if (!values[key]) { + return end(); + } + return {{key, values[key]}}; + } + + iterator end() const { return {{static_cast(values.size()), nullptr}}; } + + void emplace(uint16_t key, V value) { values[key] = value; } + +private: + std::array values; +}; + /** * A Tox_System implementation that records all I/O but does not actually * perform any real I/O. Everything inside this system is hermetic in-process @@ -280,7 +312,7 @@ struct Record_System : System { * toxcore sends packets to itself sometimes when doing onion routing * with only 2 nodes in the network. */ - std::unordered_map bound; + int_map bound; }; Global &global_; diff --git a/testing/fuzzing/protodump.cc b/testing/fuzzing/protodump.cc index 47db4999a8..3dd781c4b2 100644 --- a/testing/fuzzing/protodump.cc +++ b/testing/fuzzing/protodump.cc @@ -31,8 +31,6 @@ #include "../../toxcore/tox_dispatch.h" #include "../../toxcore/tox_events.h" #include "../../toxcore/tox_private.h" -#include "../../toxcore/tox_struct.h" -#include "../../toxcore/util.h" #include "fuzz_support.hh" namespace { @@ -179,7 +177,7 @@ void dump(std::vector recording, const char *filename) void RecordBootstrap(const char *init, const char *bootstrap) { - Record_System::Global global; + auto global = std::make_unique(); Tox_Options *opts = tox_options_new(nullptr); assert(opts != nullptr); @@ -198,9 +196,9 @@ void RecordBootstrap(const char *init, const char *bootstrap) Tox_Err_New_Testing error_new_testing; Tox_Options_Testing tox_options_testing; - Record_System sys1(global, 4, "tox1"); // fair dice roll - tox_options_set_log_user_data(opts, &sys1); - tox_options_testing.operating_system = sys1.sys.get(); + auto sys1 = std::make_unique(*global, 4, "tox1"); // fair dice roll + tox_options_set_log_user_data(opts, sys1.get()); + tox_options_testing.operating_system = sys1->sys.get(); Tox *tox1 = tox_new_testing(opts, &error_new, &tox_options_testing, &error_new_testing); assert(tox1 != nullptr); assert(error_new == TOX_ERR_NEW_OK); @@ -212,9 +210,9 @@ void RecordBootstrap(const char *init, const char *bootstrap) std::array dht_key1; tox_self_get_dht_id(tox1, dht_key1.data()); - Record_System sys2(global, 5, "tox2"); // unfair dice roll - tox_options_set_log_user_data(opts, &sys2); - tox_options_testing.operating_system = sys2.sys.get(); + auto sys2 = std::make_unique(*global, 5, "tox2"); // unfair dice roll + tox_options_set_log_user_data(opts, sys2.get()); + tox_options_testing.operating_system = sys2->sys.get(); Tox *tox2 = tox_new_testing(opts, &error_new, &tox_options_testing, &error_new_testing); assert(tox2 != nullptr); assert(error_new == TOX_ERR_NEW_OK); @@ -252,26 +250,26 @@ void RecordBootstrap(const char *init, const char *bootstrap) Tox_Events *events; events = tox_events_iterate(tox1, true, &error_iterate); - assert(tox_events_equal(sys1.sys.get(), events, events)); + assert(tox_events_equal(sys1->sys.get(), events, events)); tox_dispatch_invoke(dispatch, events, &state1); tox_events_free(events); events = tox_events_iterate(tox2, true, &error_iterate); - assert(tox_events_equal(sys2.sys.get(), events, events)); + assert(tox_events_equal(sys2->sys.get(), events, events)); tox_dispatch_invoke(dispatch, events, &state2); tox_events_free(events); // Move the clock forward a decent amount so all the time-based checks // trigger more quickly. - sys1.clock += clock_increment; - sys2.clock += clock_increment; + sys1->clock += clock_increment; + sys2->clock += clock_increment; if (Fuzz_Data::FUZZ_DEBUG) { printf("tox1: rng: %d (for clock)\n", clock_increment); printf("tox2: rng: %d (for clock)\n", clock_increment); } - sys1.push(clock_increment); - sys2.push(clock_increment); + sys1->push(clock_increment); + sys2->push(clock_increment); }; while (tox_self_get_connection_status(tox1) == TOX_CONNECTION_NONE @@ -302,7 +300,7 @@ void RecordBootstrap(const char *init, const char *bootstrap) std::printf("tox clients connected\n"); - dump(sys1.take_recording(), init); + dump(sys1->take_recording(), init); while (state1.done < MESSAGE_COUNT && state2.done < MESSAGE_COUNT) { if (Fuzz_Data::FUZZ_DEBUG) { @@ -320,7 +318,7 @@ void RecordBootstrap(const char *init, const char *bootstrap) tox_kill(tox2); tox_kill(tox1); - dump(sys1.recording(), bootstrap); + dump(sys1->recording(), bootstrap); } } diff --git a/testing/fuzzing/rebuild_protodump b/testing/fuzzing/rebuild_protodump index dafeb9958e..4c5a451af7 100755 --- a/testing/fuzzing/rebuild_protodump +++ b/testing/fuzzing/rebuild_protodump @@ -1,6 +1,10 @@ #!/bin/sh -set -eux +set -eux -o pipefail + +WORKSPACE_ROOT=$(bazel info workspace) + +cd "$WORKSPACE_ROOT" bazel test --config=asan-libfuzzer //c-toxcore/testing/fuzzing:protodump_reduce_test diff --git a/toxcore/BUILD.bazel b/toxcore/BUILD.bazel index 82263a4d9d..1f2549fac7 100644 --- a/toxcore/BUILD.bazel +++ b/toxcore/BUILD.bazel @@ -770,6 +770,23 @@ cc_library( ], ) +cc_fuzz_test( + name = "net_crypto_fuzz_test", + size = "small", + testonly = True, + srcs = ["net_crypto_fuzz_test.cc"], + corpus = ["//tools/toktok-fuzzer/corpus:net_crypto_fuzz_test"], + deps = [ + ":DHT", + ":TCP_client", + ":mem_test_util", + ":net_crypto", + ":network", + "//c-toxcore/testing/fuzzing:fuzz_support", + "//c-toxcore/testing/fuzzing:fuzz_tox", + ], +) + cc_library( name = "onion_announce", srcs = ["onion_announce.c"], diff --git a/toxcore/net_crypto.h b/toxcore/net_crypto.h index c21ac7d217..ee89a9eeb0 100644 --- a/toxcore/net_crypto.h +++ b/toxcore/net_crypto.h @@ -20,6 +20,10 @@ #include "net_profile.h" #include "network.h" +#ifdef __cplusplus +extern "C" { +#endif + /*** Crypto payloads. */ /*** Ranges. */ @@ -422,4 +426,8 @@ void kill_net_crypto(Net_Crypto *c); non_null() const Net_Profile *nc_get_tcp_client_net_profile(const Net_Crypto *c); +#ifdef __cplusplus +} /* extern "C" */ +#endif + #endif /* C_TOXCORE_TOXCORE_NET_CRYPTO_H */ diff --git a/toxcore/net_crypto_fuzz_test.cc b/toxcore/net_crypto_fuzz_test.cc new file mode 100644 index 0000000000..e5add8d429 --- /dev/null +++ b/toxcore/net_crypto_fuzz_test.cc @@ -0,0 +1,93 @@ +#include "net_crypto.h" + +#include +#include +#include +#include +#include + +#include "../testing/fuzzing/fuzz_support.hh" +#include "../testing/fuzzing/fuzz_tox.hh" +#include "DHT.h" +#include "TCP_client.h" +#include "network.h" + +namespace { + +std::optional> prepare(Fuzz_Data &input) +{ + IP_Port ipp; + ip_init(&ipp.ip, true); + ipp.port = 33445; + + CONSUME_OR_RETURN_VAL(const uint8_t *iterations_packed, input, 1, std::nullopt); + uint8_t iterations = *iterations_packed; + + return {{ipp, iterations}}; +} + +void TestNetCrypto(Fuzz_Data &input) +{ + const auto prep = prepare(input); + if (!prep.has_value()) { + return; + } + const auto [ipp, iterations] = prep.value(); + + // rest of the fuzz data is input for malloc and network + Fuzz_System sys(input); + + const Ptr logger(logger_new(sys.mem.get()), logger_kill); + if (logger == nullptr) { + return; + } + + const Ptr net(new_networking_ex(logger.get(), sys.mem.get(), sys.ns.get(), + &ipp.ip, ipp.port, ipp.port + 100, nullptr), + kill_networking); + if (net == nullptr) { + return; + } + + const std::unique_ptr> mono_time( + mono_time_new( + sys.mem.get(), [](void *user_data) { return *static_cast(user_data); }, + &sys.clock), + [mem = sys.mem.get()](Mono_Time *ptr) { mono_time_free(mem, ptr); }); + if (mono_time == nullptr) { + return; + } + + const Ptr dht(new_dht(logger.get(), sys.mem.get(), sys.rng.get(), sys.ns.get(), + mono_time.get(), net.get(), false, false), + kill_dht); + if (dht == nullptr) { + return; + } + + const TCP_Proxy_Info proxy_info = {0}; + + const Ptr net_crypto(new_net_crypto(logger.get(), sys.mem.get(), sys.rng.get(), + sys.ns.get(), mono_time.get(), dht.get(), &proxy_info), + kill_net_crypto); + if (net_crypto == nullptr) { + return; + } + + for (uint8_t i = 0; i < iterations; ++i) { + networking_poll(net.get(), nullptr); + do_dht(dht.get()); + do_net_crypto(net_crypto.get(), nullptr); + // "Sleep" + sys.clock += System::BOOTSTRAP_ITERATION_INTERVAL; + } +} + +} // namespace + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + fuzz_select_target(data, size); + return 0; +} diff --git a/toxcore/onion_client.c b/toxcore/onion_client.c index bb01b0e189..e802d70298 100644 --- a/toxcore/onion_client.c +++ b/toxcore/onion_client.c @@ -470,6 +470,7 @@ static int random_path(const Onion_Client *onion_c, Onion_Client_Paths *onion_pa onion_paths->paths[pathnum].path_num = path_num; } else { + assert(0 <= n && n < NUMBER_ONION_PATHS); pathnum = n; } }