From 50094b73851b96081f370318cdbc1b5656722433 Mon Sep 17 00:00:00 2001 From: iphydf Date: Fri, 15 Apr 2022 21:08:23 +0000 Subject: [PATCH] test: Add a protocol dump test to generate initial fuzzer input. --- auto_tests/auto_test_support.c | 24 +- .../docker/tox-bootstrapd.sha256 | 2 +- testing/fuzzing/BUILD.bazel | 54 ++- testing/fuzzing/bootstrap_harness.cc | 43 +-- testing/fuzzing/e2e_fuzz_test.cc | 213 ++++++++++++ testing/fuzzing/fuzz_support.cc | 238 +++++++++++-- testing/fuzzing/fuzz_support.h | 119 ++++++- testing/fuzzing/protodump.cc | 315 ++++++++++++++++++ testing/fuzzing/protodump_reduce.cc | 200 +++++++++++ toxcore/crypto_core.c | 11 +- toxcore/network.c | 157 +++++++-- 11 files changed, 1277 insertions(+), 99 deletions(-) create mode 100644 testing/fuzzing/e2e_fuzz_test.cc create mode 100644 testing/fuzzing/protodump.cc create mode 100644 testing/fuzzing/protodump_reduce.cc diff --git a/auto_tests/auto_test_support.c b/auto_tests/auto_test_support.c index 91a05125..7afe59d8 100644 --- a/auto_tests/auto_test_support.c +++ b/auto_tests/auto_test_support.c @@ -226,17 +226,24 @@ static void initialise_autotox(struct Tox_Options *options, AutoTox *autotox, ui options = default_opts; } - // Try a few ports for the TCP relay. - for (uint16_t tcp_port = autotest_opts->tcp_port; tcp_port < autotest_opts->tcp_port + 200; ++tcp_port) { - tox_options_set_tcp_port(options, tcp_port); + if (tox_options_get_udp_enabled(options)) { + tox_options_set_tcp_port(options, 0); + autotest_opts->tcp_port = 0; autotox->tox = tox_new_log(options, &err, &autotox->index); + ck_assert_msg(err == TOX_ERR_NEW_OK, "unexpected tox_new error: %d", err); + } else { + // Try a few ports for the TCP relay. + for (uint16_t tcp_port = autotest_opts->tcp_port; tcp_port < autotest_opts->tcp_port + 200; ++tcp_port) { + tox_options_set_tcp_port(options, tcp_port); + autotox->tox = tox_new_log(options, &err, &autotox->index); - if (autotox->tox != nullptr) { - autotest_opts->tcp_port = tcp_port; - break; + if (autotox->tox != nullptr) { + autotest_opts->tcp_port = tcp_port; + break; + } + + ck_assert_msg(err == TOX_ERR_NEW_PORT_ALLOC, "unexpected tox_new error (expected PORT_ALLOC): %d", err); } - - ck_assert_msg(err == TOX_ERR_NEW_PORT_ALLOC, "unexpected tox_new error (expected PORT_ALLOC): %d", err); } tox_options_free(default_opts); @@ -322,6 +329,7 @@ static void bootstrap_autotoxes(struct Tox_Options *options, uint32_t tox_count, } if (!udp_enabled) { + ck_assert(autotest_opts->tcp_port != 0); printf("bootstrapping all toxes to local TCP relay running on port %d\n", autotest_opts->tcp_port); for (uint32_t i = 0; i < tox_count; ++i) { diff --git a/other/bootstrap_daemon/docker/tox-bootstrapd.sha256 b/other/bootstrap_daemon/docker/tox-bootstrapd.sha256 index 46b973d3..eba31711 100644 --- a/other/bootstrap_daemon/docker/tox-bootstrapd.sha256 +++ b/other/bootstrap_daemon/docker/tox-bootstrapd.sha256 @@ -1 +1 @@ -c336ec34a4f17601b4bea707acd7162b76e0693ca044715cdb4d6646709cdb39 /usr/local/bin/tox-bootstrapd \ No newline at end of file +5581c3dda4277afb002dcb6c047e6ebfe08a1d97ae9622c37f795002c0c22074 /usr/local/bin/tox-bootstrapd diff --git a/testing/fuzzing/BUILD.bazel b/testing/fuzzing/BUILD.bazel index 944c7035..9f8c0b96 100644 --- a/testing/fuzzing/BUILD.bazel +++ b/testing/fuzzing/BUILD.bazel @@ -1,5 +1,6 @@ -load("@rules_cc//cc:defs.bzl", "cc_library") +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") load("@rules_fuzzing//fuzzing:cc_defs.bzl", "cc_fuzz_test") +load("@rules_fuzzing//fuzzing/private:binary.bzl", "fuzzing_binary") # buildifier: disable=bzl-visibility package(features = ["layering_check"]) @@ -32,10 +33,25 @@ cc_fuzz_test( corpus = ["//tools/toktok-fuzzer/corpus:bootstrap_fuzzer"], deps = [ ":fuzz_support", + ":fuzz_tox", + "//c-toxcore/toxcore:tox", + "//c-toxcore/toxcore:tox_dispatch", + "//c-toxcore/toxcore:tox_events", + ], +) + +cc_fuzz_test( + name = "e2e_fuzz_test", + srcs = ["e2e_fuzz_test.cc"], + copts = ["-UNDEBUG"], + corpus = ["//tools/toktok-fuzzer/corpus:e2e_fuzz_test"], + data = ["//tools/toktok-fuzzer/init:e2e_fuzz_test.dat"], + deps = [ + ":fuzz_support", + ":fuzz_tox", "//c-toxcore/toxcore:tox", "//c-toxcore/toxcore:tox_dispatch", "//c-toxcore/toxcore:tox_events", - "//c-toxcore/toxcore:util", ], ) @@ -49,3 +65,37 @@ cc_fuzz_test( "//c-toxcore/toxcore:tox", ], ) + +cc_binary( + name = "protodump", + srcs = ["protodump.cc"], + copts = ["-UNDEBUG"], + deps = [ + ":fuzz_support", + "//c-toxcore/toxcore:tox", + "//c-toxcore/toxcore:tox_dispatch", + "//c-toxcore/toxcore:tox_events", + "//c-toxcore/toxcore:util", + ], +) + +fuzzing_binary( + name = "protodump_bin", + testonly = True, + binary = ":protodump", + engine = "@rules_fuzzing//fuzzing:cc_engine", + tags = ["manual"], +) + +cc_fuzz_test( + name = "protodump_reduce", + srcs = ["protodump_reduce.cc"], + copts = ["-UNDEBUG"], + deps = [ + ":fuzz_support", + ":fuzz_tox", + "//c-toxcore/toxcore:tox", + "//c-toxcore/toxcore:tox_dispatch", + "//c-toxcore/toxcore:tox_events", + ], +) diff --git a/testing/fuzzing/bootstrap_harness.cc b/testing/fuzzing/bootstrap_harness.cc index b315eaed..eafa503a 100644 --- a/testing/fuzzing/bootstrap_harness.cc +++ b/testing/fuzzing/bootstrap_harness.cc @@ -1,14 +1,11 @@ #include -#include -#include +#include #include "../../toxcore/tox.h" #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.h" +#include "fuzz_tox.h" namespace { @@ -111,28 +108,36 @@ void setup_callbacks(Tox_Dispatch *dispatch) void TestBootstrap(Fuzz_Data &input) { Fuzz_System sys(input); - assert(sys.rng != nullptr); - Tox_Options *opts = tox_options_new(nullptr); + Ptr opts(tox_options_new(nullptr), tox_options_free); assert(opts != nullptr); - tox_options_set_operating_system(opts, sys.sys.get()); + tox_options_set_operating_system(opts.get(), sys.sys.get()); + + tox_options_set_log_callback(opts.get(), + [](Tox *tox, Tox_Log_Level level, const char *file, uint32_t line, const char *func, + const char *message, void *user_data) { + // Log to stdout. + if (DEBUG) { + std::printf("[tox1] %c %s:%d(%s): %s\n", tox_log_level_name(level), file, line, + func, message); + } + }); CONSUME1_OR_RETURN(const uint8_t proxy_type, input); if (proxy_type == 0) { - tox_options_set_proxy_type(opts, TOX_PROXY_TYPE_NONE); + tox_options_set_proxy_type(opts.get(), TOX_PROXY_TYPE_NONE); } else if (proxy_type == 1) { - tox_options_set_proxy_type(opts, TOX_PROXY_TYPE_SOCKS5); - tox_options_set_proxy_host(opts, "127.0.0.1"); - tox_options_set_proxy_port(opts, 8080); + tox_options_set_proxy_type(opts.get(), TOX_PROXY_TYPE_SOCKS5); + tox_options_set_proxy_host(opts.get(), "127.0.0.1"); + tox_options_set_proxy_port(opts.get(), 8080); } else if (proxy_type == 2) { - tox_options_set_proxy_type(opts, TOX_PROXY_TYPE_HTTP); - tox_options_set_proxy_host(opts, "127.0.0.1"); - tox_options_set_proxy_port(opts, 8080); + tox_options_set_proxy_type(opts.get(), TOX_PROXY_TYPE_HTTP); + tox_options_set_proxy_host(opts.get(), "127.0.0.1"); + tox_options_set_proxy_port(opts.get(), 8080); } Tox_Err_New error_new; - Tox *tox = tox_new(opts, &error_new); - tox_options_free(opts); + Tox *tox = tox_new(opts.get(), &error_new); if (tox == nullptr) { // It might fail, because some I/O happens in tox_new, and the fuzzer @@ -144,10 +149,10 @@ void TestBootstrap(Fuzz_Data &input) uint8_t pub_key[TOX_PUBLIC_KEY_SIZE] = {0}; - const bool udp_success = tox_bootstrap(tox, "127.0.0.1", 12345, pub_key, nullptr); + const bool udp_success = tox_bootstrap(tox, "127.0.0.2", 33446, pub_key, nullptr); assert(udp_success); - const bool tcp_success = tox_add_tcp_relay(tox, "127.0.0.1", 12345, pub_key, nullptr); + const bool tcp_success = tox_add_tcp_relay(tox, "127.0.0.2", 33446, pub_key, nullptr); assert(tcp_success); tox_events_init(tox); diff --git a/testing/fuzzing/e2e_fuzz_test.cc b/testing/fuzzing/e2e_fuzz_test.cc new file mode 100644 index 00000000..4bf0d316 --- /dev/null +++ b/testing/fuzzing/e2e_fuzz_test.cc @@ -0,0 +1,213 @@ +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../../toxcore/tox.h" +#include "../../toxcore/tox_dispatch.h" +#include "../../toxcore/tox_events.h" +#include "fuzz_support.h" +#include "fuzz_tox.h" + +namespace { + +void setup_callbacks(Tox_Dispatch *dispatch) +{ + tox_events_callback_conference_connected( + dispatch, [](Tox *tox, const Tox_Event_Conference_Connected *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_conference_connected( + dispatch, [](Tox *tox, const Tox_Event_Conference_Connected *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_conference_invite( + dispatch, [](Tox *tox, const Tox_Event_Conference_Invite *event, void *user_data) { + const uint32_t friend_number = tox_event_conference_invite_get_friend_number(event); + const uint8_t *cookie = tox_event_conference_invite_get_cookie(event); + const uint32_t cookie_length = tox_event_conference_invite_get_cookie_length(event); + tox_conference_join(tox, friend_number, cookie, cookie_length, nullptr); + }); + tox_events_callback_conference_message( + dispatch, [](Tox *tox, const Tox_Event_Conference_Message *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_conference_peer_list_changed(dispatch, + [](Tox *tox, const Tox_Event_Conference_Peer_List_Changed *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_conference_peer_name( + dispatch, [](Tox *tox, const Tox_Event_Conference_Peer_Name *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_conference_title( + dispatch, [](Tox *tox, const Tox_Event_Conference_Title *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_file_chunk_request( + dispatch, [](Tox *tox, const Tox_Event_File_Chunk_Request *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_file_recv( + dispatch, [](Tox *tox, const Tox_Event_File_Recv *event, void *user_data) { + const uint32_t friend_number = tox_event_file_recv_get_friend_number(event); + const uint32_t file_number = tox_event_file_recv_get_file_number(event); + tox_file_control(tox, friend_number, file_number, TOX_FILE_CONTROL_RESUME, nullptr); + }); + tox_events_callback_file_recv_chunk( + dispatch, [](Tox *tox, const Tox_Event_File_Recv_Chunk *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_file_recv_control( + dispatch, [](Tox *tox, const Tox_Event_File_Recv_Control *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_friend_connection_status( + dispatch, [](Tox *tox, const Tox_Event_Friend_Connection_Status *event, void *user_data) { + // OK: friend came online. + const uint32_t friend_number + = tox_event_friend_connection_status_get_friend_number(event); + assert(friend_number != UINT32_MAX); + }); + tox_events_callback_friend_lossless_packet( + dispatch, [](Tox *tox, const Tox_Event_Friend_Lossless_Packet *event, void *user_data) { + const uint32_t friend_number + = tox_event_friend_lossless_packet_get_friend_number(event); + const uint32_t data_length = tox_event_friend_lossless_packet_get_data_length(event); + const uint8_t *data = tox_event_friend_lossless_packet_get_data(event); + tox_friend_send_lossless_packet(tox, friend_number, data, data_length, nullptr); + }); + tox_events_callback_friend_lossy_packet( + dispatch, [](Tox *tox, const Tox_Event_Friend_Lossy_Packet *event, void *user_data) { + const uint32_t friend_number = tox_event_friend_lossy_packet_get_friend_number(event); + const uint32_t data_length = tox_event_friend_lossy_packet_get_data_length(event); + const uint8_t *data = tox_event_friend_lossy_packet_get_data(event); + tox_friend_send_lossy_packet(tox, friend_number, data, data_length, nullptr); + }); + tox_events_callback_friend_message( + dispatch, [](Tox *tox, const Tox_Event_Friend_Message *event, void *user_data) { + const uint32_t friend_number = tox_event_friend_message_get_friend_number(event); + const Tox_Message_Type type = tox_event_friend_message_get_type(event); + const uint32_t message_length = tox_event_friend_message_get_message_length(event); + const uint8_t *message = tox_event_friend_message_get_message(event); + tox_friend_send_message(tox, friend_number, type, message, message_length, nullptr); + }); + tox_events_callback_friend_name( + dispatch, [](Tox *tox, const Tox_Event_Friend_Name *event, void *user_data) { + // OK: friend name received. + }); + tox_events_callback_friend_read_receipt( + dispatch, [](Tox *tox, const Tox_Event_Friend_Read_Receipt *event, void *user_data) { + // OK: message has been received. + }); + tox_events_callback_friend_request( + dispatch, [](Tox *tox, const Tox_Event_Friend_Request *event, void *user_data) { + Tox_Err_Friend_Add err; + tox_friend_add_norequest(tox, tox_event_friend_request_get_public_key(event), &err); + assert(err == TOX_ERR_FRIEND_ADD_OK || err == TOX_ERR_FRIEND_ADD_OWN_KEY + || err == TOX_ERR_FRIEND_ADD_ALREADY_SENT + || err == TOX_ERR_FRIEND_ADD_BAD_CHECKSUM); + }); + tox_events_callback_friend_status( + dispatch, [](Tox *tox, const Tox_Event_Friend_Status *event, void *user_data) { + // OK: friend status received. + }); + tox_events_callback_friend_status_message( + dispatch, [](Tox *tox, const Tox_Event_Friend_Status_Message *event, void *user_data) { + // OK: friend status message received. + }); + tox_events_callback_friend_typing( + dispatch, [](Tox *tox, const Tox_Event_Friend_Typing *event, void *user_data) { + // OK: friend may be typing. + }); + tox_events_callback_self_connection_status( + dispatch, [](Tox *tox, const Tox_Event_Self_Connection_Status *event, void *user_data) { + // OK: we got connected. + }); +} + +void TestEndToEnd(Fuzz_Data &input) +{ + Fuzz_System sys(input); + + Ptr opts(tox_options_new(nullptr), tox_options_free); + assert(opts != nullptr); + tox_options_set_operating_system(opts.get(), sys.sys.get()); + tox_options_set_local_discovery_enabled(opts.get(), false); + + tox_options_set_log_callback(opts.get(), + [](Tox *tox, Tox_Log_Level level, const char *file, uint32_t line, const char *func, + const char *message, void *user_data) { + // Log to stdout. + if (DEBUG) { + std::printf("[tox1] %c %s:%d(%s): %s\n", tox_log_level_name(level), file, line, + func, message); + } + }); + + Tox_Err_New error_new; + Tox *tox = tox_new(opts.get(), &error_new); + + if (tox == nullptr) { + // It might fail, because some I/O happens in tox_new, and the fuzzer + // might do things that make that I/O fail. + return; + } + + assert(error_new == TOX_ERR_NEW_OK); + + tox_events_init(tox); + + Tox_Dispatch *dispatch = tox_dispatch_new(nullptr); + assert(dispatch != nullptr); + setup_callbacks(dispatch); + + while (input.size > 0) { + Tox_Err_Events_Iterate error_iterate; + Tox_Events *events = tox_events_iterate(tox, true, &error_iterate); + assert(tox_events_equal(events, events)); + tox_dispatch_invoke(dispatch, events, tox, nullptr); + tox_events_free(events); + // Move the clock forward a decent amount so all the time-based checks + // trigger more quickly. + sys.clock += std::max(System::MIN_ITERATION_INTERVAL, random_u08(sys.rng.get())); + } + + tox_dispatch_free(dispatch); + tox_kill(tox); +} + +const std::vector startup_data = [] { + constexpr char startup_file[] = "tools/toktok-fuzzer/init/e2e_fuzz_test.dat"; + + struct stat statbuf; + const int res = stat(startup_file, &statbuf); + assert(res == 0); + const int fd = open(startup_file, O_RDONLY); + assert(fd > 0); + + std::vector data(statbuf.st_size); + + const ssize_t read_count = read(fd, data.data(), data.size()); + assert(read_count > 0 && read_count == statbuf.st_size); + return data; +}(); + +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + std::vector full_data(startup_data.size() + size); + std::copy(startup_data.begin(), startup_data.end(), full_data.begin()); + std::copy(data, data + size, full_data.begin() + startup_data.size()); + + Fuzz_Data input{full_data.data(), full_data.size()}; + TestEndToEnd(input); + return 0; // Non-zero return values are reserved for future use. +} diff --git a/testing/fuzzing/fuzz_support.cc b/testing/fuzzing/fuzz_support.cc index d44d7c9a..97ed2cf5 100644 --- a/testing/fuzzing/fuzz_support.cc +++ b/testing/fuzzing/fuzz_support.cc @@ -8,7 +8,10 @@ #include #include +#include #include +#include +#include #include #include @@ -17,15 +20,20 @@ #include "../../toxcore/tox_private.h" #include "func_conversion.h" +const bool DEBUG = false; + // TODO(iphydf): Put this somewhere shared. struct Network_Addr { struct sockaddr_storage addr; size_t size; }; -static int recv_common(Fuzz_Data &input, void *buf, size_t buf_len) +System::~System() { } + +static int recv_common(Fuzz_Data &input, uint8_t *buf, size_t buf_len) { if (input.size < 2) { + errno = ENOMEM; return -1; } @@ -33,9 +41,21 @@ static int recv_common(Fuzz_Data &input, void *buf, size_t buf_len) input.data += 2; input.size -= 2; + if (fuzz_len == 0xffff) { + errno = EWOULDBLOCK; + if (DEBUG) { + std::printf("recvfrom: no data for tox1\n"); + } + return -1; + } + + if (DEBUG) { + std::printf( + "recvfrom: %zu (%02x, %02x) for tox1\n", fuzz_len, input.data[-2], input.data[-1]); + } const size_t res = std::min(buf_len, std::min(fuzz_len, input.size)); - memcpy(buf, input.data, res); + std::copy(input.data, input.data + res, buf); input.data += res; input.size -= res; @@ -43,53 +63,59 @@ static int recv_common(Fuzz_Data &input, void *buf, size_t buf_len) } static constexpr Network_Funcs fuzz_network_funcs = { - /* .close = */ [](void *obj, int sock) { return 0; }, - /* .accept = */ [](void *obj, int sock) { return 2; }, - /* .bind = */ [](void *obj, int sock, const Network_Addr *addr) { return 0; }, - /* .listen = */ [](void *obj, int sock, int backlog) { return 0; }, + /* .close = */ ![](Fuzz_System *self, int sock) { return 0; }, + /* .accept = */ ![](Fuzz_System *self, int sock) { return 1337; }, + /* .bind = */ ![](Fuzz_System *self, int sock, const Network_Addr *addr) { return 0; }, + /* .listen = */ ![](Fuzz_System *self, int sock, int backlog) { return 0; }, /* .recvbuf = */ ![](Fuzz_System *self, int sock) { + assert(sock == 42); const size_t count = random_u16(self->rng.get()); return static_cast(std::min(count, self->data.size)); }, /* .recv = */ ![](Fuzz_System *self, int sock, uint8_t *buf, size_t len) { + assert(sock == 42); // Receive data from the fuzzer. return recv_common(self->data, buf, len); }, /* .recvfrom = */ ![](Fuzz_System *self, int sock, uint8_t *buf, size_t len, Network_Addr *addr) { + assert(sock == 42); + addr->addr = sockaddr_storage{}; // Dummy Addr addr->addr.ss_family = AF_INET; // We want an AF_INET address with dummy values sockaddr_in *addr_in = reinterpret_cast(&addr->addr); - addr_in->sin_port = 12356; - addr_in->sin_addr.s_addr = INADDR_LOOPBACK + 1; + addr_in->sin_port = htons(33446); + addr_in->sin_addr.s_addr = htonl(0x7f000002); // 127.0.0.2 addr->size = sizeof(struct sockaddr); return recv_common(self->data, buf, len); }, /* .send = */ - [](void *obj, int sock, const uint8_t *buf, size_t len) { + ![](Fuzz_System *self, int sock, const uint8_t *buf, size_t len) { + assert(sock == 42); // Always succeed. return static_cast(len); }, /* .sendto = */ - [](void *obj, int sock, const uint8_t *buf, size_t len, const Network_Addr *addr) { + ![](Fuzz_System *self, int sock, const uint8_t *buf, size_t len, const Network_Addr *addr) { + assert(sock == 42); // Always succeed. return static_cast(len); }, - /* .socket = */ [](void *obj, int domain, int type, int proto) { return 1; }, - /* .socket_nonblock = */ [](void *obj, int sock, bool nonblock) { return 0; }, + /* .socket = */ ![](Fuzz_System *self, int domain, int type, int proto) { return 42; }, + /* .socket_nonblock = */ ![](Fuzz_System *self, int sock, bool nonblock) { return 0; }, /* .getsockopt = */ - [](void *obj, int sock, int level, int optname, void *optval, size_t *optlen) { - memset(optval, 0, *optlen); + ![](Fuzz_System *self, int sock, int level, int optname, void *optval, size_t *optlen) { + std::memset(optval, 0, *optlen); return 0; }, /* .setsockopt = */ - [](void *obj, int sock, int level, int optname, const void *optval, size_t optlen) { + ![](Fuzz_System *self, int sock, int level, int optname, const void *optval, size_t optlen) { return 0; }, }; @@ -101,9 +127,12 @@ static constexpr Random_Funcs fuzz_random_funcs = { const size_t bytes_read = std::min(length, self->data.size); // Initialize everything to make MSAN and others happy std::memset(bytes, 0, length); - std::memcpy(bytes, self->data.data, bytes_read); + std::copy(self->data.data, self->data.data + bytes_read, bytes); self->data.data += bytes_read; self->data.size -= bytes_read; + if (DEBUG) { + std::printf("rng: %02x..%02x[%zu] -> tox1\n", bytes[0], bytes[length - 1], length); + } }, /* .random_uniform = */ ![](Fuzz_System *self, uint32_t upper_bound) { @@ -118,10 +147,12 @@ static constexpr Random_Funcs fuzz_random_funcs = { }; Fuzz_System::Fuzz_System(Fuzz_Data &input) - : data(input) - , sys(std::make_unique()) - , ns(std::make_unique(Network{&fuzz_network_funcs, this})) - , rng(std::make_unique(Random{&fuzz_random_funcs, this})) + : System{ + std::make_unique(), + std::make_unique(Network{&fuzz_network_funcs, this}), + std::make_unique(Random{&fuzz_random_funcs, this}), + } + , data(input) { sys->mono_time_callback = ![](Fuzz_System *self) { return self->clock; }; sys->mono_time_user_data = this; @@ -129,43 +160,43 @@ Fuzz_System::Fuzz_System(Fuzz_Data &input) sys->rng = rng.get(); } -Fuzz_System::~Fuzz_System() { } - static constexpr Network_Funcs null_network_funcs = { - /* .close = */ [](void *obj, int sock) { return 0; }, - /* .accept = */ [](void *obj, int sock) { return 2; }, - /* .bind = */ [](void *obj, int sock, const Network_Addr *addr) { return 0; }, - /* .listen = */ [](void *obj, int sock, int backlog) { return 0; }, + /* .close = */ ![](Null_System *self, int sock) { return 0; }, + /* .accept = */ ![](Null_System *self, int sock) { return 1337; }, + /* .bind = */ ![](Null_System *self, int sock, const Network_Addr *addr) { return 0; }, + /* .listen = */ ![](Null_System *self, int sock, int backlog) { return 0; }, /* .recvbuf = */ ![](Null_System *self, int sock) { return 0; }, /* .recv = */ ![](Null_System *self, int sock, uint8_t *buf, size_t len) { + // Always fail. errno = ENOMEM; return -1; }, /* .recvfrom = */ ![](Null_System *self, int sock, uint8_t *buf, size_t len, Network_Addr *addr) { + // Always fail. errno = ENOMEM; return -1; }, /* .send = */ - [](void *obj, int sock, const uint8_t *buf, size_t len) { + ![](Null_System *self, int sock, const uint8_t *buf, size_t len) { // Always succeed. return static_cast(len); }, /* .sendto = */ - [](void *obj, int sock, const uint8_t *buf, size_t len, const Network_Addr *addr) { + ![](Null_System *self, int sock, const uint8_t *buf, size_t len, const Network_Addr *addr) { // Always succeed. return static_cast(len); }, - /* .socket = */ [](void *obj, int domain, int type, int proto) { return 1; }, - /* .socket_nonblock = */ [](void *obj, int sock, bool nonblock) { return 0; }, + /* .socket = */ ![](Null_System *self, int domain, int type, int proto) { return 42; }, + /* .socket_nonblock = */ ![](Null_System *self, int sock, bool nonblock) { return 0; }, /* .getsockopt = */ - [](void *obj, int sock, int level, int optname, void *optval, size_t *optlen) { - memset(optval, 0, *optlen); + ![](Null_System *self, int sock, int level, int optname, void *optval, size_t *optlen) { + std::memset(optval, 0, *optlen); return 0; }, /* .setsockopt = */ - [](void *obj, int sock, int level, int optname, const void *optval, size_t optlen) { + ![](Null_System *self, int sock, int level, int optname, const void *optval, size_t optlen) { return 0; }, }; @@ -191,9 +222,11 @@ static constexpr Random_Funcs null_random_funcs = { }; Null_System::Null_System() - : sys(std::make_unique()) - , ns(std::make_unique(Network{&null_network_funcs, this})) - , rng(std::make_unique(Random{&null_random_funcs, this})) + : System{ + std::make_unique(), + std::make_unique(Network{&null_network_funcs, this}), + std::make_unique(Random{&null_random_funcs, this}), + } { sys->mono_time_callback = ![](Fuzz_System *self) { return self->clock; }; sys->mono_time_user_data = this; @@ -201,4 +234,135 @@ Null_System::Null_System() sys->rng = rng.get(); } -Null_System::~Null_System() { } +static uint16_t get_port(const Network_Addr *addr) +{ + if (addr->addr.ss_family == AF_INET6) { + return reinterpret_cast(&addr->addr)->sin6_port; + } else { + assert(addr->addr.ss_family == AF_INET); + return reinterpret_cast(&addr->addr)->sin_port; + } +} + +static constexpr Network_Funcs record_network_funcs = { + /* .close = */ ![](Record_System *self, int sock) { return 0; }, + /* .accept = */ ![](Record_System *self, int sock) { return 2; }, + /* .bind = */ + ![](Record_System *self, int sock, const Network_Addr *addr) { + const uint16_t port = get_port(addr); + if (self->global_.bound.find(port) != self->global_.bound.end()) { + errno = EADDRINUSE; + return -1; + } + self->global_.bound.emplace(port, self); + self->port = port; + return 0; + }, + /* .listen = */ ![](Record_System *self, int sock, int backlog) { return 0; }, + /* .recvbuf = */ ![](Record_System *self, int sock) { return 0; }, + /* .recv = */ + ![](Record_System *self, int sock, uint8_t *buf, size_t len) { + // Always fail. + errno = ENOMEM; + return -1; + }, + /* .recvfrom = */ + ![](Record_System *self, int sock, uint8_t *buf, size_t len, Network_Addr *addr) { + assert(sock == 42); + if (self->recvq.empty()) { + self->recording.push_back(0xff); + self->recording.push_back(0xff); + errno = EWOULDBLOCK; + if (DEBUG) { + std::printf("recvfrom: no data for %s\n", self->name_); + } + return -1; + } + const auto [from, packet] = std::move(self->recvq.front()); + self->recvq.pop_front(); + const size_t recvlen = std::min(len, packet.size()); + std::copy(packet.begin(), packet.end(), buf); + + addr->addr = sockaddr_storage{}; + // Dummy Addr + addr->addr.ss_family = AF_INET; + + // We want an AF_INET address with dummy values + sockaddr_in *addr_in = reinterpret_cast(&addr->addr); + addr_in->sin_port = from; + addr_in->sin_addr.s_addr = htonl(0x7f000002); // 127.0.0.2 + addr->size = sizeof(struct sockaddr); + + assert(recvlen > 0 && recvlen <= INT_MAX); + self->recording.push_back(recvlen >> 8); + self->recording.push_back(recvlen & 0xff); + if (DEBUG) { + std::printf("recvfrom: %zu (%02x, %02x) for %s\n", recvlen, self->recording.end()[-2], + self->recording.end()[-1], self->name_); + } + self->recording.insert(self->recording.end(), buf, buf + recvlen); + return static_cast(recvlen); + }, + /* .send = */ + ![](Record_System *self, int sock, const uint8_t *buf, size_t len) { + // Always succeed. + return static_cast(len); + }, + /* .sendto = */ + ![](Record_System *self, int sock, const uint8_t *buf, size_t len, const Network_Addr *addr) { + assert(sock == 42); + auto backend = self->global_.bound.find(get_port(addr)); + assert(backend != self->global_.bound.end()); + backend->second->receive(self->port, buf, len); + return static_cast(len); + }, + /* .socket = */ ![](Record_System *self, int domain, int type, int proto) { return 42; }, + /* .socket_nonblock = */ ![](Record_System *self, int sock, bool nonblock) { return 0; }, + /* .getsockopt = */ + ![](Record_System *self, int sock, int level, int optname, void *optval, size_t *optlen) { + std::memset(optval, 0, *optlen); + return 0; + }, + /* .setsockopt = */ + ![](Record_System *self, int sock, int level, int optname, const void *optval, size_t optlen) { + return 0; + }, +}; + +static constexpr Random_Funcs record_random_funcs = { + /* .random_bytes = */ + ![](Record_System *self, uint8_t *bytes, size_t length) { + for (size_t i = 0; i < length; ++i) { + bytes[i] = simple_rng(self->seed_) & 0xff; + self->recording.push_back(bytes[i]); + } + if (DEBUG) { + std::printf( + "rng: %02x..%02x[%zu] -> %s\n", bytes[0], bytes[length - 1], length, self->name_); + } + }, + /* .random_uniform = */ + fuzz_random_funcs.random_uniform, +}; + +Record_System::Record_System(Global &global, uint64_t seed, const char *name) + : System{ + std::make_unique(), + std::make_unique(Network{&record_network_funcs, this}), + std::make_unique(Random{&record_random_funcs, this}), + } + , global_(global) + , seed_(seed) + , name_(name) +{ + sys->mono_time_callback = ![](Fuzz_System *self) { return self->clock; }; + sys->mono_time_user_data = this; + sys->ns = ns.get(); + sys->rng = rng.get(); +} + +void Record_System::receive(uint16_t send_port, const uint8_t *buf, size_t len) +{ + assert(port != 0); + recvq.emplace_back(send_port, std::vector{buf, buf + len}); +} diff --git a/testing/fuzzing/fuzz_support.h b/testing/fuzzing/fuzz_support.h index 9f274d89..1a5bff14 100644 --- a/testing/fuzzing/fuzz_support.h +++ b/testing/fuzzing/fuzz_support.h @@ -7,7 +7,10 @@ #include #include +#include #include +#include +#include #include #include "../../toxcore/tox.h" @@ -100,20 +103,51 @@ void fuzz_select_target(const uint8_t *data, std::size_t size, Args &&... args) struct Network; struct Random; +struct System { + std::unique_ptr sys; + std::unique_ptr ns; + std::unique_ptr rng; + + // Not inline because sizeof of the above 2 structs is not known everywhere. + ~System(); + + /** @brief Deterministic system clock for this instance. + * + * Different instances can evolve independently. The time is initialised + * with a large number, because otherwise many zero-initialised "empty" + * friends inside toxcore will be "not timed out" for a long time, messing + * up some logic. Tox moderately depends on the clock being fairly high up + * (not close to 0). + */ + uint64_t clock = UINT32_MAX; + + /** + * During bootstrap, move the time forward a decent amount, because friend + * finding and bootstrapping takes significant (around 10 seconds) wall + * clock time that should be advanced more quickly in the test. + */ + static constexpr uint8_t BOOTSTRAP_ITERATION_INTERVAL = 200; + /** + * Less than BOOTSTRAP_ITERATION_INTERVAL because otherwise we'll spam + * onion announce packets. + */ + static constexpr uint8_t MESSAGE_ITERATION_INTERVAL = 20; + /** + * Move the clock forward at least 20ms so at least some amount of + * time passes on each iteration. + */ + static constexpr uint8_t MIN_ITERATION_INTERVAL = 20; +}; + /** * A Tox_System implementation that consumes fuzzer input to produce network * inputs and random numbers. Once it runs out of fuzzer input, network receive * functions return no more data and the random numbers are always zero. */ -struct Fuzz_System { - uint64_t clock = 0; +struct Fuzz_System : System { Fuzz_Data &data; - std::unique_ptr sys; - std::unique_ptr ns; - std::unique_ptr rng; explicit Fuzz_System(Fuzz_Data &input); - ~Fuzz_System(); }; /** @@ -121,15 +155,76 @@ struct Fuzz_System { * working and deterministic RNG. Network receive functions always fail, send * always succeeds. */ -struct Null_System { - uint64_t clock = 0; +struct Null_System : System { uint64_t seed = 4; // chosen by fair dice roll. guaranteed to be random. - std::unique_ptr sys; - std::unique_ptr ns; - std::unique_ptr rng; Null_System(); - ~Null_System(); }; +/** + * 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 + * and fully deterministic. + * + * Note: take care not to initialise two systems with the same seed, since + * that's the only thing distinguishing the system's behaviour. Two toxes + * initialised with the same seed will be identical (same keys, etc.). + */ +struct Record_System : System { + /** @brief State shared between all tox instances. */ + struct Global { + /** @brief Bound UDP ports and their system instance. + * + * This implements an in-process network where instances can send + * packets to other instances by inserting them into the receiver's + * recvq using the receive function. + * + * We need to keep track of ports associated with recv queues because + * toxcore sends packets to itself sometimes when doing onion routing + * with only 2 nodes in the network. + */ + std::unordered_map bound; + }; + + Global &global_; + uint64_t seed_; //!< Current PRNG state. + const char *name_; //!< Tox system name ("tox1"/"tox2") for logging. + + std::deque>> recvq; + uint16_t port = 0; //!< Sending port for this system instance. + std::vector recording; + + explicit Record_System(Global &global, uint64_t seed, const char *name); + + /** @brief Deposit a network packet in this instance's recvq. + */ + void receive(uint16_t send_port, const uint8_t *buf, size_t len); +}; + +/** @brief Enable debug logging. + * + * This should not be enabled in fuzzer code while fuzzing, as console I/O slows + * everything down drastically. It's useful while developing the fuzzer and the + * protodump program. + */ +extern const bool DEBUG; + +inline constexpr char tox_log_level_name(Tox_Log_Level level) +{ + switch (level) { + case TOX_LOG_LEVEL_TRACE: + return 'T'; + case TOX_LOG_LEVEL_DEBUG: + return 'D'; + case TOX_LOG_LEVEL_INFO: + return 'I'; + case TOX_LOG_LEVEL_WARNING: + return 'W'; + case TOX_LOG_LEVEL_ERROR: + return 'E'; + } + + return '?'; +} + #endif // C_TOXCORE_TESTING_FUZZING_FUZZ_SUPPORT_H diff --git a/testing/fuzzing/protodump.cc b/testing/fuzzing/protodump.cc new file mode 100644 index 00000000..e7001fd5 --- /dev/null +++ b/testing/fuzzing/protodump.cc @@ -0,0 +1,315 @@ +/** @file + * @brief Generates a valid input for e2e_fuzz_test. + * + * This bootstraps 2 toxes tox1 and tox2, adds tox1 as tox2's friend, waits for + * the friend request, then tox1 adds tox2 in response, waits for the friend to + * come online, sends a 2-message exchange, and waits for the read receipt. + * + * All random inputs and network traffic is recorded and dumped to files at the + * end. This can then be fed to e2e_fuzz_test for replay (only of tox1) and + * further code path exploration using fuzzer mutations. + * + * We write 2 files: an init file that contains all the inputs needed to reach + * the "friend online" state, and a smaller file containing things to mutate + * once the tox instance is in that state. This allows for more specific + * exploration of code paths starting from "friend is online". DHT and onion + * packet parsing is explored more in bootstrap_fuzz_test. + * + * Usage: + * + * bazel build //c-toxcore/testing/fuzzing:protodump_bin && \ + * bazel-bin/c-toxcore/testing/fuzzing/protodump_bin + */ +#include +#include +#include +#include +#include + +#include "../../toxcore/tox.h" +#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.h" + +namespace { + +/** @brief Number of messages to exchange between tox1 and tox2. + * + * The higher this number, the more room we give the fuzzer to mutate the + * exchange into something more interesting. If it's too high, the fuzzer will + * be slow. + */ +constexpr uint32_t MESSAGE_COUNT = 5; + +void setup_callbacks(Tox_Dispatch *dispatch) +{ + tox_events_callback_conference_connected( + dispatch, [](Tox *tox, const Tox_Event_Conference_Connected *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_conference_connected( + dispatch, [](Tox *tox, const Tox_Event_Conference_Connected *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_conference_invite( + dispatch, [](Tox *tox, const Tox_Event_Conference_Invite *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_conference_message( + dispatch, [](Tox *tox, const Tox_Event_Conference_Message *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_conference_peer_list_changed(dispatch, + [](Tox *tox, const Tox_Event_Conference_Peer_List_Changed *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_conference_peer_name( + dispatch, [](Tox *tox, const Tox_Event_Conference_Peer_Name *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_conference_title( + dispatch, [](Tox *tox, const Tox_Event_Conference_Title *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_file_chunk_request( + dispatch, [](Tox *tox, const Tox_Event_File_Chunk_Request *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_file_recv( + dispatch, [](Tox *tox, const Tox_Event_File_Recv *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_file_recv_chunk( + dispatch, [](Tox *tox, const Tox_Event_File_Recv_Chunk *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_file_recv_control( + dispatch, [](Tox *tox, const Tox_Event_File_Recv_Control *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_friend_connection_status( + dispatch, [](Tox *tox, const Tox_Event_Friend_Connection_Status *event, void *user_data) { + // OK: friend came online. + const uint32_t friend_number + = tox_event_friend_connection_status_get_friend_number(event); + assert(friend_number == 0); + const uint8_t message = 'A'; + Tox_Err_Friend_Send_Message err; + tox_friend_send_message(tox, friend_number, TOX_MESSAGE_TYPE_NORMAL, &message, 1, &err); + assert(err == TOX_ERR_FRIEND_SEND_MESSAGE_OK); + }); + tox_events_callback_friend_lossless_packet( + dispatch, [](Tox *tox, const Tox_Event_Friend_Lossless_Packet *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_friend_lossy_packet( + dispatch, [](Tox *tox, const Tox_Event_Friend_Lossy_Packet *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_friend_message( + dispatch, [](Tox *tox, const Tox_Event_Friend_Message *event, void *user_data) { + const uint32_t friend_number = tox_event_friend_message_get_friend_number(event); + assert(friend_number == 0); + const uint32_t message_length = tox_event_friend_message_get_message_length(event); + assert(message_length == 1); + const uint8_t *message = tox_event_friend_message_get_message(event); + const uint8_t reply = message[0] + 1; + Tox_Err_Friend_Send_Message err; + tox_friend_send_message(tox, friend_number, TOX_MESSAGE_TYPE_NORMAL, &reply, 1, &err); + assert(err == TOX_ERR_FRIEND_SEND_MESSAGE_OK); + }); + tox_events_callback_friend_name( + dispatch, [](Tox *tox, const Tox_Event_Friend_Name *event, void *user_data) { + const uint32_t friend_number = tox_event_friend_name_get_friend_number(event); + assert(friend_number == 0); + }); + tox_events_callback_friend_read_receipt( + dispatch, [](Tox *tox, const Tox_Event_Friend_Read_Receipt *event, void *user_data) { + const uint32_t friend_number = tox_event_friend_read_receipt_get_friend_number(event); + assert(friend_number == 0); + const uint32_t message_id = tox_event_friend_read_receipt_get_message_id(event); + uint32_t *done = static_cast(user_data); + *done = std::max(*done, message_id); + }); + tox_events_callback_friend_request( + dispatch, [](Tox *tox, const Tox_Event_Friend_Request *event, void *user_data) { + Tox_Err_Friend_Add err; + tox_friend_add_norequest(tox, tox_event_friend_request_get_public_key(event), &err); + assert(err == TOX_ERR_FRIEND_ADD_OK || err == TOX_ERR_FRIEND_ADD_OWN_KEY + || err == TOX_ERR_FRIEND_ADD_ALREADY_SENT + || err == TOX_ERR_FRIEND_ADD_BAD_CHECKSUM); + }); + tox_events_callback_friend_status( + dispatch, [](Tox *tox, const Tox_Event_Friend_Status *event, void *user_data) { + const uint32_t friend_number = tox_event_friend_status_get_friend_number(event); + assert(friend_number == 0); + }); + tox_events_callback_friend_status_message( + dispatch, [](Tox *tox, const Tox_Event_Friend_Status_Message *event, void *user_data) { + const uint32_t friend_number = tox_event_friend_status_message_get_friend_number(event); + assert(friend_number == 0); + }); + tox_events_callback_friend_typing( + dispatch, [](Tox *tox, const Tox_Event_Friend_Typing *event, void *user_data) { + const uint32_t friend_number = tox_event_friend_typing_get_friend_number(event); + assert(friend_number == 0); + assert(!tox_event_friend_typing_get_typing(event)); + }); + tox_events_callback_self_connection_status( + dispatch, [](Tox *tox, const Tox_Event_Self_Connection_Status *event, void *user_data) { + // OK: we got connected. + }); +} + +void dump(std::vector &recording, const char *filename) +{ + std::printf("%zu bytes: %s\n", recording.size(), filename); + std::ofstream(filename, std::ios::binary) + .write(reinterpret_cast(recording.data()), recording.size()); + recording.clear(); +} + +void RecordBootstrap() +{ + Record_System::Global global; + + Tox_Options *opts = tox_options_new(nullptr); + assert(opts != nullptr); + + tox_options_set_local_discovery_enabled(opts, false); + + tox_options_set_log_callback(opts, + [](Tox *tox, Tox_Log_Level level, const char *file, uint32_t line, const char *func, + const char *message, void *user_data) { + // Log to stdout. + std::printf("[%s] %c %s:%d(%s): %s\n", static_cast(user_data)->name_, + tox_log_level_name(level), file, line, func, message); + }); + + Tox_Err_New error_new; + + Record_System sys1(global, 4, "tox1"); // fair dice roll + tox_options_set_log_user_data(opts, &sys1); + tox_options_set_operating_system(opts, sys1.sys.get()); + Tox *tox1 = tox_new(opts, &error_new); + assert(tox1 != nullptr); + assert(error_new == TOX_ERR_NEW_OK); + std::array address1; + tox_self_get_address(tox1, address1.data()); + std::array pk1; + tox_self_get_public_key(tox1, pk1.data()); + 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_set_operating_system(opts, sys2.sys.get()); + Tox *tox2 = tox_new(opts, &error_new); + assert(tox2 != nullptr); + assert(error_new == TOX_ERR_NEW_OK); + std::array address2; + tox_self_get_address(tox2, address2.data()); + std::array pk2; + tox_self_get_public_key(tox2, pk2.data()); + std::array dht_key2; + tox_self_get_dht_id(tox2, dht_key2.data()); + + assert(address1 != address2); + assert(pk1 != pk2); + assert(dht_key1 != dht_key2); + + tox_options_free(opts); + + const uint16_t port = tox_self_get_udp_port(tox1, nullptr); + + const bool udp_success = tox_bootstrap(tox2, "127.0.0.2", port, dht_key1.data(), nullptr); + assert(udp_success); + + tox_events_init(tox1); + tox_events_init(tox2); + + Tox_Dispatch *dispatch = tox_dispatch_new(nullptr); + assert(dispatch != nullptr); + setup_callbacks(dispatch); + + uint32_t done1 = 0; + uint32_t done2 = 0; + + const auto iterate = [&](uint8_t clock_increment) { + Tox_Err_Events_Iterate error_iterate; + Tox_Events *events; + + events = tox_events_iterate(tox1, true, &error_iterate); + assert(tox_events_equal(events, events)); + tox_dispatch_invoke(dispatch, events, tox1, &done1); + tox_events_free(events); + + events = tox_events_iterate(tox2, true, &error_iterate); + assert(tox_events_equal(events, events)); + tox_dispatch_invoke(dispatch, events, tox2, &done2); + 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.recording.push_back(clock_increment); + sys2.recording.push_back(clock_increment); + }; + + while (tox_self_get_connection_status(tox1) == TOX_CONNECTION_NONE + || tox_self_get_connection_status(tox2) == TOX_CONNECTION_NONE) { + if (DEBUG) { + std::printf("tox1: %d, tox2: %d\n", tox_self_get_connection_status(tox1), + tox_self_get_connection_status(tox2)); + } + iterate(System::BOOTSTRAP_ITERATION_INTERVAL); + } + + std::printf("toxes are online\n"); + + const uint8_t msg = 'A'; + const uint32_t friend_number = tox_friend_add(tox2, address1.data(), &msg, 1, nullptr); + assert(friend_number == 0); + + while (tox_friend_get_connection_status(tox2, friend_number, nullptr) == TOX_CONNECTION_NONE + || tox_friend_get_connection_status(tox1, 0, nullptr) == TOX_CONNECTION_NONE) { + if (DEBUG) { + std::printf("tox1: %d, tox2: %d, tox1 -> tox2: %d, tox2 -> tox1: %d\n", + tox_self_get_connection_status(tox1), tox_self_get_connection_status(tox2), + tox_friend_get_connection_status(tox1, 0, nullptr), + tox_friend_get_connection_status(tox2, 0, nullptr)); + } + iterate(System::BOOTSTRAP_ITERATION_INTERVAL); + } + + std::printf("tox clients connected\n"); + + dump(sys1.recording, "tools/toktok-fuzzer/init/e2e_fuzz_test.dat"); + + while (done1 < MESSAGE_COUNT && done2 < MESSAGE_COUNT) { + if (DEBUG) { + std::printf("tox1: %d, tox2: %d, tox1 -> tox2: %d, tox2 -> tox1: %d\n", + tox_self_get_connection_status(tox1), tox_self_get_connection_status(tox2), + tox_friend_get_connection_status(tox1, 0, nullptr), + tox_friend_get_connection_status(tox2, 0, nullptr)); + } + iterate(System::MESSAGE_ITERATION_INTERVAL); + } + + std::printf("test complete\n"); + + tox_dispatch_free(dispatch); + tox_kill(tox2); + tox_kill(tox1); + + dump(sys1.recording, "tools/toktok-fuzzer/corpus/e2e_fuzz_test/bootstrap.dat"); +} + +} + +int main(void) { RecordBootstrap(); } diff --git a/testing/fuzzing/protodump_reduce.cc b/testing/fuzzing/protodump_reduce.cc new file mode 100644 index 00000000..40aa5ae3 --- /dev/null +++ b/testing/fuzzing/protodump_reduce.cc @@ -0,0 +1,200 @@ +#include +#include + +#include "../../toxcore/tox.h" +#include "../../toxcore/tox_dispatch.h" +#include "../../toxcore/tox_events.h" +#include "fuzz_support.h" +#include "fuzz_tox.h" + +namespace { + +/** + * Whether to abort the program if a friend connection can be established. + * + * This is useful to make the fuzzer produce minimal startup data so the + * interesting part of the fuzzer (the part that comes after the friend + * connection is established) can run sooner and thus more frequently. + */ +constexpr bool REDUCE_PROTODUMP = false; + +void setup_callbacks(Tox_Dispatch *dispatch) +{ + tox_events_callback_conference_connected( + dispatch, [](Tox *tox, const Tox_Event_Conference_Connected *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_conference_connected( + dispatch, [](Tox *tox, const Tox_Event_Conference_Connected *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_conference_invite( + dispatch, [](Tox *tox, const Tox_Event_Conference_Invite *event, void *user_data) { + const uint32_t friend_number = tox_event_conference_invite_get_friend_number(event); + const uint8_t *cookie = tox_event_conference_invite_get_cookie(event); + const uint32_t cookie_length = tox_event_conference_invite_get_cookie_length(event); + tox_conference_join(tox, friend_number, cookie, cookie_length, nullptr); + }); + tox_events_callback_conference_message( + dispatch, [](Tox *tox, const Tox_Event_Conference_Message *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_conference_peer_list_changed(dispatch, + [](Tox *tox, const Tox_Event_Conference_Peer_List_Changed *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_conference_peer_name( + dispatch, [](Tox *tox, const Tox_Event_Conference_Peer_Name *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_conference_title( + dispatch, [](Tox *tox, const Tox_Event_Conference_Title *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_file_chunk_request( + dispatch, [](Tox *tox, const Tox_Event_File_Chunk_Request *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_file_recv( + dispatch, [](Tox *tox, const Tox_Event_File_Recv *event, void *user_data) { + const uint32_t friend_number = tox_event_file_recv_get_friend_number(event); + const uint32_t file_number = tox_event_file_recv_get_file_number(event); + tox_file_control(tox, friend_number, file_number, TOX_FILE_CONTROL_RESUME, nullptr); + }); + tox_events_callback_file_recv_chunk( + dispatch, [](Tox *tox, const Tox_Event_File_Recv_Chunk *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_file_recv_control( + dispatch, [](Tox *tox, const Tox_Event_File_Recv_Control *event, void *user_data) { + assert(event == nullptr); + }); + tox_events_callback_friend_connection_status( + dispatch, [](Tox *tox, const Tox_Event_Friend_Connection_Status *event, void *user_data) { + // OK: friend came online. + const uint32_t friend_number + = tox_event_friend_connection_status_get_friend_number(event); + assert(friend_number != UINT32_MAX); + }); + tox_events_callback_friend_lossless_packet( + dispatch, [](Tox *tox, const Tox_Event_Friend_Lossless_Packet *event, void *user_data) { + const uint32_t friend_number + = tox_event_friend_lossless_packet_get_friend_number(event); + const uint32_t data_length = tox_event_friend_lossless_packet_get_data_length(event); + const uint8_t *data = tox_event_friend_lossless_packet_get_data(event); + tox_friend_send_lossless_packet(tox, friend_number, data, data_length, nullptr); + }); + tox_events_callback_friend_lossy_packet( + dispatch, [](Tox *tox, const Tox_Event_Friend_Lossy_Packet *event, void *user_data) { + const uint32_t friend_number = tox_event_friend_lossy_packet_get_friend_number(event); + const uint32_t data_length = tox_event_friend_lossy_packet_get_data_length(event); + const uint8_t *data = tox_event_friend_lossy_packet_get_data(event); + tox_friend_send_lossy_packet(tox, friend_number, data, data_length, nullptr); + }); + tox_events_callback_friend_message( + dispatch, [](Tox *tox, const Tox_Event_Friend_Message *event, void *user_data) { + const uint32_t friend_number = tox_event_friend_message_get_friend_number(event); + const Tox_Message_Type type = tox_event_friend_message_get_type(event); + const uint32_t message_length = tox_event_friend_message_get_message_length(event); + const uint8_t *message = tox_event_friend_message_get_message(event); + tox_friend_send_message(tox, friend_number, type, message, message_length, nullptr); + }); + tox_events_callback_friend_name( + dispatch, [](Tox *tox, const Tox_Event_Friend_Name *event, void *user_data) { + // OK: friend name received. + }); + tox_events_callback_friend_read_receipt( + dispatch, [](Tox *tox, const Tox_Event_Friend_Read_Receipt *event, void *user_data) { + // OK: message has been received. + }); + tox_events_callback_friend_request( + dispatch, [](Tox *tox, const Tox_Event_Friend_Request *event, void *user_data) { + Tox_Err_Friend_Add err; + tox_friend_add_norequest(tox, tox_event_friend_request_get_public_key(event), &err); + assert(err == TOX_ERR_FRIEND_ADD_OK || err == TOX_ERR_FRIEND_ADD_OWN_KEY + || err == TOX_ERR_FRIEND_ADD_ALREADY_SENT + || err == TOX_ERR_FRIEND_ADD_BAD_CHECKSUM); + }); + tox_events_callback_friend_status( + dispatch, [](Tox *tox, const Tox_Event_Friend_Status *event, void *user_data) { + // OK: friend status received. + }); + tox_events_callback_friend_status_message( + dispatch, [](Tox *tox, const Tox_Event_Friend_Status_Message *event, void *user_data) { + // OK: friend status message received. + }); + tox_events_callback_friend_typing( + dispatch, [](Tox *tox, const Tox_Event_Friend_Typing *event, void *user_data) { + // OK: friend may be typing. + }); + tox_events_callback_self_connection_status( + dispatch, [](Tox *tox, const Tox_Event_Self_Connection_Status *event, void *user_data) { + // OK: we got connected. + }); +} + +void TestEndToEnd(Fuzz_Data &input) +{ + Fuzz_System sys(input); + + Ptr opts(tox_options_new(nullptr), tox_options_free); + assert(opts != nullptr); + tox_options_set_operating_system(opts.get(), sys.sys.get()); + tox_options_set_local_discovery_enabled(opts.get(), false); + + tox_options_set_log_callback(opts.get(), + [](Tox *tox, Tox_Log_Level level, const char *file, uint32_t line, const char *func, + const char *message, void *user_data) { + // Log to stdout. + if (DEBUG) { + std::printf("[tox1] %c %s:%d(%s): %s\n", tox_log_level_name(level), file, line, + func, message); + } + }); + + Tox_Err_New error_new; + Tox *tox = tox_new(opts.get(), &error_new); + + if (tox == nullptr) { + // It might fail, because some I/O happens in tox_new, and the fuzzer + // might do things that make that I/O fail. + return; + } + + assert(error_new == TOX_ERR_NEW_OK); + + tox_events_init(tox); + + Tox_Dispatch *dispatch = tox_dispatch_new(nullptr); + assert(dispatch != nullptr); + setup_callbacks(dispatch); + + while (input.size > 0) { + Tox_Err_Events_Iterate error_iterate; + Tox_Events *events = tox_events_iterate(tox, true, &error_iterate); + assert(tox_events_equal(events, events)); + tox_dispatch_invoke(dispatch, events, tox, nullptr); + tox_events_free(events); + sys.clock += std::max(System::MIN_ITERATION_INTERVAL, random_u08(sys.rng.get())); + } + + if (REDUCE_PROTODUMP) { + assert(tox_friend_get_connection_status(tox, 0, nullptr) != 2); + } else { + printf("friend: %d\n", tox_friend_get_connection_status(tox, 0, nullptr)); + printf("self: %d\n", tox_self_get_connection_status(tox)); + } + + tox_dispatch_free(dispatch); + tox_kill(tox); +} + +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + Fuzz_Data input{data, size}; + TestEndToEnd(input); + return 0; // Non-zero return values are reserved for future use. +} diff --git a/toxcore/crypto_core.c b/toxcore/crypto_core.c index 1ec2cee5..f52282d0 100644 --- a/toxcore/crypto_core.c +++ b/toxcore/crypto_core.c @@ -472,7 +472,7 @@ int32_t crypto_new_keypair(const Random *rng, uint8_t *public_key, uint8_t *secr #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION random_bytes(rng, secret_key, CRYPTO_SECRET_KEY_SIZE); memset(public_key, 0, CRYPTO_PUBLIC_KEY_SIZE); // Make MSAN happy - crypto_scalarmult_curve25519_base(public_key, secret_key); + crypto_derive_public_key(public_key, secret_key); return 0; #else return crypto_box_keypair(public_key, secret_key); @@ -492,13 +492,22 @@ void new_hmac_key(const Random *rng, uint8_t *key) void crypto_hmac(uint8_t auth[CRYPTO_HMAC_SIZE], const uint8_t key[CRYPTO_HMAC_KEY_SIZE], const uint8_t *data, size_t length) { +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + memcpy(auth, key, 16); + memcpy(auth + 16, data, length < 16 ? length : 16); +#else crypto_auth(auth, data, length, key); +#endif } bool crypto_hmac_verify(const uint8_t auth[CRYPTO_HMAC_SIZE], const uint8_t key[CRYPTO_HMAC_KEY_SIZE], const uint8_t *data, size_t length) { +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + return memcmp(auth, key, 16) == 0 && memcmp(auth + 16, data, length < 16 ? length : 16) == 0; +#else return crypto_auth_verify(auth, data, length, key) == 0; +#endif } void crypto_sha256(uint8_t *hash, const uint8_t *data, size_t length) diff --git a/toxcore/network.c b/toxcore/network.c index 35893b73..5c179bfa 100644 --- a/toxcore/network.c +++ b/toxcore/network.c @@ -655,6 +655,122 @@ static uint32_t data_1(uint16_t buflen, const uint8_t *buffer) return data; } +static const char *net_packet_type_name(Net_Packet_Type type) +{ + switch (type) { + case NET_PACKET_PING_REQUEST: + return "PING_REQUEST"; + + case NET_PACKET_PING_RESPONSE: + return "PING_RESPONSE"; + + case NET_PACKET_GET_NODES: + return "GET_NODES"; + + case NET_PACKET_SEND_NODES_IPV6: + return "SEND_NODES_IPV6"; + + case NET_PACKET_COOKIE_REQUEST: + return "COOKIE_REQUEST"; + + case NET_PACKET_COOKIE_RESPONSE: + return "COOKIE_RESPONSE"; + + case NET_PACKET_CRYPTO_HS: + return "CRYPTO_HS"; + + case NET_PACKET_CRYPTO_DATA: + return "CRYPTO_DATA"; + + case NET_PACKET_CRYPTO: + return "CRYPTO"; + + case NET_PACKET_LAN_DISCOVERY: + return "LAN_DISCOVERY"; + + // TODO(Jfreegman): Uncomment these when we merge the rest of new groupchats +// case NET_PACKET_GC_HANDSHAKE: +// return "GC_HANDSHAKE"; + +// case NET_PACKET_GC_LOSSLESS: +// return "GC_LOSSLESS"; + +// case NET_PACKET_GC_LOSSY: +// return "GC_LOSSY"; + + case NET_PACKET_ONION_SEND_INITIAL: + return "ONION_SEND_INITIAL"; + + case NET_PACKET_ONION_SEND_1: + return "ONION_SEND_1"; + + case NET_PACKET_ONION_SEND_2: + return "ONION_SEND_2"; + + case NET_PACKET_ANNOUNCE_REQUEST_OLD: + return "ANNOUNCE_REQUEST_OLD"; + + case NET_PACKET_ANNOUNCE_RESPONSE_OLD: + return "ANNOUNCE_RESPONSE_OLD"; + + case NET_PACKET_ONION_DATA_REQUEST: + return "ONION_DATA_REQUEST"; + + case NET_PACKET_ONION_DATA_RESPONSE: + return "ONION_DATA_RESPONSE"; + + case NET_PACKET_ANNOUNCE_REQUEST: + return "ANNOUNCE_REQUEST"; + + case NET_PACKET_ANNOUNCE_RESPONSE: + return "ANNOUNCE_RESPONSE"; + + case NET_PACKET_ONION_RECV_3: + return "ONION_RECV_3"; + + case NET_PACKET_ONION_RECV_2: + return "ONION_RECV_2"; + + case NET_PACKET_ONION_RECV_1: + return "ONION_RECV_1"; + + case NET_PACKET_FORWARD_REQUEST: + return "FORWARD_REQUEST"; + + case NET_PACKET_FORWARDING: + return "FORWARDING"; + + case NET_PACKET_FORWARD_REPLY: + return "FORWARD_REPLY"; + + case NET_PACKET_DATA_SEARCH_REQUEST: + return "DATA_SEARCH_REQUEST"; + + case NET_PACKET_DATA_SEARCH_RESPONSE: + return "DATA_SEARCH_RESPONSE"; + + case NET_PACKET_DATA_RETRIEVE_REQUEST: + return "DATA_RETRIEVE_REQUEST"; + + case NET_PACKET_DATA_RETRIEVE_RESPONSE: + return "DATA_RETRIEVE_RESPONSE"; + + case NET_PACKET_STORE_ANNOUNCE_REQUEST: + return "STORE_ANNOUNCE_REQUEST"; + + case NET_PACKET_STORE_ANNOUNCE_RESPONSE: + return "STORE_ANNOUNCE_RESPONSE"; + + case BOOTSTRAP_INFO_PACKET_ID: + return "BOOTSTRAP_INFO"; + + case NET_PACKET_MAX: + return "MAX"; + } + + return ""; +} + non_null() static void loglogdata(const Logger *log, const char *message, const uint8_t *buffer, uint16_t buflen, const IP_Port *ip_port, long res) @@ -663,21 +779,24 @@ static void loglogdata(const Logger *log, const char *message, const uint8_t *bu Ip_Ntoa ip_str; const int error = net_error(); char *strerror = net_new_strerror(error); - LOGGER_TRACE(log, "[%2u] %s %3u%c %s:%u (%u: %s) | %08x%08x...%02x", - buffer[0], message, min_u16(buflen, 999), 'E', + LOGGER_TRACE(log, "[%02x = %-20s] %s %3u%c %s:%u (%u: %s) | %08x%08x...%02x", + buffer[0], net_packet_type_name((Net_Packet_Type)buffer[0]), message, + min_u16(buflen, 999), 'E', net_ip_ntoa(&ip_port->ip, &ip_str), net_ntohs(ip_port->port), error, strerror, data_0(buflen, buffer), data_1(buflen, buffer), buffer[buflen - 1]); net_kill_strerror(strerror); } else if ((res > 0) && ((size_t)res <= buflen)) { Ip_Ntoa ip_str; - LOGGER_TRACE(log, "[%2u] %s %3u%c %s:%u (%u: %s) | %08x%08x...%02x", - buffer[0], message, min_u16(res, 999), (size_t)res < buflen ? '<' : '=', + LOGGER_TRACE(log, "[%02x = %-20s] %s %3u%c %s:%u (%u: %s) | %08x%08x...%02x", + buffer[0], net_packet_type_name((Net_Packet_Type)buffer[0]), message, + min_u16(res, 999), (size_t)res < buflen ? '<' : '=', net_ip_ntoa(&ip_port->ip, &ip_str), net_ntohs(ip_port->port), 0, "OK", data_0(buflen, buffer), data_1(buflen, buffer), buffer[buflen - 1]); } else { /* empty or overwrite */ Ip_Ntoa ip_str; - LOGGER_TRACE(log, "[%2u] %s %lu%c%u %s:%u (%u: %s) | %08x%08x...%02x", - buffer[0], message, res, res == 0 ? '!' : '>', buflen, + LOGGER_TRACE(log, "[%02x = %-20s] %s %lu%c%u %s:%u (%u: %s) | %08x%08x...%02x", + buffer[0], net_packet_type_name((Net_Packet_Type)buffer[0]), message, + res, res == 0 ? '!' : '>', buflen, net_ip_ntoa(&ip_port->ip, &ip_str), net_ntohs(ip_port->port), 0, "OK", data_0(buflen, buffer), data_1(buflen, buffer), buffer[buflen - 1]); } @@ -1628,7 +1747,7 @@ bool net_connect(const Logger *log, Socket sock, const IP_Port *ip_port) Ip_Ntoa ip_str; LOGGER_DEBUG(log, "connecting socket %d to %s:%d", - (int)sock.sock, net_ip_ntoa(&ip_port->ip, &ip_str), net_ntohs(ip_port->port)); + (int)sock.sock, net_ip_ntoa(&ip_port->ip, &ip_str), ip_port->port); errno = 0; if (connect(sock.sock, (struct sockaddr *)&addr, addrsize) == -1) { @@ -1649,18 +1768,6 @@ bool net_connect(const Logger *log, Socket sock, const IP_Port *ip_port) int32_t net_getipport(const char *node, IP_Port **res, int tox_type) { -#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION - if ((true)) { - *res = (IP_Port *)calloc(1, sizeof(IP_Port)); - assert(*res != nullptr); - IP_Port *ip_port = *res; - ip_port->ip.ip.v4.uint32 = 0x7F000003; // 127.0.0.3 - ip_port->ip.family = *make_tox_family(AF_INET); - - return 1; - } -#endif - // Try parsing as IP address first. IP_Port parsed = {{{0}}}; @@ -1676,6 +1783,18 @@ int32_t net_getipport(const char *node, IP_Port **res, int tox_type) return 1; } +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + if ((true)) { + *res = (IP_Port *)calloc(1, sizeof(IP_Port)); + assert(*res != nullptr); + IP_Port *ip_port = *res; + ip_port->ip.ip.v4.uint32 = net_htonl(0x7F000003); // 127.0.0.3 + ip_port->ip.family = *make_tox_family(AF_INET); + + return 1; + } +#endif + // It's not an IP address, so now we try doing a DNS lookup. struct addrinfo *infos; const int ret = getaddrinfo(node, nullptr, nullptr, &infos);