diff --git a/CMakeLists.txt b/CMakeLists.txt index 019feaa48..865127c10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,7 @@ option(SNMALLOC_IPO "Link with IPO/LTO support" OFF) option(SNMALLOC_BENCHMARK_INDIVIDUAL_MITIGATIONS "Build tests and ld_preload for individual mitigations" OFF) option(SNMALLOC_ENABLE_DYNAMIC_LOADING "Build such that snmalloc can be dynamically loaded. This is not required for LD_PRELOAD, and will harm performance if enabled." OFF) option(SNMALLOC_ENABLE_WAIT_ON_ADDRESS "Use wait on address backoff strategy if it is available" ON) +option(SNMALLOC_ENABLE_FUZZING "Enable fuzzing instrumentation tests" OFF) # Options that apply only if we're not building the header-only library cmake_dependent_option(SNMALLOC_RUST_SUPPORT "Build static library for rust" OFF "NOT SNMALLOC_HEADER_ONLY_LIBRARY" OFF) cmake_dependent_option(SNMALLOC_STATIC_LIBRARY "Build static libraries" ON "NOT SNMALLOC_HEADER_ONLY_LIBRARY" OFF) @@ -599,3 +600,6 @@ install(EXPORT snmallocConfig DESTINATION "share/snmalloc" ) +if (SNMALLOC_ENABLE_FUZZING) + add_subdirectory(fuzzing) +endif() diff --git a/fuzzing/CMakeLists.txt b/fuzzing/CMakeLists.txt new file mode 100644 index 000000000..b8f9b58d5 --- /dev/null +++ b/fuzzing/CMakeLists.txt @@ -0,0 +1,22 @@ +include(FetchContent) + +FetchContent_Declare( + fuzztest + GIT_REPOSITORY https://github.com/google/fuzztest.git + GIT_TAG 2024-10-28 +) + +FetchContent_MakeAvailable(fuzztest) + +enable_testing() +fuzztest_setup_fuzzing_flags() + +add_executable( + snmalloc-fuzzer + snmalloc-fuzzer.cpp +) + +target_link_libraries(snmalloc-fuzzer PRIVATE snmalloc) +target_compile_options(snmalloc-fuzzer PRIVATE -fsanitize=address -DADDRESS_SANITIZER) + +link_fuzztest(snmalloc-fuzzer) diff --git a/fuzzing/snmalloc-fuzzer.cpp b/fuzzing/snmalloc-fuzzer.cpp new file mode 100644 index 000000000..03e6d0d68 --- /dev/null +++ b/fuzzing/snmalloc-fuzzer.cpp @@ -0,0 +1,199 @@ +#include "fuzztest/fuzztest.h" +#include "snmalloc/snmalloc.h" + +#include +#include +#include +#include +#include +#include + +void simple_memcpy(std::vector data) +{ + std::vector dest(data.size()); + snmalloc::memcpy(dest.data(), data.data(), data.size()); + if (data != dest) + abort(); +} + +void memcpy_with_align_offset( + size_t source_alignment, + size_t source_offset, + size_t dest_alignment, + size_t dest_offset, + std::string data) +{ + source_alignment = 1 << source_alignment; + dest_alignment = 1 << dest_alignment; + source_offset = source_offset % source_alignment; + dest_offset = dest_offset % dest_alignment; + auto src_ = ::operator new( + data.size() + source_offset, std::align_val_t{source_alignment}); + auto dst_ = + ::operator new(data.size() + dest_offset, std::align_val_t{dest_alignment}); + auto src = static_cast(src_) + source_offset; + auto dst = static_cast(dst_) + dest_offset; + snmalloc::memcpy(src, data.data(), data.size()); + snmalloc::memcpy(dst, src, data.size()); + if (std::string_view(dst, data.size()) != data) + abort(); + ::operator delete(src_, std::align_val_t{source_alignment}); + ::operator delete(dst_, std::align_val_t{dest_alignment}); +} + +void simple_memmove(std::vector data) +{ + std::vector dest(data.size()); + snmalloc::memmove(dest.data(), data.data(), data.size()); + if (data != dest) + abort(); +} + +void forward_memmove(std::string data, size_t offset) +{ + std::string to_move = data; + offset = std::min(offset, data.size()); + snmalloc::memmove( + to_move.data() + offset, to_move.data(), to_move.size() - offset); + size_t after_move = to_move.size() - offset; + if ( + std::string_view(data.data(), after_move) != + std::string_view(to_move.data() + offset, after_move)) + abort(); +} + +void backward_memmove(std::string data, size_t offset) +{ + std::string to_move = data; + offset = std::min(offset, data.size()); + snmalloc::memmove( + to_move.data(), to_move.data() + offset, to_move.size() - offset); + size_t after_move = to_move.size() - offset; + if ( + std::string_view(data.data() + offset, after_move) != + std::string_view(to_move.data(), after_move)) + abort(); +} + +constexpr static size_t size_limit = 16384; + +enum class EventKind : unsigned +{ + AllocZero = 0, + AllocNoZero = 1, + Free = 2, + Check = 3, + ReFill = 4, +}; + +struct Event +{ + EventKind kind; + size_t size_or_index; + char filler; + + Event(std::tuple payload) + : kind(static_cast(std::get<0>(payload) % 5)), + size_or_index(std::get<1>(payload)), + filler(std::get<2>(payload)) + { + if (kind == EventKind::AllocZero || kind == EventKind::AllocNoZero) + { + size_or_index %= size_limit; + } + } +}; + +struct Result +{ + char filler; + char* ptr; + size_t size; + + void check() + { + auto res = std::reduce( + std::execution::unseq, + ptr, + ptr + size, + static_cast(0), + [&](unsigned char acc, char c) -> unsigned char { + return acc | static_cast(c != filler); + }); + if (res) + abort(); + } +}; + +void snmalloc_random_walk( + std::vector> payload) +{ + std::vector results; + for (auto& p : payload) + { + Event e(p); + auto scoped = snmalloc::get_scoped_allocator(); + switch (e.kind) + { + case EventKind::AllocZero: + { + auto ptr = + static_cast(scoped->alloc(e.size_or_index)); + results.push_back({0, ptr, e.size_or_index}); + break; + } + + case EventKind::AllocNoZero: + { + auto ptr = + static_cast(scoped->alloc(e.size_or_index)); + std::fill(ptr, ptr + e.size_or_index, e.filler); + results.push_back({e.filler, ptr, e.size_or_index}); + break; + } + + case EventKind::Free: + { + if (results.empty()) + break; + auto index = e.size_or_index % results.size(); + scoped->dealloc(results[index].ptr); + results.erase(results.begin() + static_cast(index)); + break; + } + + case EventKind::Check: + { + for (auto& r : results) + r.check(); + break; + } + + case EventKind::ReFill: + { + if (results.empty()) + break; + auto index = e.size_or_index % results.size(); + std::fill( + results[index].ptr, + results[index].ptr + results[index].size, + e.filler); + results[index].filler = e.filler; + break; + } + } + } +} + +FUZZ_TEST(snmalloc_fuzzing, simple_memcpy); +FUZZ_TEST(snmalloc_fuzzing, simple_memmove); +FUZZ_TEST(snmalloc_fuzzing, forward_memmove); +FUZZ_TEST(snmalloc_fuzzing, backward_memmove); +FUZZ_TEST(snmalloc_fuzzing, memcpy_with_align_offset) + .WithDomains( + fuzztest::InRange(0, 6), + fuzztest::Positive(), + fuzztest::InRange(0, 6), + fuzztest::Positive(), + fuzztest::String()); +FUZZ_TEST(snmalloc_fuzzing, snmalloc_random_walk);