Merge commit '565efa4f39650d09c05f3895f9a2b16d0f5e7bad'
Some checks failed
ContinuousDelivery / linux-ubuntu (push) Has been cancelled
ContinuousDelivery / linux-debian12 (push) Has been cancelled
ContinuousDelivery / android (map[ndk_abi:arm64-v8a vcpkg_toolkit:arm64-android-23]) (push) Has been cancelled
ContinuousDelivery / android (map[ndk_abi:armeabi-v7a vcpkg_toolkit:arm-neon-android-23]) (push) Has been cancelled
ContinuousDelivery / android (map[ndk_abi:x86_64 vcpkg_toolkit:x64-android-23]) (push) Has been cancelled
ContinuousDelivery / windows (windows-2022, ) (push) Has been cancelled
ContinuousDelivery / windows (windows-2022, asan) (push) Has been cancelled
ContinuousIntegration / on ubuntu-24.04-arm (push) Has been cancelled
ContinuousIntegration / asan on ubuntu-24.04-arm (push) Has been cancelled
ContinuousIntegration / on ubuntu-latest (push) Has been cancelled
ContinuousIntegration / asan on ubuntu-latest (push) Has been cancelled
ContinuousIntegration / linux-debian12 (push) Has been cancelled
ContinuousIntegration / android (map[ndk_abi:arm64-v8a vcpkg_toolkit:arm64-android-23]) (push) Has been cancelled
ContinuousIntegration / android (map[ndk_abi:armeabi-v7a vcpkg_toolkit:arm-neon-android-23]) (push) Has been cancelled
ContinuousIntegration / android (map[ndk_abi:x86_64 vcpkg_toolkit:x64-android-23]) (push) Has been cancelled
ContinuousIntegration / macos (push) Has been cancelled
ContinuousIntegration / windows (push) Has been cancelled
ContinuousDelivery / dumpsyms (push) Has been cancelled
ContinuousDelivery / release (push) Has been cancelled

This commit is contained in:
Green Sky
2026-01-11 14:42:31 +01:00
328 changed files with 19057 additions and 13982 deletions

View File

@@ -1,28 +1,6 @@
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
load("@rules_cc//cc:defs.bzl", "cc_binary")
load("@rules_fuzzing//fuzzing:cc_defs.bzl", "cc_fuzz_test")
cc_library(
name = "fuzz_support",
srcs = [
"func_conversion.hh",
"fuzz_support.cc",
],
hdrs = ["fuzz_support.hh"],
visibility = ["//c-toxcore:__subpackages__"],
deps = [
"//c-toxcore/toxcore:crypto_core",
"//c-toxcore/toxcore:network",
"//c-toxcore/toxcore:tox",
],
)
cc_library(
name = "fuzz_tox",
hdrs = ["fuzz_tox.hh"],
visibility = ["//c-toxcore:__subpackages__"],
deps = [":fuzz_support"],
)
cc_fuzz_test(
name = "bootstrap_fuzz_test",
size = "small",
@@ -30,8 +8,7 @@ cc_fuzz_test(
copts = ["-UNDEBUG"],
corpus = ["//tools/toktok-fuzzer/corpus:bootstrap_fuzz_test"],
deps = [
":fuzz_support",
":fuzz_tox",
"//c-toxcore/testing/support",
"//c-toxcore/toxcore:tox",
"//c-toxcore/toxcore:tox_dispatch",
"//c-toxcore/toxcore:tox_events",
@@ -46,8 +23,7 @@ cc_fuzz_test(
corpus = ["//tools/toktok-fuzzer/corpus:e2e_fuzz_test"],
data = ["//tools/toktok-fuzzer/init:e2e_fuzz_test.dat"],
deps = [
":fuzz_support",
":fuzz_tox",
"//c-toxcore/testing/support",
"//c-toxcore/toxcore:crypto_core",
"//c-toxcore/toxcore:tox",
"//c-toxcore/toxcore:tox_dispatch",
@@ -62,7 +38,7 @@ cc_fuzz_test(
copts = ["-UNDEBUG"],
corpus = ["//tools/toktok-fuzzer/corpus:toxsave_fuzz_test"],
deps = [
":fuzz_support",
"//c-toxcore/testing/support",
"//c-toxcore/toxcore:tox",
],
)
@@ -72,7 +48,7 @@ cc_binary(
srcs = ["protodump.cc"],
copts = ["-UNDEBUG"],
deps = [
":fuzz_support",
"//c-toxcore/testing/support",
"//c-toxcore/toxcore:tox",
"//c-toxcore/toxcore:tox_dispatch",
"//c-toxcore/toxcore:tox_events",
@@ -90,38 +66,3 @@ genrule(
tags = ["manual"],
tools = [":protodump"],
)
# bazel test --config=asan-libfuzzer //c-toxcore/testing/fuzzing:protodump_reduce_test
cc_test(
name = "protodump_reduce_test",
size = "small",
srcs = ["protodump_reduce.cc"],
args = ["$(location :e2e_fuzz_test_init.dat)"],
copts = ["-UNDEBUG"],
data = [":e2e_fuzz_test_init.dat"],
tags = ["manual"],
deps = [
":fuzz_support",
":fuzz_tox",
"//c-toxcore/toxcore:crypto_core",
"//c-toxcore/toxcore:tox",
"//c-toxcore/toxcore:tox_dispatch",
"//c-toxcore/toxcore:tox_events",
"@rules_fuzzing//fuzzing:cc_engine",
],
)
cc_fuzz_test(
name = "protodump_reduce",
size = "small",
srcs = ["protodump_reduce.cc"],
copts = ["-UNDEBUG"],
deps = [
":fuzz_support",
":fuzz_tox",
"//c-toxcore/toxcore:crypto_core",
"//c-toxcore/toxcore:tox",
"//c-toxcore/toxcore:tox_dispatch",
"//c-toxcore/toxcore:tox_events",
],
)

View File

@@ -1,5 +1,4 @@
# Override network and random functions
add_library(fuzz_support func_conversion.hh fuzz_support.cc fuzz_support.hh)
set(LIBFUZZER_LINKER_FLAGS)
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
@@ -12,9 +11,9 @@ function(fuzz_test target source_dir)
set(CORPUS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/toktok-fuzzer/corpus/${target}_fuzz_test)
file(GLOB CORPUS "${CORPUS_DIR}/*")
add_executable(${target}_fuzz_test ${source_dir}/${target}_fuzz_test.cc)
target_link_libraries(${target}_fuzz_test PRIVATE fuzz_support test_util toxcore_fuzz ${LIBFUZZER_LINKER_FLAGS})
target_link_libraries(${target}_fuzz_test PRIVATE support test_util toxcore_fuzz ${LIBFUZZER_LINKER_FLAGS})
if(CORPUS)
add_test(NAME ${target}_fuzz COMMAND ${CROSSCOMPILING_EMULATOR} ${target}_fuzz_test -max_total_time=10 ${CORPUS})
add_test(NAME ${target}_fuzz COMMAND ${target}_fuzz_test -max_total_time=10 ${CORPUS})
set_property(TEST ${target}_fuzz PROPERTY ENVIRONMENT "LLVM_PROFILE_FILE=${target}.profraw;srcdir=${CMAKE_CURRENT_SOURCE_DIR}")
endif()
endfunction()

View File

@@ -1,14 +1,26 @@
#include <cassert>
#include <cstdio>
#include <functional>
#include <memory>
#include "../../toxcore/tox.h"
#include "../../toxcore/tox_dispatch.h"
#include "../../toxcore/tox_events.h"
#include "fuzz_support.hh"
#include "fuzz_tox.hh"
#include "../support/public/fuzz_data.hh"
#include "../support/public/fuzz_helpers.hh"
#include "../support/public/simulated_environment.hh"
namespace {
using tox::test::configure_fuzz_memory_source;
using tox::test::configure_fuzz_packet_source;
using tox::test::FakeClock;
using tox::test::Fuzz_Data;
using tox::test::SimulatedEnvironment;
template <typename T>
using Ptr = std::unique_ptr<T, void (*)(T *)>;
void setup_callbacks(Tox_Dispatch *dispatch)
{
tox_events_callback_conference_connected(
@@ -97,10 +109,15 @@ void setup_callbacks(Tox_Dispatch *dispatch)
void TestBootstrap(Fuzz_Data &input)
{
// Null system for regularly working memory allocations needed in
// tox_events_equal.
Null_System null_sys;
Fuzz_System sys(input);
SimulatedEnvironment env;
env.fake_clock().advance(1000000000); // Match legacy behavior
auto node = env.create_node(33445);
configure_fuzz_memory_source(env.fake_memory(), input);
configure_fuzz_packet_source(*node->endpoint, input);
// Create a second null system for tox_events_equal check
SimulatedEnvironment null_env;
auto null_node = null_env.create_node(0); // Port 0 (unbound/irrelevant)
Ptr<Tox_Options> opts(tox_options_new(nullptr), tox_options_free);
assert(opts != nullptr);
@@ -110,8 +127,26 @@ void TestBootstrap(Fuzz_Data &input)
const char *message, void *user_data) {
// Log to stdout.
if (Fuzz_Data::FUZZ_DEBUG) {
std::printf("[tox1] %c %s:%u(%s): %s\n", tox_log_level_name(level), file, line,
func, message);
// Approximate level name mapping
char level_char = '?';
switch (level) {
case TOX_LOG_LEVEL_TRACE:
level_char = 'T';
break;
case TOX_LOG_LEVEL_DEBUG:
level_char = 'D';
break;
case TOX_LOG_LEVEL_INFO:
level_char = 'I';
break;
case TOX_LOG_LEVEL_WARNING:
level_char = 'W';
break;
case TOX_LOG_LEVEL_ERROR:
level_char = 'E';
break;
}
std::printf("[tox1] %c %s:%u(%s): %s\n", level_char, file, line, func, message);
}
});
@@ -134,7 +169,7 @@ void TestBootstrap(Fuzz_Data &input)
}
Tox_Options_Testing tox_options_testing;
tox_options_testing.operating_system = sys.sys.get();
tox_options_testing.operating_system = &node->system;
Tox_Err_New error_new;
Tox_Err_New_Testing error_new_testing;
@@ -165,15 +200,17 @@ void TestBootstrap(Fuzz_Data &input)
while (!input.empty()) {
Tox_Err_Events_Iterate error_iterate;
Tox_Events *events = tox_events_iterate(tox, true, &error_iterate);
assert(tox_events_equal(null_sys.sys.get(), events, events));
assert(tox_events_equal(&null_node->system, events, events));
tox_dispatch_invoke(dispatch, events, tox);
tox_events_free(events);
// 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());
env.advance_time(200);
// If no input was consumed, stop.
if (input_size == input.size()) {
break;
}
input_size = input.size();
}

View File

@@ -5,17 +5,29 @@
#include <cassert>
#include <cstdio>
#include <functional>
#include <vector>
#include "../../toxcore/crypto_core.h"
#include "../../toxcore/tox.h"
#include "../../toxcore/tox_dispatch.h"
#include "../../toxcore/tox_events.h"
#include "fuzz_support.hh"
#include "fuzz_tox.hh"
#include "../support/public/fuzz_data.hh"
#include "../support/public/fuzz_helpers.hh"
#include "../support/public/simulated_environment.hh"
namespace {
using tox::test::configure_fuzz_memory_source;
using tox::test::configure_fuzz_packet_source;
using tox::test::configure_fuzz_random_source;
using tox::test::FakeClock;
using tox::test::Fuzz_Data;
using tox::test::SimulatedEnvironment;
template <typename T>
using Ptr = std::unique_ptr<T, void (*)(T *)>;
void setup_callbacks(Tox_Dispatch *dispatch)
{
tox_events_callback_conference_connected(
@@ -131,9 +143,16 @@ void setup_callbacks(Tox_Dispatch *dispatch)
void TestEndToEnd(Fuzz_Data &input)
{
Fuzz_System sys(input);
// Used for places where we want all allocations to succeed.
Null_System null_sys;
SimulatedEnvironment env;
env.fake_clock().advance(1000000000); // Match legacy behavior
auto node = env.create_node(33445);
configure_fuzz_memory_source(env.fake_memory(), input);
configure_fuzz_packet_source(*node->endpoint, input);
configure_fuzz_random_source(env.fake_random(), input);
// Null system replacement for event comparison
SimulatedEnvironment null_env;
auto null_node = null_env.create_node(0);
Ptr<Tox_Options> opts(tox_options_new(nullptr), tox_options_free);
assert(opts != nullptr);
@@ -144,13 +163,31 @@ void TestEndToEnd(Fuzz_Data &input)
const char *message, void *user_data) {
// Log to stdout.
if (Fuzz_Data::FUZZ_DEBUG) {
std::printf("[tox1] %c %s:%u(%s): %s\n", tox_log_level_name(level), file, line,
func, message);
// Approximate level name mapping
char level_char = '?';
switch (level) {
case TOX_LOG_LEVEL_TRACE:
level_char = 'T';
break;
case TOX_LOG_LEVEL_DEBUG:
level_char = 'D';
break;
case TOX_LOG_LEVEL_INFO:
level_char = 'I';
break;
case TOX_LOG_LEVEL_WARNING:
level_char = 'W';
break;
case TOX_LOG_LEVEL_ERROR:
level_char = 'E';
break;
}
std::printf("[tox1] %c %s:%u(%s): %s\n", level_char, file, line, func, message);
}
});
Tox_Options_Testing tox_options_testing;
tox_options_testing.operating_system = sys.sys.get();
tox_options_testing.operating_system = &node->system;
Tox_Err_New error_new;
Tox_Err_New_Testing error_new_testing;
@@ -171,15 +208,18 @@ void TestEndToEnd(Fuzz_Data &input)
assert(dispatch != nullptr);
setup_callbacks(dispatch);
// MIN_ITERATION_INTERVAL = 20
const uint8_t MIN_ITERATION_INTERVAL = 20;
while (!input.empty()) {
Tox_Err_Events_Iterate error_iterate;
Tox_Events *events = tox_events_iterate(tox, true, &error_iterate);
assert(tox_events_equal(null_sys.sys.get(), events, events));
assert(tox_events_equal(&null_node->system, events, events));
tox_dispatch_invoke(dispatch, events, tox);
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()));
uint32_t rand_val = env.fake_random().uniform(256);
env.fake_clock().advance(std::max<uint64_t>(MIN_ITERATION_INTERVAL, rand_val));
}
tox_dispatch_free(dispatch);

View File

@@ -1,69 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later
* Copyright © 2022-2025 The TokTok team.
*/
#ifndef C_TOXCORE_TESTING_FUZZING_FUNC_CONVERSION_H
#define C_TOXCORE_TESTING_FUZZING_FUNC_CONVERSION_H
namespace detail {
template <typename F, F f>
struct func_conversion {
private:
template <typename R, typename... Args>
using func_pointer = R (*)(Args...);
template <typename From>
struct static_caster {
From obj;
template <typename To>
operator To() const
{
return static_cast<To>(obj);
}
};
public:
template <typename R, typename Arg, typename... Args>
constexpr operator func_pointer<R, Arg, Args...>()
{
return [](Arg obj, auto... args) { return f(static_caster<Arg>{obj}, args...); };
}
};
template <typename F>
struct make_funptr;
template <typename T, typename R, typename... Args>
struct make_funptr<R (T::*)(Args...) const> {
using type = R (*)(Args...);
};
/** @brief Turn a memfunptr type into a plain funptr type.
*
* Not needed in C++20, because we can pass the lambda itself as template
* argument, but in C++17, we need to do an early conversion.
*/
template <typename F>
using make_funptr_t = typename make_funptr<F>::type;
}
/** @brief Turn a C++ lambda into a C function pointer with `void*` param.
*
* Takes a lambda function with any pointer type as first parameter and turns it
* into a C function pointer with `void*` as the first parameter. Internally, it
* `static_cast`s that `void*` to the lambda's parameter type, avoiding a bunch
* of casts inside the lambdas.
*
* This works on any type `T` that can be `static_cast` to `U`, not just `void*`
* to `U*`, but the common case for C callbacks is `void*`.
*/
template <typename F>
static constexpr auto operator!(F f)
{
return detail::func_conversion<detail::make_funptr_t<decltype(&F::operator())>, f>{};
}
#endif // C_TOXCORE_TESTING_FUZZING_FUNC_CONVERSION_H

View File

@@ -1,479 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later
* Copyright © 2021-2025 The TokTok team.
*/
#include "fuzz_support.hh"
#ifdef _WIN32
#include <winsock2.h>
// Comment line here to avoid reordering by source code formatters.
#include <windows.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#include <sys/socket.h>
#endif
#include <algorithm>
#include <cassert>
#include <cerrno>
#include <climits>
#include <cstdio>
#include <cstring>
#include <memory>
#include "../../toxcore/crypto_core.h"
#include "../../toxcore/network.h"
#include "../../toxcore/tox_memory_impl.h"
#include "../../toxcore/tox_private.h"
#include "../../toxcore/tox_random_impl.h"
#include "func_conversion.hh"
// TODO(iphydf): Put this somewhere shared.
struct Network_Addr {
struct sockaddr_storage addr;
size_t size;
};
System::System(std::unique_ptr<Tox_System> in_sys, std::unique_ptr<Tox_Memory> in_mem,
std::unique_ptr<Network> in_ns, std::unique_ptr<Tox_Random> in_rng)
: sys(std::move(in_sys))
, mem(std::move(in_mem))
, ns(std::move(in_ns))
, rng(std::move(in_rng))
{
}
System::System(System &&) = default;
System::~System() { }
static int recv_common(Fuzz_Data &input, uint8_t *buf, size_t buf_len)
{
if (input.size() < 2) {
errno = ENOMEM;
return -1;
}
CONSUME_OR_ABORT(const uint8_t *fuzz_len_bytes, input, 2);
const std::size_t fuzz_len = (fuzz_len_bytes[0] << 8) | fuzz_len_bytes[1];
if (fuzz_len == 0xffff) {
errno = EWOULDBLOCK;
if (Fuzz_Data::FUZZ_DEBUG) {
std::printf("recvfrom: no data for tox1\n");
}
return -1;
}
if (Fuzz_Data::FUZZ_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()));
CONSUME_OR_ABORT(const uint8_t *data, input, res);
std::copy(data, data + res, buf);
return res;
}
static void *report_alloc(const char *name, const char *func, std::size_t size, void *ptr)
{
if (Fuzz_Data::FUZZ_DEBUG) {
printf("%s: %s(%zu): %s\n", name, func, size, ptr == nullptr ? "false" : "true");
}
return ptr;
}
template <typename F, F Func, typename... Args>
static void *alloc_common(const char *func, std::size_t size, Fuzz_Data &data, Args... args)
{
CONSUME1_OR_RETURN_VAL(
const bool, want_alloc, data, report_alloc("tox1", func, size, Func(args...)));
if (!want_alloc) {
return nullptr;
}
return report_alloc("tox1", func, size, Func(args...));
}
static constexpr Tox_Memory_Funcs fuzz_memory_funcs = {
/* .malloc = */
![](Fuzz_System *self, uint32_t size) {
return alloc_common<decltype(std::malloc), std::malloc>("malloc", size, self->data, size);
},
/* .realloc = */
![](Fuzz_System *self, void *ptr, uint32_t size) {
return alloc_common<decltype(std::realloc), std::realloc>(
"realloc", size, self->data, ptr, size);
},
/* .dealloc = */
![](Fuzz_System *self, void *ptr) { std::free(ptr); },
};
static constexpr Network_Funcs fuzz_network_funcs = {
/* .close = */ ![](Fuzz_System *self, Socket sock) { return 0; },
/* .accept = */ ![](Fuzz_System *self, Socket sock) { return Socket{1337}; },
/* .bind = */ ![](Fuzz_System *self, Socket sock, const Network_Addr *addr) { return 0; },
/* .listen = */ ![](Fuzz_System *self, Socket sock, int backlog) { return 0; },
/* .connect = */ ![](Fuzz_System *self, Socket sock, const Network_Addr *addr) { return 0; },
/* .recvbuf = */
![](Fuzz_System *self, Socket sock) {
assert(sock.value == 42 || sock.value == 1337);
const size_t count = random_u16(self->rng.get());
return static_cast<int>(std::min(count, self->data.size()));
},
/* .recv = */
![](Fuzz_System *self, Socket sock, uint8_t *buf, size_t len) {
assert(sock.value == 42 || sock.value == 1337);
// Receive data from the fuzzer.
return recv_common(self->data, buf, len);
},
/* .recvfrom = */
![](Fuzz_System *self, Socket sock, uint8_t *buf, size_t len, Network_Addr *addr) {
assert(sock.value == 42 || sock.value == 1337);
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<sockaddr_in *>(&addr->addr);
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 = */
![](Fuzz_System *self, Socket sock, const uint8_t *buf, size_t len) {
assert(sock.value == 42 || sock.value == 1337);
// Always succeed.
return static_cast<int>(len);
},
/* .sendto = */
![](Fuzz_System *self, Socket sock, const uint8_t *buf, size_t len, const Network_Addr *addr) {
assert(sock.value == 42 || sock.value == 1337);
// Always succeed.
return static_cast<int>(len);
},
/* .socket = */ ![](Fuzz_System *self, int domain, int type, int proto) { return Socket{42}; },
/* .socket_nonblock = */ ![](Fuzz_System *self, Socket sock, bool nonblock) { return 0; },
/* .getsockopt = */
![](Fuzz_System *self, Socket sock, int level, int optname, void *optval, size_t *optlen) {
std::memset(optval, 0, *optlen);
return 0;
},
/* .setsockopt = */
![](Fuzz_System *self, Socket sock, int level, int optname, const void *optval, size_t optlen) {
return 0;
},
};
static constexpr Tox_Random_Funcs fuzz_random_funcs = {
/* .bytes_callback = */
![](Fuzz_System *self, uint8_t *bytes, size_t length) {
// 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]);
} else {
std::printf("rng: %02x..%02x[%zu]\n", bytes[0], bytes[length - 1], length);
}
}
},
/* .uniform_callback = */
![](Fuzz_System *self, uint32_t upper_bound) {
uint32_t randnum = 0;
if (upper_bound > 0) {
self->rng->funcs->bytes_callback(
self, reinterpret_cast<uint8_t *>(&randnum), sizeof(randnum));
randnum %= upper_bound;
}
return randnum;
},
};
Fuzz_System::Fuzz_System(Fuzz_Data &input)
: System{
std::make_unique<Tox_System>(),
std::make_unique<Tox_Memory>(Tox_Memory{&fuzz_memory_funcs, this}),
std::make_unique<Network>(Network{&fuzz_network_funcs, this}),
std::make_unique<Tox_Random>(Tox_Random{&fuzz_random_funcs, this}),
}
, data(input)
{
sys->mono_time_callback = [](void *self) { return static_cast<Fuzz_System *>(self)->clock; };
sys->mono_time_user_data = this;
sys->mem = mem.get();
sys->ns = ns.get();
sys->rng = rng.get();
}
static constexpr Tox_Memory_Funcs null_memory_funcs = {
/* .malloc = */
![](Null_System *self, uint32_t size) { return std::malloc(size); },
/* .realloc = */
![](Null_System *self, void *ptr, uint32_t size) { return std::realloc(ptr, size); },
/* .dealloc = */
![](Null_System *self, void *ptr) { std::free(ptr); },
};
static constexpr Network_Funcs null_network_funcs = {
/* .close = */ ![](Null_System *self, Socket sock) { return 0; },
/* .accept = */ ![](Null_System *self, Socket sock) { return Socket{1337}; },
/* .bind = */ ![](Null_System *self, Socket sock, const Network_Addr *addr) { return 0; },
/* .listen = */ ![](Null_System *self, Socket sock, int backlog) { return 0; },
/* .connect = */ ![](Null_System *self, Socket sock, const Network_Addr *addr) { return 0; },
/* .recvbuf = */ ![](Null_System *self, Socket sock) { return 0; },
/* .recv = */
![](Null_System *self, Socket sock, uint8_t *buf, size_t len) {
// Always fail.
errno = ENOMEM;
return -1;
},
/* .recvfrom = */
![](Null_System *self, Socket sock, uint8_t *buf, size_t len, Network_Addr *addr) {
// Always fail.
errno = ENOMEM;
return -1;
},
/* .send = */
![](Null_System *self, Socket sock, const uint8_t *buf, size_t len) {
// Always succeed.
return static_cast<int>(len);
},
/* .sendto = */
![](Null_System *self, Socket sock, const uint8_t *buf, size_t len, const Network_Addr *addr) {
// Always succeed.
return static_cast<int>(len);
},
/* .socket = */ ![](Null_System *self, int domain, int type, int proto) { return Socket{42}; },
/* .socket_nonblock = */ ![](Null_System *self, Socket sock, bool nonblock) { return 0; },
/* .getsockopt = */
![](Null_System *self, Socket sock, int level, int optname, void *optval, size_t *optlen) {
std::memset(optval, 0, *optlen);
return 0;
},
/* .setsockopt = */
![](Null_System *self, Socket sock, int level, int optname, const void *optval, size_t optlen) {
return 0;
},
};
static uint64_t simple_rng(uint64_t &seed)
{
// https://nuclear.llnl.gov/CNP/rng/rngman/node4.html
seed = 2862933555777941757LL * seed + 3037000493LL;
return seed;
}
static constexpr Tox_Random_Funcs null_random_funcs = {
/* .bytes_callback = */
![](Null_System *self, uint8_t *bytes, size_t length) {
for (size_t i = 0; i < length; ++i) {
bytes[i] = simple_rng(self->seed) & 0xff;
}
},
/* .uniform_callback = */
![](Null_System *self, uint32_t upper_bound) {
return static_cast<uint32_t>(simple_rng(self->seed)) % upper_bound;
},
};
Null_System::Null_System()
: System{
std::make_unique<Tox_System>(),
std::make_unique<Tox_Memory>(Tox_Memory{&null_memory_funcs, this}),
std::make_unique<Network>(Network{&null_network_funcs, this}),
std::make_unique<Tox_Random>(Tox_Random{&null_random_funcs, this}),
}
{
sys->mono_time_callback = [](void *self) { return static_cast<Null_System *>(self)->clock; };
sys->mono_time_user_data = this;
sys->mem = mem.get();
sys->ns = ns.get();
sys->rng = rng.get();
}
static uint16_t get_port(const Network_Addr *addr)
{
if (addr->addr.ss_family == AF_INET6) {
return reinterpret_cast<const sockaddr_in6 *>(&addr->addr)->sin6_port;
} else {
assert(addr->addr.ss_family == AF_INET);
return reinterpret_cast<const sockaddr_in *>(&addr->addr)->sin_port;
}
}
static constexpr Tox_Memory_Funcs record_memory_funcs = {
/* .malloc = */
![](Record_System *self, uint32_t size) {
self->push(true);
return report_alloc(self->name_, "malloc", size, std::malloc(size));
},
/* .realloc = */
![](Record_System *self, void *ptr, uint32_t size) {
self->push(true);
return report_alloc(self->name_, "realloc", size, std::realloc(ptr, size));
},
/* .dealloc = */
![](Record_System *self, void *ptr) { std::free(ptr); },
};
static constexpr Network_Funcs record_network_funcs = {
/* .close = */ ![](Record_System *self, Socket sock) { return 0; },
/* .accept = */ ![](Record_System *self, Socket sock) { return Socket{2}; },
/* .bind = */
![](Record_System *self, Socket 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, Socket sock, int backlog) { return 0; },
/* .connect = */ ![](Record_System *self, Socket sock, const Network_Addr *addr) { return 0; },
/* .recvbuf = */ ![](Record_System *self, Socket sock) { return 0; },
/* .recv = */
![](Record_System *self, Socket sock, uint8_t *buf, size_t len) {
// Always fail.
errno = ENOMEM;
return -1;
},
/* .recvfrom = */
![](Record_System *self, Socket sock, uint8_t *buf, size_t len, Network_Addr *addr) {
assert(sock.value == 42);
if (self->recvq.empty()) {
self->push("\xff\xff");
errno = EWOULDBLOCK;
if (Fuzz_Data::FUZZ_DEBUG) {
std::printf("%s: recvfrom: no data\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<sockaddr_in *>(&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->push(uint8_t(recvlen >> 8));
self->push(uint8_t(recvlen & 0xff));
if (Fuzz_Data::FUZZ_DEBUG) {
std::printf("%s: recvfrom: %zu (%02x, %02x)\n", self->name_, recvlen,
self->recording().end()[-2], self->recording().end()[-1]);
}
self->push(buf, recvlen);
return static_cast<int>(recvlen);
},
/* .send = */
![](Record_System *self, Socket sock, const uint8_t *buf, size_t len) {
// Always succeed.
return static_cast<int>(len);
},
/* .sendto = */
![](Record_System *self, Socket sock, const uint8_t *buf, size_t len,
const Network_Addr *addr) {
assert(sock.value == 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<int>(len);
},
/* .socket = */
![](Record_System *self, int domain, int type, int proto) { return Socket{42}; },
/* .socket_nonblock = */ ![](Record_System *self, Socket sock, bool nonblock) { return 0; },
/* .getsockopt = */
![](Record_System *self, Socket sock, int level, int optname, void *optval, size_t *optlen) {
std::memset(optval, 0, *optlen);
return 0;
},
/* .setsockopt = */
![](Record_System *self, Socket sock, int level, int optname, const void *optval,
size_t optlen) { return 0; },
};
static constexpr Tox_Random_Funcs record_random_funcs = {
/* .bytes_callback = */
![](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->push(bytes[i]);
}
if (Fuzz_Data::FUZZ_DEBUG) {
std::printf(
"%s: rng: %02x..%02x[%zu]\n", self->name_, bytes[0], bytes[length - 1], length);
}
},
/* .uniform_callback = */
fuzz_random_funcs.uniform_callback,
};
Record_System::Record_System(Global &global, uint64_t seed, const char *name)
: System{
std::make_unique<Tox_System>(),
std::make_unique<Tox_Memory>(Tox_Memory{&record_memory_funcs, this}),
std::make_unique<Network>(Network{&record_network_funcs, this}),
std::make_unique<Random>(Random{&record_random_funcs, this}),
}
, global_(global)
, seed_(seed)
, name_(name)
{
sys->mono_time_callback = [](void *self) { return static_cast<Record_System *>(self)->clock; };
sys->mono_time_user_data = this;
sys->mem = mem.get();
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<uint8_t>{buf, buf + len});
}

View File

@@ -1,407 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later
* Copyright © 2021-2025 The TokTok team.
*/
#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 <utility>
#include <vector>
#include "../../toxcore/tox_private.h"
struct Fuzz_Data {
static constexpr bool FUZZ_DEBUG = false;
static constexpr std::size_t TRACE_TRAP = -1; // 579;
private:
const uint8_t *data_;
const uint8_t *base_;
std::size_t size_;
public:
Fuzz_Data(const uint8_t *input_data, std::size_t input_size)
: data_(input_data)
, base_(input_data)
, size_(input_size)
{
}
Fuzz_Data &operator=(const Fuzz_Data &rhs) = delete;
Fuzz_Data(const Fuzz_Data &rhs) = delete;
struct Consumer {
const char *func;
Fuzz_Data &fd;
operator bool()
{
// Special case because memcpy causes UB for bool (which can't be
// anything other than 0 or 1).
const bool val = fd.data_[0];
if (FUZZ_DEBUG) {
std::printf("consume@%zu(%s): bool %s\n", fd.pos(), func, val ? "true" : "false");
}
++fd.data_;
--fd.size_;
return val;
}
template <typename T>
operator T()
{
const uint8_t *bytes = fd.consume(func, sizeof(T));
T val;
std::memcpy(&val, bytes, sizeof(T));
return val;
}
};
Consumer consume1(const char *func) { return Consumer{func, *this}; }
std::size_t size() const { return size_; }
std::size_t pos() const { return data_ - base_; }
const uint8_t *data() const { return data_; }
bool empty() const { return size_ == 0; }
const uint8_t *consume(const char *func, std::size_t count)
{
const uint8_t *val = data_;
if (FUZZ_DEBUG) {
if (pos() == TRACE_TRAP) {
__asm__("int $3");
}
if (count == 1) {
std::printf("consume@%zu(%s): %d (0x%02x)\n", pos(), func, val[0], val[0]);
} else if (count != 0) {
std::printf("consume@%zu(%s): %02x..%02x[%zu]\n", pos(), func, val[0],
val[count - 1], count);
}
}
data_ += count;
size_ -= count;
return val;
}
};
/** @brief Consumes 1 byte of the fuzzer input or returns if no data available.
*
* This advances the fuzzer input data by 1 byte and consumes that byte in the
* declaration.
*
* @example
* @code
* CONSUME1_OR_RETURN(const uint8_t, one_byte, input);
* @endcode
*/
#define CONSUME1_OR_RETURN(TYPE, NAME, INPUT) \
if (INPUT.size() < sizeof(TYPE)) { \
return; \
} \
TYPE NAME = INPUT.consume1(__func__)
/** @brief Consumes 1 byte of the fuzzer input or returns a value if no data
* available.
*
* This advances the fuzzer input data by 1 byte and consumes that byte in the
* declaration.
*
* @example
* @code
* CONSUME1_OR_RETURN_VAL(const uint8_t one_byte, input, nullptr);
* @endcode
*/
#define CONSUME1_OR_RETURN_VAL(TYPE, NAME, INPUT, VAL) \
if (INPUT.size() < sizeof(TYPE)) { \
return VAL; \
} \
TYPE NAME = INPUT.consume1(__func__)
/** @brief Consumes SIZE bytes of the fuzzer input or returns if not enough data available.
*
* This advances the fuzzer input data by SIZE byte and consumes those bytes in
* the declaration. If less than SIZE bytes are available in the fuzzer input,
* this macro returns from the enclosing function.
*
* @example
* @code
* CONSUME_OR_RETURN(const uint8_t *ten_bytes, input, 10);
* @endcode
*/
#define CONSUME_OR_RETURN(DECL, INPUT, SIZE) \
if (INPUT.size() < SIZE) { \
return; \
} \
DECL = INPUT.consume(__func__, SIZE)
#define CONSUME_OR_RETURN_VAL(DECL, INPUT, SIZE, VAL) \
if (INPUT.size() < SIZE) { \
return VAL; \
} \
DECL = INPUT.consume(__func__, SIZE)
#define CONSUME_OR_ABORT(DECL, INPUT, SIZE) \
if (INPUT.size() < SIZE) { \
abort(); \
} \
DECL = INPUT.consume(__func__, SIZE)
using Fuzz_Target = void (*)(Fuzz_Data &input);
template <Fuzz_Target... Args>
struct Fuzz_Target_Selector;
template <Fuzz_Target Arg, Fuzz_Target... Args>
struct Fuzz_Target_Selector<Arg, Args...> {
static void select(uint8_t selector, Fuzz_Data &input)
{
if (selector == sizeof...(Args)) {
return Arg(input);
}
return Fuzz_Target_Selector<Args...>::select(selector, input);
}
};
template <>
struct Fuzz_Target_Selector<> {
static void select(uint8_t selector, Fuzz_Data &input)
{
// The selector selected no function, so we do nothing and rely on the
// fuzzer to come up with a better selector.
}
};
template <Fuzz_Target... Args>
void fuzz_select_target(const uint8_t *data, std::size_t size)
{
Fuzz_Data input{data, size};
CONSUME1_OR_RETURN(const uint8_t, selector, input);
return Fuzz_Target_Selector<Args...>::select(selector, input);
}
struct Tox_Memory;
struct Network;
struct Tox_Random;
struct 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).
*
* We make it a nice large round number so we can recognise it when debugging.
*/
uint64_t clock = 1000000000;
std::unique_ptr<Tox_System> sys;
std::unique_ptr<Tox_Memory> mem;
std::unique_ptr<Network> ns;
std::unique_ptr<Tox_Random> rng;
System(std::unique_ptr<Tox_System> sys, std::unique_ptr<Tox_Memory> mem,
std::unique_ptr<Network> ns, std::unique_ptr<Tox_Random> rng);
System(System &&);
// Not inline because sizeof of the above 2 structs is not known everywhere.
~System();
/**
* 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 : System {
Fuzz_Data &data;
explicit Fuzz_System(Fuzz_Data &input);
};
/**
* A Tox_System implementation that consumes no fuzzer input but still has a
* working and deterministic RNG. Network receive functions always fail, send
* always succeeds.
*/
struct Null_System : System {
uint64_t seed = 4; // chosen by fair dice roll. guaranteed to be random.
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
* 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 {
static constexpr bool FUZZ_DEBUG = Fuzz_Data::FUZZ_DEBUG;
/** @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.
*/
int_map<Record_System *> bound;
};
Global &global_;
uint64_t seed_; //!< Current PRNG state.
const char *name_; //!< Tox system name ("tox1"/"tox2") for logging.
std::deque<std::pair<uint16_t, std::vector<uint8_t>>> recvq;
uint16_t port = 0; //!< Sending port for this system instance.
Record_System(Global &global, uint64_t seed, const char *name);
Record_System(const Record_System &) = delete;
Record_System operator=(const Record_System &) = delete;
/** @brief Deposit a network packet in this instance's recvq.
*/
void receive(uint16_t send_port, const uint8_t *buf, size_t len);
void push(bool byte)
{
if (FUZZ_DEBUG) {
if (recording_.size() == Fuzz_Data::TRACE_TRAP) {
__asm__("int $3");
}
std::printf(
"%s: produce@%zu(bool %s)\n", name_, recording_.size(), byte ? "true" : "false");
}
recording_.push_back(byte);
}
void push(uint8_t byte)
{
if (FUZZ_DEBUG) {
if (recording_.size() == Fuzz_Data::TRACE_TRAP) {
__asm__("int $3");
}
std::printf("%s: produce@%zu(%u (0x%02x))\n", name_, recording_.size(), byte, byte);
}
recording_.push_back(byte);
}
void push(const uint8_t *bytes, std::size_t size)
{
if (FUZZ_DEBUG) {
if (recording_.size() == Fuzz_Data::TRACE_TRAP) {
__asm__("int $3");
}
std::printf("%s: produce@%zu(%02x..%02x[%zu])\n", name_, recording_.size(), bytes[0],
bytes[size - 1], size);
}
recording_.insert(recording_.end(), bytes, bytes + size);
}
template <std::size_t N>
void push(const char (&bytes)[N])
{
push(reinterpret_cast<const uint8_t *>(bytes), N - 1);
}
const std::vector<uint8_t> &recording() const { return recording_; }
std::vector<uint8_t> take_recording() const { return std::move(recording_); }
private:
std::vector<uint8_t> recording_;
};
/** @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 FUZZ_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

View File

@@ -1,17 +0,0 @@
/* SPDX-License-Identifier: GPL-3.0-or-later
* Copyright © 2022-2025 The TokTok team.
*/
#ifndef C_TOXCORE_TESTING_FUZZING_FUZZ_TOX_H
#define C_TOXCORE_TESTING_FUZZING_FUZZ_TOX_H
#include <memory>
#include "../../toxcore/network.h"
constexpr uint16_t SIZE_IP_PORT = SIZE_IP6 + sizeof(uint16_t);
template <typename T>
using Ptr = std::unique_ptr<T, void (*)(T *)>;
#endif // C_TOXCORE_TESTING_FUZZING_FUZZ_TOX_H

View File

@@ -26,23 +26,41 @@
#include <cstring>
#include <fstream>
#include <memory>
#include <vector>
#include "../../toxcore/tox.h"
#include "../../toxcore/tox_dispatch.h"
#include "../../toxcore/tox_events.h"
#include "../../toxcore/tox_private.h"
#include "fuzz_support.hh"
#include "../support/public/simulated_environment.hh"
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.
*/
using tox::test::FakeClock;
using tox::test::Packet;
using tox::test::ScopedToxSystem;
using tox::test::SimulatedEnvironment;
constexpr uint32_t MESSAGE_COUNT = 5;
class Recorder {
public:
std::vector<uint8_t> data;
void push(bool val) { data.push_back(val); }
void push(uint8_t val) { data.push_back(val); }
void push(const uint8_t *bytes, size_t size) { data.insert(data.end(), bytes, bytes + size); }
// Format: 2 bytes length (big-endian), then data.
void push_packet(const uint8_t *bytes, size_t size)
{
assert(size <= 65535);
push(static_cast<uint8_t>(size >> 8));
push(static_cast<uint8_t>(size & 0xFF));
push(bytes, size);
}
};
struct State {
Tox *tox;
uint32_t done;
@@ -91,7 +109,6 @@ void setup_callbacks(Tox_Dispatch *dispatch)
tox_events_callback_friend_connection_status(
dispatch, [](const Tox_Event_Friend_Connection_Status *event, void *user_data) {
State *state = static_cast<State *>(user_data);
// OK: friend came online.
const uint32_t friend_number
= tox_event_friend_connection_status_get_friend_number(event);
assert(friend_number == 0);
@@ -163,9 +180,7 @@ void setup_callbacks(Tox_Dispatch *dispatch)
assert(!tox_event_friend_typing_get_typing(event));
});
tox_events_callback_self_connection_status(
dispatch, [](const Tox_Event_Self_Connection_Status *event, void *user_data) {
// OK: we got connected.
});
dispatch, [](const Tox_Event_Self_Connection_Status *event, void *user_data) {});
}
void dump(std::vector<uint8_t> recording, const char *filename)
@@ -177,69 +192,80 @@ void dump(std::vector<uint8_t> recording, const char *filename)
void RecordBootstrap(const char *init, const char *bootstrap)
{
auto global = std::make_unique<Record_System::Global>();
SimulatedEnvironment env1;
SimulatedEnvironment env2;
// Set deterministic seeds.
std::minstd_rand rng1(4);
env1.fake_random().set_entropy_source([&](uint8_t *out, size_t count) {
std::uniform_int_distribution<uint16_t> dist(0, 255);
for (size_t i = 0; i < count; ++i)
out[i] = static_cast<uint8_t>(dist(rng1));
});
std::minstd_rand rng2(5);
env2.fake_random().set_entropy_source([&](uint8_t *out, size_t count) {
std::uniform_int_distribution<uint16_t> dist(0, 255);
for (size_t i = 0; i < count; ++i)
out[i] = static_cast<uint8_t>(dist(rng2));
});
Recorder recorder1;
Recorder recorder2;
env1.fake_memory().set_observer([&](bool success) { recorder1.push(success); });
env1.fake_random().set_observer(
[&](const uint8_t *data, size_t count) { recorder1.push(data, count); });
auto node1 = env1.create_node(33445);
auto node2 = env2.create_node(33446);
// Record received packets.
node1->endpoint->set_recv_observer([&](const std::vector<uint8_t> &data, const IP_Port &from) {
recorder1.push_packet(data.data(), data.size());
});
// Bridge the two simulated networks.
env1.simulation().net().add_observer(
[&](const Packet &p) { env2.simulation().net().send_packet(p); });
env2.simulation().net().add_observer(
[&](const Packet &p) { env1.simulation().net().send_packet(p); });
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<Record_System *>(user_data)->name_,
tox_log_level_name(level), file, line, func, message);
});
Tox_Options_Testing opts_test1;
opts_test1.operating_system = &node1->system;
Tox_Err_New error_new;
Tox_Err_New_Testing error_new_testing;
Tox_Options_Testing tox_options_testing;
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);
Tox *tox1 = tox_new_testing(opts, &error_new, &opts_test1, &error_new_testing);
assert(tox1 != nullptr);
assert(error_new == TOX_ERR_NEW_OK);
assert(error_new_testing == TOX_ERR_NEW_TESTING_OK);
std::array<uint8_t, TOX_ADDRESS_SIZE> address1;
tox_self_get_address(tox1, address1.data());
std::array<uint8_t, TOX_PUBLIC_KEY_SIZE> pk1;
tox_self_get_public_key(tox1, pk1.data());
std::array<uint8_t, TOX_PUBLIC_KEY_SIZE> dht_key1;
tox_self_get_dht_id(tox1, dht_key1.data());
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);
Tox_Options_Testing opts_test2;
opts_test2.operating_system = &node2->system;
Tox *tox2 = tox_new_testing(opts, &error_new, &opts_test2, &error_new_testing);
assert(tox2 != nullptr);
assert(error_new == TOX_ERR_NEW_OK);
assert(error_new_testing == TOX_ERR_NEW_TESTING_OK);
std::array<uint8_t, TOX_ADDRESS_SIZE> address2;
tox_self_get_address(tox2, address2.data());
std::array<uint8_t, TOX_PUBLIC_KEY_SIZE> pk2;
tox_self_get_public_key(tox2, pk2.data());
std::array<uint8_t, TOX_PUBLIC_KEY_SIZE> 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);
std::array<uint8_t, TOX_ADDRESS_SIZE> address1;
tox_self_get_address(tox1, address1.data());
std::array<uint8_t, TOX_PUBLIC_KEY_SIZE> dht_key1;
tox_self_get_dht_id(tox1, dht_key1.data());
const bool udp_success = tox_bootstrap(tox2, "127.0.0.2", port, dht_key1.data(), nullptr);
// Bootstrap tox2 to tox1.
const bool udp_success = tox_bootstrap(tox2, "127.0.0.1", 33445, 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);
State state1 = {tox1, 0};
@@ -250,35 +276,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));
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));
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;
// Record the clock increment.
env1.fake_clock().advance(clock_increment);
recorder1.push(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);
env2.fake_clock().advance(clock_increment);
env1.simulation().net().process_events(env1.fake_clock().current_time_ms());
env2.simulation().net().process_events(env2.fake_clock().current_time_ms());
};
while (tox_self_get_connection_status(tox1) == TOX_CONNECTION_NONE
|| tox_self_get_connection_status(tox2) == TOX_CONNECTION_NONE) {
if (Fuzz_Data::FUZZ_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);
iterate(200);
}
std::printf("toxes are online\n");
@@ -289,27 +306,18 @@ void RecordBootstrap(const char *init, const char *bootstrap)
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 (Fuzz_Data::FUZZ_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);
iterate(200);
}
std::printf("tox clients connected\n");
dump(sys1->take_recording(), init);
dump(recorder1.data, init);
// Clear the recorder.
recorder1.data.clear();
while (state1.done < MESSAGE_COUNT && state2.done < MESSAGE_COUNT) {
if (Fuzz_Data::FUZZ_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);
iterate(20);
}
std::printf("test complete\n");
@@ -318,7 +326,7 @@ void RecordBootstrap(const char *init, const char *bootstrap)
tox_kill(tox2);
tox_kill(tox1);
dump(sys1->recording(), bootstrap);
dump(recorder1.data, bootstrap);
}
}

View File

@@ -1,213 +0,0 @@
#include <cassert>
#include <cstdio>
#include "../../toxcore/crypto_core.h"
#include "../../toxcore/tox.h"
#include "../../toxcore/tox_dispatch.h"
#include "../../toxcore/tox_events.h"
#include "../../toxcore/tox_private.h"
#include "fuzz_support.hh"
#include "fuzz_tox.hh"
namespace {
constexpr bool PROTODUMP_DEBUG = Fuzz_Data::FUZZ_DEBUG;
void setup_callbacks(Tox_Dispatch *dispatch)
{
tox_events_callback_conference_connected(
dispatch, [](const Tox_Event_Conference_Connected *event, void *user_data) {
assert(event == nullptr);
});
tox_events_callback_conference_connected(
dispatch, [](const Tox_Event_Conference_Connected *event, void *user_data) {
assert(event == nullptr);
});
tox_events_callback_conference_invite(
dispatch, [](const Tox_Event_Conference_Invite *event, void *user_data) {
assert(event == nullptr);
});
tox_events_callback_conference_message(
dispatch, [](const Tox_Event_Conference_Message *event, void *user_data) {
assert(event == nullptr);
});
tox_events_callback_conference_peer_list_changed(
dispatch, [](const Tox_Event_Conference_Peer_List_Changed *event, void *user_data) {
assert(event == nullptr);
});
tox_events_callback_conference_peer_name(
dispatch, [](const Tox_Event_Conference_Peer_Name *event, void *user_data) {
assert(event == nullptr);
});
tox_events_callback_conference_title(dispatch,
[](const Tox_Event_Conference_Title *event, void *user_data) { assert(event == nullptr); });
tox_events_callback_file_chunk_request(
dispatch, [](const Tox_Event_File_Chunk_Request *event, void *user_data) {
assert(event == nullptr);
});
tox_events_callback_file_recv(dispatch,
[](const Tox_Event_File_Recv *event, void *user_data) { assert(event == nullptr); });
tox_events_callback_file_recv_chunk(dispatch,
[](const Tox_Event_File_Recv_Chunk *event, void *user_data) { assert(event == nullptr); });
tox_events_callback_file_recv_control(
dispatch, [](const Tox_Event_File_Recv_Control *event, void *user_data) {
assert(event == nullptr);
});
tox_events_callback_friend_connection_status(
dispatch, [](const Tox_Event_Friend_Connection_Status *event, void *user_data) {
Tox *tox = static_cast<Tox *>(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, [](const Tox_Event_Friend_Lossless_Packet *event, void *user_data) {
assert(event == nullptr);
});
tox_events_callback_friend_lossy_packet(
dispatch, [](const Tox_Event_Friend_Lossy_Packet *event, void *user_data) {
assert(event == nullptr);
});
tox_events_callback_friend_message(
dispatch, [](const Tox_Event_Friend_Message *event, void *user_data) {
Tox *tox = static_cast<Tox *>(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, [](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, [](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<uint32_t *>(user_data);
*done = std::max(*done, message_id);
});
tox_events_callback_friend_request(
dispatch, [](const Tox_Event_Friend_Request *event, void *user_data) {
Tox *tox = static_cast<Tox *>(user_data);
Tox_Err_Friend_Add err;
tox_friend_add_norequest(tox, tox_event_friend_request_get_public_key(event), &err);
});
tox_events_callback_friend_status(
dispatch, [](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, [](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, [](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, [](const Tox_Event_Self_Connection_Status *event, void *user_data) {
// OK: we got connected.
});
}
void TestEndToEnd(Fuzz_Data &input)
{
/**
* 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.
*/
const bool PROTODUMP_REDUCE = getenv("PROTODUMP_REDUCE") != nullptr;
Fuzz_System sys(input);
Ptr<Tox_Options> opts(tox_options_new(nullptr), tox_options_free);
assert(opts != nullptr);
tox_options_set_local_discovery_enabled(opts.get(), false);
Tox_Options_Testing tox_options_testing;
tox_options_testing.operating_system = 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 (PROTODUMP_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_Err_New_Testing error_new_testing;
Tox *tox = tox_new_testing(opts.get(), &error_new, &tox_options_testing, &error_new_testing);
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);
assert(error_new_testing == TOX_ERR_NEW_TESTING_OK);
tox_events_init(tox);
Tox_Dispatch *dispatch = tox_dispatch_new(nullptr);
assert(dispatch != nullptr);
setup_callbacks(dispatch);
while (!input.empty()) {
Tox_Err_Events_Iterate error_iterate;
Tox_Events *events = tox_events_iterate(tox, true, &error_iterate);
tox_events_equal(tox_get_system(tox), events, events); // TODO(iphydf): assert?
tox_dispatch_invoke(dispatch, events, tox);
tox_events_free(events);
const uint8_t clock_increment = random_u08(sys.rng.get());
if (PROTODUMP_DEBUG) {
printf("clock increment: %d\n", clock_increment);
}
sys.clock += std::max(System::MIN_ITERATION_INTERVAL, clock_increment);
}
if (PROTODUMP_REDUCE) {
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));
assert(tox_friend_get_connection_status(tox, 0, nullptr) == 2);
assert(input.empty());
}
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.
}

View File

@@ -4,10 +4,14 @@
#include "../../toxcore/tox.h"
#include "../../toxcore/tox_private.h"
#include "fuzz_support.hh"
#include "../support/public/fuzz_data.hh"
#include "../support/public/simulated_environment.hh"
namespace {
using tox::test::Fuzz_Data;
using tox::test::SimulatedEnvironment;
void TestSaveDataLoading(Fuzz_Data &input)
{
Tox_Err_Options_New error_options;
@@ -27,10 +31,12 @@ void TestSaveDataLoading(Fuzz_Data &input)
tox_options_set_savedata_type(tox_options, TOX_SAVEDATA_TYPE_TOX_SAVE);
Tox_Options_Testing tox_options_testing;
Null_System sys;
tox_options_testing.operating_system = sys.sys.get();
SimulatedEnvironment env;
auto node = env.create_node(33445);
tox_options_testing.operating_system = &node->system;
Tox *tox = tox_new_testing(tox_options, nullptr, &tox_options_testing, nullptr);
Tox_Err_New_Testing err_testing;
Tox *tox = tox_new_testing(tox_options, nullptr, &tox_options_testing, &err_testing);
tox_options_free(tox_options);
if (tox == nullptr) {
// Tox save was invalid, we're finished here