Skip to content

Commit

Permalink
test: Add a Net_Crypto fuzz test.
Browse files Browse the repository at this point in the history
  • Loading branch information
iphydf committed Feb 5, 2025
1 parent b4a0e61 commit 4626c2e
Show file tree
Hide file tree
Showing 11 changed files with 214 additions and 28 deletions.
1 change: 1 addition & 0 deletions testing/fuzzing/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
6 changes: 6 additions & 0 deletions testing/fuzzing/bootstrap_fuzz_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
1 change: 0 additions & 1 deletion testing/fuzzing/e2e_fuzz_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

#include <cassert>
#include <cstdio>
#include <fstream>
#include <vector>

#include "../../toxcore/crypto_core.h"
Expand Down
39 changes: 33 additions & 6 deletions testing/fuzzing/fuzz_support.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::size_t>(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]);
Expand Down
38 changes: 35 additions & 3 deletions testing/fuzzing/fuzz_support.hh
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
#ifndef C_TOXCORE_TESTING_FUZZING_FUZZ_SUPPORT_H
#define C_TOXCORE_TESTING_FUZZING_FUZZ_SUPPORT_H

#include <array>
#include <cassert>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <deque>
#include <memory>
#include <unordered_map>
#include <utility>
#include <vector>

#include "../../toxcore/tox.h"
#include "../../toxcore/tox_private.h"

struct Fuzz_Data {
Expand Down Expand Up @@ -256,6 +256,38 @@ struct Null_System : System {
Null_System();
};

template <typename V>
class int_map {
public:
struct iterator {
std::pair<uint16_t, V> 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<uint16_t, V> operator*() const { return pair; }
const std::pair<uint16_t, V> *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<uint16_t>(values.size()), nullptr}}; }

void emplace(uint16_t key, V value) { values[key] = value; }

private:
std::array<V, UINT16_MAX> 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
Expand All @@ -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<uint16_t, Record_System *> bound;
int_map<Record_System *> bound;
};

Global &global_;
Expand Down
32 changes: 15 additions & 17 deletions testing/fuzzing/protodump.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -179,7 +177,7 @@ void dump(std::vector<uint8_t> recording, const char *filename)

void RecordBootstrap(const char *init, const char *bootstrap)
{
Record_System::Global global;
auto global = std::make_unique<Record_System::Global>();

Tox_Options *opts = tox_options_new(nullptr);
assert(opts != nullptr);
Expand All @@ -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<Record_System>(*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);
Expand All @@ -212,9 +210,9 @@ void RecordBootstrap(const char *init, const char *bootstrap)
std::array<uint8_t, TOX_PUBLIC_KEY_SIZE> 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<Record_System>(*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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}

}
Expand Down
6 changes: 5 additions & 1 deletion testing/fuzzing/rebuild_protodump
Original file line number Diff line number Diff line change
@@ -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

Expand Down
17 changes: 17 additions & 0 deletions toxcore/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
8 changes: 8 additions & 0 deletions toxcore/net_crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
#include "net_profile.h"
#include "network.h"

#ifdef __cplusplus
extern "C" {
#endif

/*** Crypto payloads. */

/*** Ranges. */
Expand Down Expand Up @@ -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 */
93 changes: 93 additions & 0 deletions toxcore/net_crypto_fuzz_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#include "net_crypto.h"

#include <cassert>
#include <cstring>
#include <functional>
#include <memory>
#include <optional>

#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<std::tuple<IP_Port, uint8_t>> 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(logger_new(sys.mem.get()), logger_kill);
if (logger == nullptr) {
return;
}

const Ptr<Networking_Core> 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, std::function<void(Mono_Time *)>> mono_time(
mono_time_new(
sys.mem.get(), [](void *user_data) { return *static_cast<uint64_t *>(user_data); },
&sys.clock),
[mem = sys.mem.get()](Mono_Time *ptr) { mono_time_free(mem, ptr); });
if (mono_time == nullptr) {
return;
}

const Ptr<DHT> 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> 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<TestNetCrypto>(data, size);
return 0;
}
Loading

0 comments on commit 4626c2e

Please sign in to comment.