From 25f2dfa53be1c0b847c38c76d464267d73dd1523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D0=B0=D0=B2=D0=B5=D0=BB=20=D0=9D=D0=B5=D0=BA=D1=80?= =?UTF-8?q?=D0=B0=D1=81=D0=BE=D0=B2?= Date: Fri, 11 Sep 2026 13:49:40 +0300 Subject: [PATCH] Add AFL++ custom mutator integration --- CMakeLists.txt | 17 + README.md | 64 ++- cmake/external/aflplusplus.cmake | 94 ++++ examples/CMakeLists.txt | 4 + examples/aflplusplus/CMakeLists.txt | 50 ++ examples/aflplusplus/aflplusplus_example.cc | 14 + .../aflplusplus/aflplusplus_example.proto | 9 + .../aflplusplus_example_mutator.cc | 4 + .../aflplusplus/aflplusplus_example_test.cc | 93 ++++ libprotobuf-mutator.pc.in | 2 +- port/protobuf.h | 11 + src/CMakeLists.txt | 9 + src/aflplusplus/CMakeLists.txt | 43 ++ src/aflplusplus/aflplusplus_macro.cc | 56 +++ src/aflplusplus/aflplusplus_macro.h | 107 ++++ src/aflplusplus/aflplusplus_mutate_wrapper.c | 14 + src/aflplusplus/aflplusplus_mutate_wrapper.h | 20 + src/aflplusplus/aflplusplus_mutator.cc | 471 ++++++++++++++++++ src/aflplusplus/aflplusplus_mutator.h | 75 +++ src/aflplusplus/aflplusplus_test.cc | 260 ++++++++++ src/field_instance.h | 4 +- src/libfuzzer/libfuzzer_macro.cc | 195 +------- src/libfuzzer/libfuzzer_macro.h | 20 +- src/mutation_io.cc | 191 +++++++ src/mutation_io.h | 116 +++++ 25 files changed, 1735 insertions(+), 208 deletions(-) create mode 100644 cmake/external/aflplusplus.cmake create mode 100644 examples/aflplusplus/CMakeLists.txt create mode 100644 examples/aflplusplus/aflplusplus_example.cc create mode 100644 examples/aflplusplus/aflplusplus_example.proto create mode 100644 examples/aflplusplus/aflplusplus_example_mutator.cc create mode 100644 examples/aflplusplus/aflplusplus_example_test.cc create mode 100644 src/aflplusplus/CMakeLists.txt create mode 100644 src/aflplusplus/aflplusplus_macro.cc create mode 100644 src/aflplusplus/aflplusplus_macro.h create mode 100644 src/aflplusplus/aflplusplus_mutate_wrapper.c create mode 100644 src/aflplusplus/aflplusplus_mutate_wrapper.h create mode 100644 src/aflplusplus/aflplusplus_mutator.cc create mode 100644 src/aflplusplus/aflplusplus_mutator.h create mode 100644 src/aflplusplus/aflplusplus_test.cc create mode 100644 src/mutation_io.cc create mode 100644 src/mutation_io.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8735e17..dd5c954 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,17 @@ option(LIB_PROTO_MUTATOR_TESTING "Enable test building" ON) option(LIB_PROTO_MUTATOR_EXAMPLES "Enable examples building" ON) option(LIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF "Automatically download working protobuf" OFF) +option(LIB_PROTO_MUTATOR_WITH_AFLPLUSPLUS + "Build the AFL++ custom mutator library" OFF) +set(LIB_PROTO_MUTATOR_AFLPLUSPLUS_REPOSITORY + "https://github.com/AFLplusplus/AFLplusplus.git" + CACHE STRING "AFLplusplus git repository") +set(LIB_PROTO_MUTATOR_AFLPLUSPLUS_TAG "stable" + CACHE STRING "AFLplusplus git tag") +set(LIB_PROTO_MUTATOR_AFLPLUSPLUS_SOURCE_DIR "" + CACHE PATH "Use this AFLplusplus checkout instead of downloading one") +option(LIB_PROTO_MUTATOR_QUIET + "Silence all libprotobuf logging in the process" OFF) option(LIB_PROTO_MUTATOR_WITH_ASAN "Enable address sanitizer" OFF) option(LIB_PROTO_MUTATOR_EXAMPLES_USE_LATEST "Use latest libexpat/libxml2 for expat_example/libxml2_example" OFF) set(PKG_CONFIG_PATH "share/pkgconfig" CACHE STRING "Directory to install pkgconfig file") @@ -134,6 +145,12 @@ else() include_directories(${CMAKE_CURRENT_BINARY_DIR}) endif() +if (LIB_PROTO_MUTATOR_WITH_AFLPLUSPLUS) + set(CMAKE_POSITION_INDEPENDENT_CODE ON) + set(PC_AFLPLUSPLUS_LIB " -lprotobuf-mutator-aflplusplus") + include(aflplusplus) +endif() + set(LIB_DIR "lib${LIB_SUFFIX}") if (LIB_PROTO_MUTATOR_TESTING) diff --git a/README.md b/README.md index e87f96e..a8ee37a 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,14 @@ ## Overview libprotobuf-mutator is a library to randomly mutate [protobuffers](https://github.com/google/protobuf).
-It could be used together with guided fuzzing engines, such as [libFuzzer](http://libfuzzer.info). +It could be used together with guided fuzzing engines, such as [libFuzzer](http://libfuzzer.info) +and [AFL++](https://github.com/AFLplusplus/AFLplusplus). The core of libprotobuf-mutator has the following dependencies: - Protobuf >=3.6.1.3 (library and compiler) - Clang >=12.0.0 including libFuzzer +- AFL++ >=4.08c headers, for the AFL++ integration only; cmake fetches them ## Quick start on Debian/Ubuntu @@ -141,6 +143,66 @@ DEFINE_PROTO_FUZZER(const MyMessageType& input) { ConsumeMyMessageType(input); } ``` +## Integrating with AFL++ +AflplusplusProtobufMutator plugs into AFL++ as a +[custom mutator](https://github.com/AFLplusplus/AFLplusplus/blob/stable/docs/custom_mutators.md). +Unlike the libFuzzer integration this is two binaries: a shared object that +`afl-fuzz` loads, and the fuzz target itself. + +Pass `-DLIB_PROTO_MUTATOR_WITH_AFLPLUSPLUS=ON` to cmake to build +`libprotobuf-mutator-aflplusplus`. The AFL++ headers it needs are fetched by +cmake; `LIB_PROTO_MUTATOR_AFLPLUSPLUS_TAG` picks the revision and +`LIB_PROTO_MUTATOR_AFLPLUSPLUS_SOURCE_DIR` builds against a checkout you already +have. + +The mutator library is a single translation unit built as a shared object: + +```c++ +#include "src/aflplusplus/aflplusplus_macro.h" + +DEFINE_AFLPLUSPLUS_PROTO_MUTATOR_LIBRARY(MyMessageType) +``` + +The fuzz target looks like the libFuzzer one: + +```c++ +#include "src/aflplusplus/aflplusplus_macro.h" + +DEFINE_AFLPLUSPLUS_PROTO_FUZZER(const MyMessageType& input) { + // Code which needs to be fuzzed. + ConsumeMyMessageType(input); +} +``` + +Run it with: + +```sh +AFL_CUSTOM_MUTATOR_LIBRARY=./custom_mutator.so \ +AFL_CUSTOM_MUTATOR_ONLY=1 \ +afl-fuzz -i in -o out -- ./my_fuzz_target +``` + +`AFL_CUSTOM_MUTATOR_ONLY` is not optional. AFL++'s own stages mutate bytes and +would feed the target inputs that no longer parse as a protobuf, so the mutator +refuses to start without it. + +Both macros default to the text serialization, same as `DEFINE_PROTO_FUZZER`. +`DEFINE_BINARY_AFLPLUSPLUS_PROTO_MUTATOR_LIBRARY` and +`DEFINE_BINARY_AFLPLUSPLUS_PROTO_FUZZER` switch to binary. The mutator library +and the target have to agree: text with text, binary with binary. + +The library also implements AFL++'s trimming API. It drops one set field or one +repeated element per step instead of cutting bytes out of the serialized +message, which would leave most of the queue unparseable. + +The mutator casts the `void*` it receives from `afl-fuzz` to `afl_state_t*`, so +the AFL++ revision it was built against has to match the `afl-fuzz` binary that +loads it. + +Please see [aflplusplus_example_mutator.cc](/examples/aflplusplus/aflplusplus_example_mutator.cc) +and [aflplusplus_example.cc](/examples/aflplusplus/aflplusplus_example.cc) as an +example. + ## UTF-8 strings "proto2" and "proto3" handle invalid UTF-8 strings differently. In both cases string should be UTF-8, however only "proto3" enforces that. So if fuzzer is diff --git a/cmake/external/aflplusplus.cmake b/cmake/external/aflplusplus.cmake new file mode 100644 index 0000000..ca7b17a --- /dev/null +++ b/cmake/external/aflplusplus.cmake @@ -0,0 +1,94 @@ +set(AFLPLUSPLUS_TARGET external.aflplusplus) +set(AFLPLUSPLUS_INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/${AFLPLUSPLUS_TARGET}) + +set(AFLPLUSPLUS_INCLUDE_DIRS ${AFLPLUSPLUS_INSTALL_DIR}/include + ${AFLPLUSPLUS_INSTALL_DIR}/include/afl) +set(AFLPLUSPLUS_PERFORMANCE_SOURCE + ${AFLPLUSPLUS_INSTALL_DIR}/src/afl-performance.c) + +file(MAKE_DIRECTORY ${AFLPLUSPLUS_INCLUDE_DIRS}) + +function(check_aflplusplus_version expected) + if (LIB_PROTO_MUTATOR_AFLPLUSPLUS_SKIP_VERSION_CHECK) + return() + endif() + + find_program(AFLPLUSPLUS_FUZZ_BINARY afl-fuzz) + if (NOT AFLPLUSPLUS_FUZZ_BINARY) + return() + endif() + + execute_process(COMMAND ${AFLPLUSPLUS_FUZZ_BINARY} --version + OUTPUT_VARIABLE binary_version + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET) + string(REGEX REPLACE "^afl-fuzz" "" binary_version "${binary_version}") + string(REGEX REPLACE "^(\\+\\+|v)" "" binary_version "${binary_version}") + string(REGEX REPLACE "^(\\+\\+|v)" "" expected "${expected}") + + if (NOT binary_version STREQUAL expected) + message(FATAL_ERROR + "AFLplusplus headers are ${expected} but ${AFLPLUSPLUS_FUZZ_BINARY}" + " is ${binary_version}. The mutator reads afl_state_t out of the" + " pointer afl-fuzz passes it, and that struct differs between" + " releases, so both have to come from one revision. Point" + " LIB_PROTO_MUTATOR_AFLPLUSPLUS_SOURCE_DIR at the checkout this" + " afl-fuzz was built from, put the matching afl-fuzz first on PATH," + " or set LIB_PROTO_MUTATOR_AFLPLUSPLUS_SKIP_VERSION_CHECK=ON.") + endif() +endfunction() + +if (LIB_PROTO_MUTATOR_AFLPLUSPLUS_SOURCE_DIR) + get_filename_component(AFLPLUSPLUS_SOURCE_DIR + "${LIB_PROTO_MUTATOR_AFLPLUSPLUS_SOURCE_DIR}" ABSOLUTE) + if (NOT EXISTS ${AFLPLUSPLUS_SOURCE_DIR}/include/afl-mutations.h) + message(FATAL_ERROR + "LIB_PROTO_MUTATOR_AFLPLUSPLUS_SOURCE_DIR=${AFLPLUSPLUS_SOURCE_DIR}" + " is not an AFLplusplus checkout") + endif() + + file(STRINGS ${AFLPLUSPLUS_SOURCE_DIR}/include/config.h + AFLPLUSPLUS_VERSION_LINE REGEX "^#define VERSION ") + string(REGEX REPLACE "^#define VERSION[ \t]+\"([^\"]*)\".*$" "\\1" + AFLPLUSPLUS_VERSION "${AFLPLUSPLUS_VERSION_LINE}") + message(STATUS "AFLplusplus ${AFLPLUSPLUS_VERSION} " + "from ${AFLPLUSPLUS_SOURCE_DIR}") + check_aflplusplus_version("${AFLPLUSPLUS_VERSION}") + + set(AFLPLUSPLUS_DOWNLOAD_ARGS + SOURCE_DIR ${AFLPLUSPLUS_SOURCE_DIR} + DOWNLOAD_COMMAND "") +else() + if (LIB_PROTO_MUTATOR_AFLPLUSPLUS_TAG MATCHES "^v?[0-9]") + check_aflplusplus_version("${LIB_PROTO_MUTATOR_AFLPLUSPLUS_TAG}") + endif() + + set(AFLPLUSPLUS_DOWNLOAD_ARGS + GIT_REPOSITORY ${LIB_PROTO_MUTATOR_AFLPLUSPLUS_REPOSITORY} + GIT_TAG ${LIB_PROTO_MUTATOR_AFLPLUSPLUS_TAG} + GIT_SHALLOW ON + GIT_PROGRESS ON + ) +endif() + +# AFL++ has no build system we could reuse here: only the headers and +# rand_next() from src/afl-performance.c are needed, so install just those. +include (ExternalProject) +if (POLICY CMP0097) + cmake_policy(SET CMP0097 NEW) +endif() +ExternalProject_Add(${AFLPLUSPLUS_TARGET} + PREFIX ${AFLPLUSPLUS_TARGET} + ${AFLPLUSPLUS_DOWNLOAD_ARGS} + GIT_SUBMODULES "" + UPDATE_COMMAND "" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory + /include + ${AFLPLUSPLUS_INSTALL_DIR}/include/afl + COMMAND ${CMAKE_COMMAND} -E copy + /src/afl-performance.c + ${AFLPLUSPLUS_PERFORMANCE_SOURCE} + BUILD_BYPRODUCTS ${AFLPLUSPLUS_PERFORMANCE_SOURCE} +) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d932aed..a3e3ca8 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -16,3 +16,7 @@ add_subdirectory(xml) add_subdirectory(expat) add_subdirectory(libfuzzer) add_subdirectory(libxml2) + +if (LIB_PROTO_MUTATOR_WITH_AFLPLUSPLUS) + add_subdirectory(aflplusplus) +endif() diff --git a/examples/aflplusplus/CMakeLists.txt b/examples/aflplusplus/CMakeLists.txt new file mode 100644 index 0000000..9d6d0e1 --- /dev/null +++ b/examples/aflplusplus/CMakeLists.txt @@ -0,0 +1,50 @@ +protobuf_generate_cpp(AFLPLUSPLUS_EXAMPLE_PROTO_SRCS + AFLPLUSPLUS_EXAMPLE_PROTO_HDRS + aflplusplus_example.proto) + +add_library(aflplusplus-example-proto + ${AFLPLUSPLUS_EXAMPLE_PROTO_SRCS}) +set_property(TARGET aflplusplus-example-proto + PROPERTY COMPILE_FLAGS ${NO_FUZZING_FLAGS}) + +add_library(aflplusplus_example_mutator MODULE + aflplusplus_example_mutator.cc) +target_link_libraries(aflplusplus_example_mutator + aflplusplus-example-proto + protobuf-mutator-aflplusplus) +set_target_properties(aflplusplus_example_mutator PROPERTIES + PREFIX "" + COMPILE_FLAGS "${NO_FUZZING_FLAGS}") + +add_executable(aflplusplus_example + aflplusplus_example.cc) +target_link_libraries(aflplusplus_example + aflplusplus-example-proto + protobuf-mutator-aflplusplus + ${LIB_PROTO_MUTATOR_FUZZER_LIBRARIES}) +set_property(TARGET aflplusplus_example + PROPERTY COMPILE_FLAGS) +set_property(TARGET aflplusplus_example + PROPERTY LINK_FLAGS ${FUZZING_FLAGS_BINARY}) + +add_executable(aflplusplus_example_test + aflplusplus_example_test.cc) +add_dependencies(aflplusplus_example_test + aflplusplus_example + aflplusplus_example_mutator) +target_include_directories(aflplusplus_example_test PRIVATE + ${AFLPLUSPLUS_INCLUDE_DIRS}) +target_compile_definitions(aflplusplus_example_test PRIVATE + AFLPLUSPLUS_EXAMPLE_MUTATOR_PATH="$") +target_link_libraries(aflplusplus_example_test + protobuf-mutator + protobuf-mutator-aflplusplus + ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} + ${CMAKE_DL_LIBS}) +set_target_properties(aflplusplus_example_test PROPERTIES + COMPILE_FLAGS "${NO_FUZZING_FLAGS}") +add_test(test.aflplusplus_example_test aflplusplus_example_test --gtest_color=yes AUTO) +if(LIB_PROTO_MUTATOR_TESTING) + add_dependencies(check aflplusplus_example_test) +endif() diff --git a/examples/aflplusplus/aflplusplus_example.cc b/examples/aflplusplus/aflplusplus_example.cc new file mode 100644 index 0000000..2854509 --- /dev/null +++ b/examples/aflplusplus/aflplusplus_example.cc @@ -0,0 +1,14 @@ +#include + +#include + +#include "examples/aflplusplus/aflplusplus_example.pb.h" +#include "src/aflplusplus/aflplusplus_macro.h" + +DEFINE_AFLPLUSPLUS_PROTO_FUZZER(const aflplusplus_example::Msg& message) { + if (message.optional_string() == "afl" && message.nested_size() > 2 && + message.nested(0).optional_uint64() == 0xdeadbeef) { + std::cerr << message.DebugString() << "\n"; + abort(); + } +} diff --git a/examples/aflplusplus/aflplusplus_example.proto b/examples/aflplusplus/aflplusplus_example.proto new file mode 100644 index 0000000..8cbdaeb --- /dev/null +++ b/examples/aflplusplus/aflplusplus_example.proto @@ -0,0 +1,9 @@ +syntax = "proto2"; +package aflplusplus_example; + +message Msg { + optional float optional_float = 1; + optional uint64 optional_uint64 = 2; + optional string optional_string = 3; + repeated Msg nested = 4; +} diff --git a/examples/aflplusplus/aflplusplus_example_mutator.cc b/examples/aflplusplus/aflplusplus_example_mutator.cc new file mode 100644 index 0000000..b10435f --- /dev/null +++ b/examples/aflplusplus/aflplusplus_example_mutator.cc @@ -0,0 +1,4 @@ +#include "examples/aflplusplus/aflplusplus_example.pb.h" +#include "src/aflplusplus/aflplusplus_macro.h" + +DEFINE_AFLPLUSPLUS_PROTO_MUTATOR_LIBRARY(aflplusplus_example::Msg) diff --git a/examples/aflplusplus/aflplusplus_example_test.cc b/examples/aflplusplus/aflplusplus_example_test.cc new file mode 100644 index 0000000..78b77bb --- /dev/null +++ b/examples/aflplusplus/aflplusplus_example_test.cc @@ -0,0 +1,93 @@ +#include +#include + +#include +#include + +#include "google/protobuf/dynamic_message.h" +#include "port/gtest.h" +#include "port/protobuf.h" +#include "src/mutation_io.h" + +extern "C" { +#include "config.h" +#include "types.h" +#include "afl-fuzz.h" +} + +namespace { + +using CustomInit = void* (*)(void*, unsigned int); +using CustomFuzz = size_t (*)(void*, unsigned char*, size_t, unsigned char**, + unsigned char*, size_t, size_t); +using CustomDeinit = void (*)(void*); + +class AflplusplusExampleTest : public testing::Test { + protected: + static void SetUpTestSuite() { + handle_ = + dlopen(AFLPLUSPLUS_EXAMPLE_MUTATOR_PATH, RTLD_NOW | RTLD_NODELETE); + ASSERT_TRUE(handle_) << dlerror(); + } + + void SetUp() override { + setenv("AFL_CUSTOM_MUTATOR_ONLY", "1", 1); + afl_.reset(new afl_state_t()); + afl_->fixed_seed = 1; + rand_set_seed(afl_.get(), 1); + } + + template + static T Symbol(const char* name) { + return reinterpret_cast(dlsym(handle_, name)); + } + + static void* handle_; + std::unique_ptr afl_; +}; + +void* AflplusplusExampleTest::handle_ = nullptr; + +TEST_F(AflplusplusExampleTest, ExportsCustomMutatorApi) { + for (const char* name : + {"afl_custom_init", "afl_custom_fuzz", "afl_custom_deinit", + "afl_custom_init_trim", "afl_custom_trim", "afl_custom_post_trim"}) { + EXPECT_TRUE(dlsym(handle_, name)) << "missing symbol " << name; + } +} + +TEST_F(AflplusplusExampleTest, ProducesParseableMessages) { + auto init = Symbol("afl_custom_init"); + auto fuzz = Symbol("afl_custom_fuzz"); + auto deinit = Symbol("afl_custom_deinit"); + ASSERT_TRUE(init); + ASSERT_TRUE(fuzz); + ASSERT_TRUE(deinit); + + const protobuf_mutator::protobuf::Descriptor* descriptor = + protobuf_mutator::protobuf::DescriptorPool::generated_pool() + ->FindMessageTypeByName("aflplusplus_example.Msg"); + ASSERT_TRUE(descriptor); + protobuf_mutator::protobuf::DynamicMessageFactory factory; + std::unique_ptr message( + factory.GetPrototype(descriptor)->New()); + + void* mutator = init(afl_.get(), 1); + ASSERT_TRUE(mutator); + + unsigned char buf[4096] = {}; + int produced = 0; + for (int i = 0; i < 100; ++i) { + unsigned char* out = nullptr; + size_t size = fuzz(mutator, buf, 0, &out, nullptr, 0, sizeof(buf)); + if (!size) continue; + ASSERT_TRUE(out); + ++produced; + EXPECT_TRUE(protobuf_mutator::ParseMessage(false, out, size, message.get())); + } + EXPECT_GT(produced, 0); + + deinit(mutator); +} + +} // namespace diff --git a/libprotobuf-mutator.pc.in b/libprotobuf-mutator.pc.in index 551dc72..fbfe812 100644 --- a/libprotobuf-mutator.pc.in +++ b/libprotobuf-mutator.pc.in @@ -6,4 +6,4 @@ Name: libprotobuf-mutator Description: randomly mutate protobuffers for fuzzing Version: 0 Cflags: -I${includedir} -I${includedir}/src -Libs: -L${libdir} -lprotobuf-mutator-libfuzzer -lprotobuf-mutator +Libs: -L${libdir} -lprotobuf-mutator-libfuzzer@PC_AFLPLUSPLUS_LIB@ -lprotobuf-mutator diff --git a/port/protobuf.h b/port/protobuf.h index 4fd7d74..65d18ec 100644 --- a/port/protobuf.h +++ b/port/protobuf.h @@ -87,6 +87,17 @@ inline bool HasPresence(const google::protobuf::FieldDescriptor& descriptor) { #endif } +inline bool IsValidUtf8(const std::string& value) { +#if GOOGLE_PROTOBUF_VERSION < 4025000 + return google::protobuf::internal::IsStructurallyValidUTF8( + value.data(), static_cast(value.size())); +#else + using google::protobuf::internal::WireFormatLite; + return WireFormatLite::VerifyUtf8String( + value.data(), static_cast(value.size()), WireFormatLite::PARSE, ""); +#endif +} + inline void PrepareTextParser(google::protobuf::TextFormat::Parser& parser) { // commit d8c2501b43c1b56e3efa74048a18f8ce06ba07fe of >=3.8.0 for .SetRecursionLimit // commit 176f7db11d8242b36a3ea6abb1cc436fca5bf75d of >=3.8.0 for .AllowUnknownField diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 633d882..9c90038 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,8 +14,13 @@ add_subdirectory(libfuzzer) +if (LIB_PROTO_MUTATOR_WITH_AFLPLUSPLUS) + add_subdirectory(aflplusplus) +endif() + add_library(protobuf-mutator binary_format.cc + mutation_io.cc mutator.cc text_format.cc utf8_fix.cc) @@ -26,6 +31,10 @@ set_target_properties(protobuf-mutator PROPERTIES SOVERSION 0) target_compile_features(protobuf-mutator PUBLIC cxx_std_14) +if (LIB_PROTO_MUTATOR_QUIET) + target_compile_definitions(protobuf-mutator PRIVATE LIB_PROTO_MUTATOR_QUIET) +endif() + if (LIB_PROTO_MUTATOR_TESTING) protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS mutator_test_proto2.proto diff --git a/src/aflplusplus/CMakeLists.txt b/src/aflplusplus/CMakeLists.txt new file mode 100644 index 0000000..25ad13d --- /dev/null +++ b/src/aflplusplus/CMakeLists.txt @@ -0,0 +1,43 @@ +set_source_files_properties(${AFLPLUSPLUS_PERFORMANCE_SOURCE} PROPERTIES + GENERATED TRUE + COMPILE_FLAGS "-w") + +add_library(protobuf-mutator-aflplusplus + aflplusplus_macro.cc + aflplusplus_mutator.cc + aflplusplus_mutate_wrapper.c + ${AFLPLUSPLUS_PERFORMANCE_SOURCE}) +add_dependencies(protobuf-mutator-aflplusplus ${AFLPLUSPLUS_TARGET}) +target_include_directories(protobuf-mutator-aflplusplus PRIVATE + ${AFLPLUSPLUS_INCLUDE_DIRS}) +target_link_libraries(protobuf-mutator-aflplusplus + protobuf-mutator + ${Protobuf_LIBRARIES}) +set_target_properties(protobuf-mutator-aflplusplus PROPERTIES + COMPILE_FLAGS "${NO_FUZZING_FLAGS}" + SOVERSION 0) +target_compile_features(protobuf-mutator-aflplusplus PUBLIC cxx_std_14) + +if (LIB_PROTO_MUTATOR_TESTING) + add_executable(aflplusplus_test + aflplusplus_test.cc) + add_dependencies(aflplusplus_test ${AFLPLUSPLUS_TARGET}) + target_include_directories(aflplusplus_test PRIVATE + ${AFLPLUSPLUS_INCLUDE_DIRS}) + target_link_libraries(aflplusplus_test + protobuf-mutator + protobuf-mutator-aflplusplus + mutator-test-proto + ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT}) + set_target_properties(aflplusplus_test PROPERTIES + COMPILE_FLAGS "${NO_FUZZING_FLAGS}") + add_test(test.protobuf_aflplusplus_test aflplusplus_test --gtest_color=yes AUTO) + add_dependencies(check aflplusplus_test) +endif() + +install(TARGETS protobuf-mutator-aflplusplus + EXPORT libprotobuf-mutatorTargets + ARCHIVE DESTINATION ${LIB_DIR} + LIBRARY DESTINATION ${LIB_DIR} + INCLUDES DESTINATION include/libprotobuf-mutator include/libprotobuf-mutator/src) diff --git a/src/aflplusplus/aflplusplus_macro.cc b/src/aflplusplus/aflplusplus_macro.cc new file mode 100644 index 0000000..abb3367 --- /dev/null +++ b/src/aflplusplus/aflplusplus_macro.cc @@ -0,0 +1,56 @@ +#include "src/aflplusplus/aflplusplus_macro.h" + +#include "src/aflplusplus/aflplusplus_mutator.h" +#include "src/mutation_io.h" +#include "src/mutator.h" + +namespace protobuf_mutator { +namespace aflplusplus { + +namespace { + +Mutator* AsMutator(void* data) { return static_cast(data); } + + +protobuf_mutator::Mutator* GetMutator() { + static protobuf_mutator::Mutator mutator; + return &mutator; +} + +} // namespace + +void* CreateMutator(void* afl, unsigned int seed, bool binary) { + return Mutator::Create(afl, seed, binary); +} + +void DestroyMutator(void* data) { delete AsMutator(data); } + +size_t CustomProtoFuzz(void* data, uint8_t* buf, size_t buf_size, + uint8_t** out_buf, uint8_t* add_buf, size_t add_buf_size, + size_t max_size, protobuf::Message* input1, + protobuf::Message* input2) { + return AsMutator(data)->Fuzz(buf, buf_size, out_buf, add_buf, add_buf_size, + max_size, input1, input2); +} + +int CustomProtoInitTrim(void* data, uint8_t* buf, size_t buf_size, + protobuf::Message* scratch) { + return AsMutator(data)->InitTrim(buf, buf_size, scratch); +} + +size_t CustomProtoTrim(void* data, uint8_t** out_buf) { + return AsMutator(data)->Trim(out_buf); +} + +int CustomProtoPostTrim(void* data, unsigned char success) { + return AsMutator(data)->PostTrim(success != 0); +} + +bool LoadProtoInput(bool binary, const uint8_t* data, size_t size, + protobuf::Message* input) { + return protobuf_mutator::LoadProtoInput(binary, data, size, + GetMutator(), input); +} + +} // namespace aflplusplus +} // namespace protobuf_mutator diff --git a/src/aflplusplus/aflplusplus_macro.h b/src/aflplusplus/aflplusplus_macro.h new file mode 100644 index 0000000..d268eed --- /dev/null +++ b/src/aflplusplus/aflplusplus_macro.h @@ -0,0 +1,107 @@ +#ifndef SRC_AFLPLUSPLUS_AFLPLUSPLUS_MACRO_H_ +#define SRC_AFLPLUSPLUS_AFLPLUSPLUS_MACRO_H_ + +#include +#include + +#include "port/protobuf.h" +#include "src/mutation_io.h" + +// Defines the whole afl-fuzz custom mutator interface for the given protobuf +// message type. Put this in a single translation unit, build it as a shared +// object and point AFL_CUSTOM_MUTATOR_LIBRARY at it. The fuzz target has to be +// defined with the matching macro below, text with text, binary with binary. +#define DEFINE_AFLPLUSPLUS_PROTO_MUTATOR_LIBRARY(Proto) \ + DEFINE_TEXT_AFLPLUSPLUS_PROTO_MUTATOR_LIBRARY(Proto) +// Text serialization is easier to read; binary makes mutations faster. +#define DEFINE_TEXT_AFLPLUSPLUS_PROTO_MUTATOR_LIBRARY(Proto) \ + DEFINE_AFLPLUSPLUS_PROTO_MUTATOR_LIBRARY_IMPL(false, Proto) +#define DEFINE_BINARY_AFLPLUSPLUS_PROTO_MUTATOR_LIBRARY(Proto) \ + DEFINE_AFLPLUSPLUS_PROTO_MUTATOR_LIBRARY_IMPL(true, Proto) + +// Defines the fuzz target entry point. +#define DEFINE_AFLPLUSPLUS_PROTO_FUZZER(arg) \ + DEFINE_TEXT_AFLPLUSPLUS_PROTO_FUZZER(arg) +#define DEFINE_TEXT_AFLPLUSPLUS_PROTO_FUZZER(arg) \ + DEFINE_AFLPLUSPLUS_PROTO_FUZZER_IMPL(false, arg) +#define DEFINE_BINARY_AFLPLUSPLUS_PROTO_FUZZER(arg) \ + DEFINE_AFLPLUSPLUS_PROTO_FUZZER_IMPL(true, arg) + +#define DEFINE_AFLPLUSPLUS_CUSTOM_INIT_IMPL(use_binary) \ + extern "C" void* afl_custom_init(void* afl, unsigned int seed) { \ + return protobuf_mutator::aflplusplus::CreateMutator(afl, seed, \ + use_binary); \ + } + +#define DEFINE_AFLPLUSPLUS_CUSTOM_DEINIT_IMPL \ + extern "C" void afl_custom_deinit(void* data) { \ + protobuf_mutator::aflplusplus::DestroyMutator(data); \ + } + +#define DEFINE_AFLPLUSPLUS_CUSTOM_FUZZ_IMPL(Proto) \ + extern "C" size_t afl_custom_fuzz( \ + void* data, unsigned char* buf, size_t buf_size, \ + unsigned char** out_buf, unsigned char* add_buf, size_t add_buf_size, \ + size_t max_size) { \ + Proto input1; \ + Proto input2; \ + return protobuf_mutator::aflplusplus::CustomProtoFuzz( \ + data, buf, buf_size, out_buf, add_buf, add_buf_size, max_size, \ + &input1, &input2); \ + } + +#define DEFINE_AFLPLUSPLUS_CUSTOM_TRIM_IMPL(Proto) \ + extern "C" int afl_custom_init_trim(void* data, unsigned char* buf, \ + size_t buf_size) { \ + Proto input; \ + return protobuf_mutator::aflplusplus::CustomProtoInitTrim( \ + data, buf, buf_size, &input); \ + } \ + extern "C" size_t afl_custom_trim(void* data, unsigned char** out_buf) { \ + return protobuf_mutator::aflplusplus::CustomProtoTrim(data, out_buf); \ + } \ + extern "C" int afl_custom_post_trim(void* data, unsigned char success) { \ + return protobuf_mutator::aflplusplus::CustomProtoPostTrim(data, \ + success); \ + } + +#define DEFINE_AFLPLUSPLUS_PROTO_MUTATOR_LIBRARY_IMPL(use_binary, Proto) \ + DEFINE_AFLPLUSPLUS_CUSTOM_INIT_IMPL(use_binary) \ + DEFINE_AFLPLUSPLUS_CUSTOM_FUZZ_IMPL(Proto) \ + DEFINE_AFLPLUSPLUS_CUSTOM_TRIM_IMPL(Proto) \ + DEFINE_AFLPLUSPLUS_CUSTOM_DEINIT_IMPL + +#define DEFINE_AFLPLUSPLUS_PROTO_FUZZER_IMPL(use_binary, arg) \ + static void TestOneProtoInput(arg); \ + using FuzzerProtoType = protobuf_mutator::macro_internal:: \ + GetFirstParam::type; \ + extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { \ + FuzzerProtoType input; \ + if (protobuf_mutator::aflplusplus::LoadProtoInput(use_binary, data, \ + size, &input)) \ + TestOneProtoInput(input); \ + return 0; \ + } \ + static void TestOneProtoInput(arg) + +namespace protobuf_mutator { +namespace aflplusplus { + +void* CreateMutator(void* afl, unsigned int seed, bool binary); +void DestroyMutator(void* data); +size_t CustomProtoFuzz(void* data, uint8_t* buf, size_t buf_size, + uint8_t** out_buf, uint8_t* add_buf, size_t add_buf_size, + size_t max_size, protobuf::Message* input1, + protobuf::Message* input2); +int CustomProtoInitTrim(void* data, uint8_t* buf, size_t buf_size, + protobuf::Message* scratch); +size_t CustomProtoTrim(void* data, uint8_t** out_buf); +int CustomProtoPostTrim(void* data, unsigned char success); + +bool LoadProtoInput(bool binary, const uint8_t* data, size_t size, + protobuf::Message* input); + +} // namespace aflplusplus +} // namespace protobuf_mutator + +#endif // SRC_AFLPLUSPLUS_AFLPLUSPLUS_MACRO_H_ diff --git a/src/aflplusplus/aflplusplus_mutate_wrapper.c b/src/aflplusplus/aflplusplus_mutate_wrapper.c new file mode 100644 index 0000000..0a89b3f --- /dev/null +++ b/src/aflplusplus/aflplusplus_mutate_wrapper.c @@ -0,0 +1,14 @@ +#include "src/aflplusplus/aflplusplus_mutate_wrapper.h" + +#include "config.h" +#include "types.h" +#include "afl-fuzz.h" +#include "afl-mutations.h" + +uint32_t aflplusplus_mutate_wrapper(void* afl, uint8_t* buf, uint32_t len, + uint32_t steps, int is_text, + int is_exploration, uint8_t* splice_buf, + uint32_t splice_len, uint32_t max_len) { + return afl_mutate((afl_state_t*)afl, buf, len, steps, is_text, is_exploration, + splice_buf, splice_len, max_len); +} diff --git a/src/aflplusplus/aflplusplus_mutate_wrapper.h b/src/aflplusplus/aflplusplus_mutate_wrapper.h new file mode 100644 index 0000000..3472f94 --- /dev/null +++ b/src/aflplusplus/aflplusplus_mutate_wrapper.h @@ -0,0 +1,20 @@ +#ifndef SRC_AFLPLUSPLUS_AFLPLUSPLUS_MUTATE_WRAPPER_H_ +#define SRC_AFLPLUSPLUS_AFLPLUSPLUS_MUTATE_WRAPPER_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +uint32_t aflplusplus_mutate_wrapper(void* afl, uint8_t* buf, uint32_t len, + uint32_t steps, int is_text, + int is_exploration, uint8_t* splice_buf, + uint32_t splice_len, uint32_t max_len); + +#ifdef __cplusplus +} +#endif + +#endif // SRC_AFLPLUSPLUS_AFLPLUSPLUS_MUTATE_WRAPPER_H_ diff --git a/src/aflplusplus/aflplusplus_mutator.cc b/src/aflplusplus/aflplusplus_mutator.cc new file mode 100644 index 0000000..07682e5 --- /dev/null +++ b/src/aflplusplus/aflplusplus_mutator.cc @@ -0,0 +1,471 @@ +#include "src/aflplusplus/aflplusplus_mutator.h" + +#if defined(__has_feature) +#if __has_feature(memory_sanitizer) +#include +#endif +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "afl/alloc-inl.h" +#include "afl/config.h" +#include "port/protobuf.h" +#include "src/aflplusplus/aflplusplus_mutate_wrapper.h" +#include "src/mutation_io.h" + +#define DEFINE_MUTATE_METHOD(Type, Name) \ + Type Mutator::Mutate##Name(Type value) { \ + return MutateValue(value, afl_, this); \ + } + +namespace protobuf_mutator { +namespace aflplusplus { + +namespace { + +using protobuf::Descriptor; +using protobuf::FieldDescriptor; +using protobuf::Message; +using protobuf::Reflection; + +constexpr size_t kNoUnit = static_cast(-1); +constexpr size_t kTrimGiveUpMin = 32; + +class TrimWalker { + public: + explicit TrimWalker(size_t target) : target_(target) {} + + bool Walk(Message* message) { + const Descriptor* descriptor = message->GetDescriptor(); + const Reflection* reflection = message->GetReflection(); + for (int i = 0; i < descriptor->field_count(); ++i) { + const FieldDescriptor* field = descriptor->field(i); + const bool is_message = + field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE; + if (field->is_repeated()) { + for (int j = 0; j < reflection->FieldSize(*message, field); ++j) { + if (Take()) { + RemoveRepeated(reflection, message, field, j); + return true; + } + if (is_message && + Walk(reflection->MutableRepeatedMessage(message, field, j))) + return true; + } + } else if (reflection->HasField(*message, field)) { + if (!field->is_required() && Take()) { + reflection->ClearField(message, field); + return true; + } + if (is_message && Walk(reflection->MutableMessage(message, field))) + return true; + } + } + return false; + } + + size_t count() const { return index_; } + + private: + bool Take() { return index_++ == target_; } + + static void RemoveRepeated(const Reflection* reflection, Message* message, + const FieldDescriptor* field, int index) { + const int field_size = reflection->FieldSize(*message, field); + for (int i = index + 1; i < field_size; ++i) + reflection->SwapElements(message, field, i, i - 1); + reflection->RemoveLast(message, field); + } + + const size_t target_; + size_t index_ = 0; +}; + +size_t CountTrimUnits(Message* message) { + TrimWalker walker(kNoUnit); + walker.Walk(message); + return walker.count(); +} + +class ValueWalker { + public: + explicit ValueWalker(size_t target) : target_(target) {} + + bool Walk(Message* message) { + const Descriptor* descriptor = message->GetDescriptor(); + const Reflection* reflection = message->GetReflection(); + for (int i = 0; i < descriptor->field_count(); ++i) { + const FieldDescriptor* field = descriptor->field(i); + const bool is_string = + field->cpp_type() == FieldDescriptor::CPPTYPE_STRING; + const bool is_message = + field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE; + if (field->is_repeated()) { + for (int j = 0; j < reflection->FieldSize(*message, field); ++j) { + if (is_string) { + if (Take(message, field, j)) return true; + } else if (is_message && + Walk(reflection->MutableRepeatedMessage(message, field, + j))) { + return true; + } + } + } else if (reflection->HasField(*message, field)) { + if (is_string) { + if (Take(message, field, -1)) return true; + } else if (is_message && + Walk(reflection->MutableMessage(message, field))) { + return true; + } + } + } + return false; + } + + size_t count() const { return index_; } + + std::string Get() const { + const Reflection* reflection = message_->GetReflection(); + return element_ < 0 + ? reflection->GetString(*message_, field_) + : reflection->GetRepeatedString(*message_, field_, element_); + } + + void Set(const std::string& value) const { + const Reflection* reflection = message_->GetReflection(); + if (element_ < 0) + reflection->SetString(message_, field_, value); + else + reflection->SetRepeatedString(message_, field_, element_, value); + } + + bool EnforceUtf8() const { return RequiresUtf8Validation(*field_); } + + private: + bool Take(Message* message, const FieldDescriptor* field, int element) { + if (index_++ != target_) return false; + message_ = message; + field_ = field; + element_ = element; + return true; + } + + const size_t target_; + size_t index_ = 0; + Message* message_ = nullptr; + const FieldDescriptor* field_ = nullptr; + int element_ = -1; +}; + +size_t CountTrimValues(Message* message) { + ValueWalker walker(kNoUnit); + walker.Walk(message); + return walker.count(); +} + +size_t NextPow2(size_t value) { + size_t result = 1; + while (result < value) result <<= 1; + return result; +} + +size_t ShrinkStart(size_t length) { + return std::max(NextPow2(length) / TRIM_START_STEPS, TRIM_MIN_BYTES); +} + +size_t ShrinkEnd(size_t length) { + return std::max(NextPow2(length) / TRIM_END_STEPS, TRIM_MIN_BYTES); +} + +size_t CountShrinkSteps(Message* message) { + const size_t values = CountTrimValues(message); + size_t steps = 0; + for (size_t i = 0; i < values; ++i) { + ValueWalker walker(i); + if (!walker.Walk(message)) break; + const size_t length = walker.Get().size(); + const size_t end = ShrinkEnd(length); + for (size_t len = ShrinkStart(length); len >= end; len >>= 1) + for (size_t pos = len; pos < length; pos += len) ++steps; + } + return steps; +} + +constexpr int kMaxNesting = 100; + +bool TooDeep(const Message& message, int budget) { + if (budget < 0) return true; + const Reflection* reflection = message.GetReflection(); + std::vector fields; + reflection->ListFields(message, &fields); + for (const FieldDescriptor* field : fields) { + if (field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) continue; + if (field->is_repeated()) { + for (int i = 0; i < reflection->FieldSize(message, field); ++i) { + if (TooDeep(reflection->GetRepeatedMessage(message, field, i), + budget - 1)) + return true; + } + } else if (TooDeep(reflection->GetMessage(message, field), budget - 1)) { + return true; + } + } + return false; +} + +int ClampToInt(size_t value) { + return static_cast( + std::min(value, static_cast(INT_MAX))); +} + +template +T MutateValue(T v, void* afl, Mutator* mutator) { + size_t size = aflplusplus_mutate_wrapper( + afl, reinterpret_cast(&v), sizeof(v), mutator->GetRandom(1, 16), + 0 /*is_text*/, mutator->GetRandom(0, 1) /*is_exploration*/, nullptr, 0, + sizeof(v)); + + if (size < sizeof(v)) + memset(reinterpret_cast(&v) + size, 0, sizeof(v) - size); + +#if defined(__has_feature) +#if __has_feature(memory_sanitizer) + __msan_unpoison(&v, sizeof(v)); +#endif +#endif + + return v; +} + +} // namespace + +Mutator* Mutator::Create(void* afl_state, unsigned int seed, bool binary) { + const char* only = getenv("AFL_CUSTOM_MUTATOR_ONLY"); + if (!only || !*only) { + fprintf(stderr, + "[-] protobuf-mutator-aflplusplus: set AFL_CUSTOM_MUTATOR_ONLY=1, " + "the other mutation stages produce inputs this mutator cannot " + "parse.\n"); + exit(1); + } + + std::unique_ptr mutator(new Mutator(afl_state, binary)); + if (!mutator->Reserve(MAX_FILE)) { + perror("afl_custom_init"); + return nullptr; + } + mutator->Seed(seed); + return mutator.release(); +} + +Mutator::Mutator(void* afl_state, bool binary) + : afl_(afl_state), binary_(binary) {} + +Mutator::~Mutator() { + if (out_buf_) afl_free(out_buf_); + if (trim_buf_) afl_free(trim_buf_); +} + +bool Mutator::Reserve(size_t size) { + return afl_realloc(reinterpret_cast(&out_buf_), + std::max(size, MAX_FILE)) != nullptr; +} + +size_t Mutator::CopyToTrimBuffer(const std::string& data, uint8_t** out_buf) { + if (!afl_realloc(reinterpret_cast(&trim_buf_), + std::max(data.size(), 1))) + return 0; + memcpy(trim_buf_, data.data(), data.size()); + *out_buf = trim_buf_; + return data.size(); +} + +size_t Mutator::Fuzz(const uint8_t* buf, size_t buf_size, uint8_t** out_buf, + const uint8_t* add_buf, size_t add_buf_size, + size_t max_size, protobuf::Message* input1, + protobuf::Message* input2) { + *out_buf = const_cast(buf); + + size_t new_size = 0; + if (!add_buf || !add_buf_size || GetRandom(1, 10) <= 5) { + memcpy(out_buf_, buf, buf_size); + new_size = + MutateMessage(binary_, this, out_buf_, buf_size, max_size, input1); + } else { + new_size = CrossOverMessages(binary_, this, buf, buf_size, add_buf, + add_buf_size, out_buf_, max_size, input1, + input2); + } + + if (!new_size) return 0; + if (GetCache()->LoadIfSame(out_buf_, new_size, input1)) { + if (TooDeep(*input1, kMaxNesting)) return 0; + GetCache()->Store(out_buf_, new_size, input1); + } + + *out_buf = out_buf_; + return new_size; +} + +int Mutator::InitTrim(const uint8_t* buf, size_t buf_size, + protobuf::Message* scratch) { + trim_index_ = 0; + trim_steps_ = 0; + trim_progress_ = 0; + trim_give_up_ = 0; + trim_had_success_ = false; + trim_exhausted_ = false; + shrinking_ = false; + shrink_index_ = 0; + shrink_remove_len_ = 0; + shrink_min_len_ = 0; + shrink_remove_pos_ = 0; + if (!ParseMessage(binary_, buf, buf_size, scratch)) return 0; + + if (!trim_message_) trim_message_.reset(scratch->New()); + if (!trim_candidate_) trim_candidate_.reset(scratch->New()); + trim_message_->GetReflection()->Swap(trim_message_.get(), scratch); + + trim_serialized_ = SaveMessage(binary_, *trim_message_); + if (trim_serialized_.empty()) return 0; + if (trim_serialized_.size() > buf_size) + trim_serialized_.assign(reinterpret_cast(buf), buf_size); + + trim_steps_ = CountTrimUnits(trim_message_.get()) + + CountShrinkSteps(trim_message_.get()); + trim_give_up_ = std::max(kTrimGiveUpMin, trim_steps_ / 4); + return ClampToInt(trim_steps_); +} + +bool Mutator::BuildDropCandidate() { + if (shrinking_) return false; + trim_candidate_->CopyFrom(*trim_message_); + TrimWalker walker(trim_index_); + if (walker.Walk(trim_candidate_.get())) return true; + shrinking_ = true; + return false; +} + +bool Mutator::BuildShrinkCandidate() { + const size_t values = CountTrimValues(trim_message_.get()); + while (shrink_index_ < values) { + ValueWalker walker(shrink_index_); + if (!walker.Walk(trim_message_.get())) break; + + const std::string value = walker.Get(); + if (!shrink_remove_len_) { + shrink_remove_len_ = ShrinkStart(value.size()); + shrink_min_len_ = ShrinkEnd(value.size()); + shrink_remove_pos_ = shrink_remove_len_; + } + + while (shrink_remove_len_ >= shrink_min_len_) { + if (shrink_remove_pos_ >= value.size()) { + shrink_remove_len_ >>= 1; + shrink_remove_pos_ = shrink_remove_len_; + continue; + } + + const size_t cut = + std::min(shrink_remove_len_, value.size() - shrink_remove_pos_); + std::string shorter = value.substr(0, shrink_remove_pos_) + + value.substr(shrink_remove_pos_ + cut); + if (walker.EnforceUtf8() && !IsValidUtf8(shorter)) { + shrink_remove_pos_ += shrink_remove_len_; + continue; + } + + trim_candidate_->CopyFrom(*trim_message_); + ValueWalker setter(shrink_index_); + if (!setter.Walk(trim_candidate_.get())) return false; + setter.Set(shorter); + return true; + } + + ++shrink_index_; + shrink_remove_len_ = 0; + } + return false; +} + +size_t Mutator::Trim(uint8_t** out_buf) { + trim_candidate_serialized_.clear(); + if (trim_message_ && !trim_exhausted_) { + if (BuildDropCandidate() || BuildShrinkCandidate()) { + trim_candidate_serialized_ = SaveMessage(binary_, *trim_candidate_); + if (trim_candidate_serialized_.size() >= trim_serialized_.size()) + trim_candidate_serialized_.clear(); + } else { + trim_exhausted_ = true; + } + } + + if (trim_candidate_serialized_.empty()) + return CopyToTrimBuffer(trim_serialized_, out_buf); + return CopyToTrimBuffer(trim_candidate_serialized_, out_buf); +} + +int Mutator::PostTrim(bool success) { + if (success && !trim_candidate_serialized_.empty()) { + trim_message_->GetReflection()->Swap(trim_message_.get(), + trim_candidate_.get()); + trim_serialized_.swap(trim_candidate_serialized_); + trim_had_success_ = true; + } else if (shrinking_) { + shrink_remove_pos_ += shrink_remove_len_; + } else { + ++trim_index_; + } + + ++trim_progress_; + if (!trim_had_success_ && trim_progress_ >= trim_give_up_) + trim_exhausted_ = true; + + if (!trim_message_ || trim_exhausted_ || trim_progress_ >= trim_steps_) + return ClampToInt(trim_steps_); + return ClampToInt(trim_progress_); +} + +int64_t Mutator::GetRandom(size_t min, size_t max) { + return std::uniform_int_distribution(min, max)(*random()); +} + +DEFINE_MUTATE_METHOD(int32_t, Int32) +DEFINE_MUTATE_METHOD(int64_t, Int64) +DEFINE_MUTATE_METHOD(uint32_t, UInt32) +DEFINE_MUTATE_METHOD(uint64_t, UInt64) +DEFINE_MUTATE_METHOD(float, Float) +DEFINE_MUTATE_METHOD(double, Double) + +std::string Mutator::MutateString(const std::string& value, + int size_increase_hint) { + if (!std::uniform_int_distribution(0, 20)(*random())) return {}; + + std::string result = value; + int new_size = static_cast(value.size()) + size_increase_hint; + result.resize(std::max(1, new_size)); + + result.resize(aflplusplus_mutate_wrapper( + afl_, reinterpret_cast(&result[0]), + value.size() ? value.size() : 1, GetRandom(1, 16), 0 /*is_text*/, + GetRandom(0, 1) /*is_exploration*/, nullptr, 0, result.size())); + +#if defined(__has_feature) +#if __has_feature(memory_sanitizer) + __msan_unpoison(&result[0], result.size()); +#endif +#endif + return result; +} + +} // namespace aflplusplus +} // namespace protobuf_mutator diff --git a/src/aflplusplus/aflplusplus_mutator.h b/src/aflplusplus/aflplusplus_mutator.h new file mode 100644 index 0000000..e625b8e --- /dev/null +++ b/src/aflplusplus/aflplusplus_mutator.h @@ -0,0 +1,75 @@ +#ifndef SRC_AFLPLUSPLUS_AFLPLUSPLUS_MUTATOR_H_ +#define SRC_AFLPLUSPLUS_AFLPLUSPLUS_MUTATOR_H_ + +#include +#include + +#include +#include + +#include "port/protobuf.h" +#include "src/mutator.h" + +namespace protobuf_mutator { +namespace aflplusplus { + +class Mutator : public protobuf_mutator::Mutator { + public: + static Mutator* Create(void* afl_state, unsigned int seed, bool binary); + + Mutator(void* afl_state, bool binary); + ~Mutator() override; + + size_t Fuzz(const uint8_t* buf, size_t buf_size, uint8_t** out_buf, + const uint8_t* add_buf, size_t add_buf_size, size_t max_size, + protobuf::Message* input1, protobuf::Message* input2); + + + int InitTrim(const uint8_t* buf, size_t buf_size, protobuf::Message* scratch); + size_t Trim(uint8_t** out_buf); + int PostTrim(bool success); + + int64_t GetRandom(size_t min, size_t max); + + protected: + int32_t MutateInt32(int32_t value) override; + int64_t MutateInt64(int64_t value) override; + uint32_t MutateUInt32(uint32_t value) override; + uint64_t MutateUInt64(uint64_t value) override; + float MutateFloat(float value) override; + double MutateDouble(double value) override; + std::string MutateString(const std::string& value, + int size_increase_hint) override; + + private: + bool Reserve(size_t size); + size_t CopyToTrimBuffer(const std::string& data, uint8_t** out_buf); + bool BuildDropCandidate(); + bool BuildShrinkCandidate(); + + void* afl_ = nullptr; + bool binary_ = false; + uint8_t* out_buf_ = nullptr; + uint8_t* trim_buf_ = nullptr; + + std::unique_ptr trim_message_; + std::unique_ptr trim_candidate_; + std::string trim_serialized_; + std::string trim_candidate_serialized_; + size_t trim_index_ = 0; + size_t trim_steps_ = 0; + size_t trim_progress_ = 0; + size_t trim_give_up_ = 0; + bool trim_had_success_ = false; + bool trim_exhausted_ = false; + bool shrinking_ = false; + size_t shrink_index_ = 0; + size_t shrink_remove_len_ = 0; + size_t shrink_min_len_ = 0; + size_t shrink_remove_pos_ = 0; +}; + +} // namespace aflplusplus +} // namespace protobuf_mutator + +#endif // SRC_AFLPLUSPLUS_AFLPLUSPLUS_MUTATOR_H_ diff --git a/src/aflplusplus/aflplusplus_test.cc b/src/aflplusplus/aflplusplus_test.cc new file mode 100644 index 0000000..7db7173 --- /dev/null +++ b/src/aflplusplus/aflplusplus_test.cc @@ -0,0 +1,260 @@ +#include + +#include +#include +#include + +#include "port/gtest.h" +#include "port/protobuf.h" +#include "src/aflplusplus/aflplusplus_macro.h" +#include "src/mutation_io.h" +#include "src/mutator.h" +#include "src/mutator_test_proto2.pb.h" + +extern "C" { +#include "config.h" +#include "types.h" +#include "afl-fuzz.h" +} + +using ::testing::StrictMock; + +static class MockFuzzer* mock_fuzzer; + +class MockFuzzer { + public: + MockFuzzer() { mock_fuzzer = this; } + ~MockFuzzer() { mock_fuzzer = nullptr; } + MOCK_METHOD(void, TestOneInput, (const protobuf_mutator::Msg& message)); +}; + +DEFINE_TEXT_AFLPLUSPLUS_PROTO_MUTATOR_LIBRARY(protobuf_mutator::Msg) + +DEFINE_TEXT_AFLPLUSPLUS_PROTO_FUZZER(const protobuf_mutator::Msg& message) { + mock_fuzzer->TestOneInput(message); +} + +MATCHER(IsInitialized, "") { return arg.IsInitialized(); } + +namespace { + +std::string MakeMessage() { + protobuf_mutator::Msg message; + message.set_optional_int32(42); + message.set_optional_string("trim me"); + message.add_repeated_int32(1); + message.add_repeated_int32(2); + message.add_repeated_string("first"); + message.add_repeated_msg()->set_optional_int64(3); + + protobuf_mutator::Mutator fixer; + fixer.Seed(1); + fixer.Fix(&message); + + return protobuf_mutator::SaveMessage(false, message); +} + +class AflplusplusTest : public testing::Test { + protected: + void SetUp() override { + setenv("AFL_CUSTOM_MUTATOR_ONLY", "1", 1); + + afl_.reset(new afl_state_t()); + afl_->fixed_seed = 1; + rand_set_seed(afl_.get(), 1); + + mutator_ = afl_custom_init(afl_.get(), 7); + ASSERT_TRUE(mutator_); + } + + void TearDown() override { afl_custom_deinit(mutator_); } + + size_t Fuzz(unsigned char* buf, size_t buf_size, unsigned char** out_buf, + unsigned char* add_buf, size_t add_buf_size, size_t max_size) { + for (int i = 0; i < 100; ++i) { + if (size_t size = afl_custom_fuzz(mutator_, buf, buf_size, out_buf, + add_buf, add_buf_size, max_size)) + return size; + } + return 0; + } + + std::unique_ptr afl_; + void* mutator_ = nullptr; +}; + +TEST_F(AflplusplusTest, LLVMFuzzerTestOneInput) { + StrictMock mock; + EXPECT_CALL(mock, TestOneInput(IsInitialized())); + LLVMFuzzerTestOneInput(reinterpret_cast(""), 0); +} + +TEST_F(AflplusplusTest, AflCustomFuzz) { + StrictMock mock; + EXPECT_CALL(mock, TestOneInput(IsInitialized())); + + unsigned char buf[4096] = {}; + unsigned char* out = nullptr; + size_t size = Fuzz(buf, 0, &out, nullptr, 0, sizeof(buf)); + ASSERT_GT(size, 0U); + ASSERT_TRUE(out); + LLVMFuzzerTestOneInput(out, size); +} + +TEST_F(AflplusplusTest, AflCustomFuzzWithSpliceBuffer) { + const int kRuns = 20; + std::string seed = MakeMessage(); + std::string splice = MakeMessage(); + + StrictMock mock; + EXPECT_CALL(mock, TestOneInput(IsInitialized())).Times(kRuns); + + std::vector buf(seed.begin(), seed.end()); + buf.resize(4096); + for (int i = 0; i < kRuns; ++i) { + unsigned char* out = nullptr; + size_t size = + Fuzz(buf.data(), seed.size(), &out, + reinterpret_cast(&splice[0]), splice.size(), + buf.size()); + ASSERT_GT(size, 0U); + ASSERT_TRUE(out); + LLVMFuzzerTestOneInput(out, size); + } +} + +TEST_F(AflplusplusTest, AflCustomTrim) { + std::string data = MakeMessage(); + std::vector buf(data.begin(), data.end()); + + int steps = afl_custom_init_trim(mutator_, buf.data(), buf.size()); + ASSERT_GT(steps, 0); + + unsigned char* out = nullptr; + size_t size = afl_custom_trim(mutator_, &out); + ASSERT_TRUE(out); + ASSERT_GT(size, 0U); + EXPECT_LT(size, buf.size()); + + int next = afl_custom_post_trim(mutator_, 1); + EXPECT_GE(next, 0); + EXPECT_LE(next, steps); +} + +TEST_F(AflplusplusTest, AflCustomTrimTerminates) { + std::string data = MakeMessage(); + std::vector buf(data.begin(), data.end()); + + int steps = afl_custom_init_trim(mutator_, buf.data(), buf.size()); + ASSERT_GT(steps, 0); + + size_t previous = buf.size(); + int cur = 0; + int iterations = 0; + while (cur < steps) { + unsigned char* out = nullptr; + size_t size = afl_custom_trim(mutator_, &out); + ASSERT_TRUE(out); + ASSERT_GT(size, 0U); + EXPECT_LE(size, previous); + previous = size; + + cur = afl_custom_post_trim(mutator_, 1); + ASSERT_GE(cur, 0); + ASSERT_LE(++iterations, steps); + } + EXPECT_LT(previous, buf.size()); +} + +TEST_F(AflplusplusTest, AflCustomTrimRejectsEverything) { + std::string data = MakeMessage(); + std::vector buf(data.begin(), data.end()); + + int steps = afl_custom_init_trim(mutator_, buf.data(), buf.size()); + ASSERT_GT(steps, 0); + + int cur = 0; + int iterations = 0; + while (cur < steps) { + unsigned char* out = nullptr; + ASSERT_GT(afl_custom_trim(mutator_, &out), 0U); + ASSERT_TRUE(out); + cur = afl_custom_post_trim(mutator_, 0); + ASSERT_GE(cur, 0); + ASSERT_LE(++iterations, steps); + } + + unsigned char* out = nullptr; + size_t size = afl_custom_trim(mutator_, &out); + ASSERT_TRUE(out); + EXPECT_EQ(data, std::string(reinterpret_cast(out), size)); +} + +TEST_F(AflplusplusTest, AflCustomFuzzDropsTooDeepMutations) { + protobuf_mutator::Msg message; + protobuf_mutator::Msg* deepest = &message; + for (int i = 0; i < 100; ++i) deepest = deepest->mutable_optional_msg(); + std::string seed = protobuf_mutator::SaveMessage(false, message); + + const int kRuns = 200; + std::vector buf(seed.begin(), seed.end()); + buf.resize(1 << 20); + + int dropped = 0; + for (int i = 0; i < kRuns; ++i) { + unsigned char* out = nullptr; + size_t size = afl_custom_fuzz(mutator_, buf.data(), seed.size(), &out, + nullptr, 0, buf.size()); + if (!size) { + ++dropped; + continue; + } + ASSERT_TRUE(out); + protobuf_mutator::Msg parsed; + EXPECT_TRUE(protobuf_mutator::ParseMessage(false, out, size, &parsed)) + << "mutation " << i << " of " << size << " bytes does not parse back"; + } + + EXPECT_GT(dropped, 0); +} + +TEST_F(AflplusplusTest, AflCustomTrimShrinksStringValues) { + protobuf_mutator::Msg message; + message.set_optional_string("keep" + std::string(500, 'x')); + protobuf_mutator::Mutator fixer; + fixer.Seed(1); + fixer.Fix(&message); + const std::string data = protobuf_mutator::SaveMessage(false, message); + + std::vector buf(data.begin(), data.end()); + int steps = afl_custom_init_trim(mutator_, buf.data(), buf.size()); + ASSERT_GT(steps, 0); + + std::string last = data; + int cur = 0; + int iterations = 0; + while (cur < steps) { + unsigned char* out = nullptr; + size_t size = afl_custom_trim(mutator_, &out); + ASSERT_TRUE(out); + + protobuf_mutator::Msg candidate; + const bool keeps_prefix = + protobuf_mutator::ParseMessage(false, out, size, &candidate) && + candidate.optional_string().compare(0, 4, "keep") == 0; + if (keeps_prefix) last.assign(reinterpret_cast(out), size); + + cur = afl_custom_post_trim(mutator_, keeps_prefix); + ASSERT_GE(cur, 0); + ASSERT_LT(++iterations, 100000); + } + + protobuf_mutator::Msg trimmed; + ASSERT_TRUE(protobuf_mutator::ParseMessage( + false, reinterpret_cast(last.data()), last.size(), + &trimmed)); + EXPECT_EQ("keep", trimmed.optional_string()); + EXPECT_LT(last.size(), data.size()); +} + +} // namespace diff --git a/src/field_instance.h b/src/field_instance.h index 5b0df7f..7db12c2 100644 --- a/src/field_instance.h +++ b/src/field_instance.h @@ -171,9 +171,7 @@ class ConstFieldInstance { bool CanStore(const std::string& value) const { if (!EnforceUtf8()) return true; - using protobuf::internal::WireFormatLite; - return WireFormatLite::VerifyUtf8String(value.data(), value.length(), - WireFormatLite::PARSE, ""); + return IsValidUtf8(value); } std::string name() const { return std::string(descriptor_->name()); } diff --git a/src/libfuzzer/libfuzzer_macro.cc b/src/libfuzzer/libfuzzer_macro.cc index 2004df6..11c2699 100644 --- a/src/libfuzzer/libfuzzer_macro.cc +++ b/src/libfuzzer/libfuzzer_macro.cc @@ -14,200 +14,28 @@ #include "src/libfuzzer/libfuzzer_macro.h" -#include -#include -#include +#include -#include "src/binary_format.h" #include "src/libfuzzer/libfuzzer_mutator.h" -#include "src/text_format.h" +#include "src/mutation_io.h" namespace protobuf_mutator { namespace libfuzzer { namespace { -class InputReader { - public: - InputReader(const uint8_t* data, size_t size) : data_(data), size_(size) {} - virtual ~InputReader() = default; - - virtual bool Read(protobuf::Message* message) const = 0; - - const uint8_t* data() const { return data_; } - size_t size() const { return size_; } - - private: - const uint8_t* data_; - size_t size_; -}; - -class OutputWriter { - public: - OutputWriter(uint8_t* data, size_t size) : data_(data), size_(size) {} - virtual ~OutputWriter() = default; - - virtual size_t Write(const protobuf::Message& message) = 0; - - uint8_t* data() const { return data_; } - size_t size() const { return size_; } - - private: - uint8_t* data_; - size_t size_; -}; - -class TextInputReader : public InputReader { - public: - using InputReader::InputReader; - - bool Read(protobuf::Message* message) const override { - return ParseTextMessage(data(), size(), message); - } -}; - -class TextOutputWriter : public OutputWriter { - public: - using OutputWriter::OutputWriter; - - size_t Write(const protobuf::Message& message) override { - return SaveMessageAsText(message, data(), size()); - } -}; - -class BinaryInputReader : public InputReader { - public: - using InputReader::InputReader; - - bool Read(protobuf::Message* message) const override { - return ParseBinaryMessage(data(), size(), message); - } -}; - -class BinaryOutputWriter : public OutputWriter { - public: - using OutputWriter::OutputWriter; - - size_t Write(const protobuf::Message& message) override { - return SaveMessageAsBinary(message, data(), size()); - } -}; - -class LastMutationCache { - public: - void Store(const uint8_t* data, size_t size, protobuf::Message* message) { - if (!message_) message_.reset(message->New()); - message->GetReflection()->Swap(message, message_.get()); - data_.assign(data, data + size); - } - - bool LoadIfSame(const uint8_t* data, size_t size, - protobuf::Message* message) { - if (!message_ || size != data_.size() || - !std::equal(data_.begin(), data_.end(), data)) - return false; - - message->GetReflection()->Swap(message, message_.get()); - message_.reset(); - return true; - } - - private: - std::vector data_; - std::unique_ptr message_; -}; - -LastMutationCache* GetCache() { - static LastMutationCache cache; - return &cache; -} - Mutator* GetMutator() { static Mutator mutator; return &mutator; } -size_t GetMaxSize(const InputReader& input, const OutputWriter& output, - const protobuf::Message& message) { - size_t max_size = message.ByteSizeLong() + output.size(); - max_size -= std::min(max_size, input.size()); - return max_size; -} - -size_t MutateMessage(unsigned int seed, const InputReader& input, - OutputWriter* output, protobuf::Message* message) { - GetMutator()->Seed(seed); - input.Read(message); - size_t max_size = GetMaxSize(input, *output, *message); - GetMutator()->Mutate(message, max_size); - if (size_t new_size = output->Write(*message)) { - assert(new_size <= output->size()); - GetCache()->Store(output->data(), new_size, message); - return new_size; - } - return 0; -} - -size_t CrossOverMessages(unsigned int seed, const InputReader& input1, - const InputReader& input2, OutputWriter* output, - protobuf::Message* message1, - protobuf::Message* message2) { - GetMutator()->Seed(seed); - input1.Read(message1); - input2.Read(message2); - size_t max_size = GetMaxSize(input1, *output, *message1); - GetMutator()->CrossOver(*message2, message1, max_size); - if (size_t new_size = output->Write(*message1)) { - assert(new_size <= output->size()); - GetCache()->Store(output->data(), new_size, message1); - return new_size; - } - return 0; -} - -size_t MutateTextMessage(uint8_t* data, size_t size, size_t max_size, - unsigned int seed, protobuf::Message* message) { - TextInputReader input(data, size); - TextOutputWriter output(data, max_size); - return MutateMessage(seed, input, &output, message); -} - -size_t CrossOverTextMessages(const uint8_t* data1, size_t size1, - const uint8_t* data2, size_t size2, uint8_t* out, - size_t max_out_size, unsigned int seed, - protobuf::Message* message1, - protobuf::Message* message2) { - TextInputReader input1(data1, size1); - TextInputReader input2(data2, size2); - TextOutputWriter output(out, max_out_size); - return CrossOverMessages(seed, input1, input2, &output, message1, message2); -} - -size_t MutateBinaryMessage(uint8_t* data, size_t size, size_t max_size, - unsigned int seed, protobuf::Message* message) { - BinaryInputReader input(data, size); - BinaryOutputWriter output(data, max_size); - return MutateMessage(seed, input, &output, message); -} - -size_t CrossOverBinaryMessages(const uint8_t* data1, size_t size1, - const uint8_t* data2, size_t size2, uint8_t* out, - size_t max_out_size, unsigned int seed, - protobuf::Message* message1, - protobuf::Message* message2) { - BinaryInputReader input1(data1, size1); - BinaryInputReader input2(data2, size2); - BinaryOutputWriter output(out, max_out_size); - return CrossOverMessages(seed, input1, input2, &output, message1, message2); -} - } // namespace size_t CustomProtoMutator(bool binary, uint8_t* data, size_t size, size_t max_size, unsigned int seed, protobuf::Message* input) { - auto mutate = binary ? &MutateBinaryMessage : &MutateTextMessage; - return mutate(data, size, max_size, seed, input); + GetMutator()->Seed(seed); + return MutateMessage(binary, GetMutator(), data, size, max_size, input); } size_t CustomProtoCrossOver(bool binary, const uint8_t* data1, size_t size1, @@ -215,20 +43,15 @@ size_t CustomProtoCrossOver(bool binary, const uint8_t* data1, size_t size1, size_t max_out_size, unsigned int seed, protobuf::Message* input1, protobuf::Message* input2) { - auto cross = binary ? &CrossOverBinaryMessages : &CrossOverTextMessages; - return cross(data1, size1, data2, size2, out, max_out_size, seed, input1, - input2); + GetMutator()->Seed(seed); + return CrossOverMessages(binary, GetMutator(), data1, size1, data2, size2, + out, max_out_size, input1, input2); } bool LoadProtoInput(bool binary, const uint8_t* data, size_t size, protobuf::Message* input) { - if (GetCache()->LoadIfSame(data, size, input)) return true; - auto result = binary ? ParseBinaryMessage(data, size, input) - : ParseTextMessage(data, size, input); - if (!result) return false; - GetMutator()->Seed(size); - GetMutator()->Fix(input); - return true; + return protobuf_mutator::LoadProtoInput(binary, data, size, GetMutator(), + input); } void RegisterPostProcessor( diff --git a/src/libfuzzer/libfuzzer_macro.h b/src/libfuzzer/libfuzzer_macro.h index 8907326..2e86c81 100644 --- a/src/libfuzzer/libfuzzer_macro.h +++ b/src/libfuzzer/libfuzzer_macro.h @@ -19,9 +19,9 @@ #include #include -#include #include "port/protobuf.h" +#include "src/mutation_io.h" // Defines custom mutator, crossover and test functions using default // serialization format. Default is text. @@ -84,9 +84,8 @@ #define DEFINE_PROTO_FUZZER_IMPL(use_binary, arg) \ static void TestOneProtoInput(arg); \ - using FuzzerProtoType = \ - protobuf_mutator::libfuzzer::macro_internal::GetFirstParam< \ - decltype(&TestOneProtoInput)>::type; \ + using FuzzerProtoType = protobuf_mutator::macro_internal:: \ + GetFirstParam::type; \ DEFINE_CUSTOM_PROTO_MUTATOR_IMPL(use_binary, FuzzerProtoType) \ DEFINE_CUSTOM_PROTO_CROSSOVER_IMPL(use_binary, FuzzerProtoType) \ DEFINE_TEST_ONE_PROTO_INPUT_IMPL(use_binary, FuzzerProtoType) \ @@ -124,19 +123,6 @@ struct PostProcessorRegistration { } }; -namespace macro_internal { - -template -struct GetFirstParam; - -template -struct GetFirstParam { - using type = typename std::remove_const< - typename std::remove_reference::type>::type; -}; - -} // namespace macro_internal - } // namespace libfuzzer } // namespace protobuf_mutator diff --git a/src/mutation_io.cc b/src/mutation_io.cc new file mode 100644 index 0000000..567f2af --- /dev/null +++ b/src/mutation_io.cc @@ -0,0 +1,191 @@ +// Copyright 2017 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/mutation_io.h" + +#include +#include + +#include "src/binary_format.h" +#include "src/text_format.h" + +#ifdef LIB_PROTO_MUTATOR_QUIET +#if GOOGLE_PROTOBUF_VERSION >= 4022000 +#include "absl/base/log_severity.h" +#include "absl/log/globals.h" +#else +#include "google/protobuf/stubs/logging.h" +#endif +#endif + +namespace protobuf_mutator { + +#ifdef LIB_PROTO_MUTATOR_QUIET +namespace { + +bool SilenceProtobufLog() { +#if GOOGLE_PROTOBUF_VERSION >= 4022000 + absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfinity); +#else + protobuf::SetLogHandler(nullptr); +#endif + return true; +} + +bool protobuf_log_silenced = SilenceProtobufLog(); + +} // namespace +#endif + +bool TextInputReader::Read(protobuf::Message* message) const { + return ParseTextMessage(data(), size(), message); +} + +size_t TextOutputWriter::Write(const protobuf::Message& message) { + return SaveMessageAsText(message, data(), size()); +} + +bool BinaryInputReader::Read(protobuf::Message* message) const { + return ParseBinaryMessage(data(), size(), message); +} + +size_t BinaryOutputWriter::Write(const protobuf::Message& message) { + return SaveMessageAsBinary(message, data(), size()); +} + +bool ParseMessage(bool binary, const uint8_t* data, size_t size, + protobuf::Message* message) { + return binary ? ParseBinaryMessage(data, size, message) + : ParseTextMessage(data, size, message); +} + +std::string SaveMessage(bool binary, const protobuf::Message& message) { + return binary ? SaveMessageAsBinary(message) : SaveMessageAsText(message); +} + +void LastMutationCache::Store(const uint8_t* data, size_t size, + protobuf::Message* message) { + if (!message_) message_.reset(message->New()); + message->GetReflection()->Swap(message, message_.get()); + data_.assign(data, data + size); +} + +bool LastMutationCache::LoadIfSame(const uint8_t* data, size_t size, + protobuf::Message* message) { + if (!message_ || size != data_.size() || + !std::equal(data_.begin(), data_.end(), data)) + return false; + + message->GetReflection()->Swap(message, message_.get()); + message_.reset(); + return true; +} + +LastMutationCache* GetCache() { + static LastMutationCache cache; + return &cache; +} + +size_t GetMaxSize(const InputReader& input, const OutputWriter& output, + const protobuf::Message& message) { + size_t max_size = message.ByteSizeLong() + output.size(); + max_size -= std::min(max_size, input.size()); + return max_size; +} + +namespace { + +size_t MutateMessage(Mutator* mutator, const InputReader& input, + OutputWriter* output, protobuf::Message* message) { + input.Read(message); + mutator->Mutate(message, GetMaxSize(input, *output, *message)); + if (size_t new_size = output->Write(*message)) { + assert(new_size <= output->size()); + GetCache()->Store(output->data(), new_size, message); + return new_size; + } + return 0; +} + +size_t CrossOverMessages(Mutator* mutator, const InputReader& input1, + const InputReader& input2, OutputWriter* output, + protobuf::Message* message1, + protobuf::Message* message2) { + input1.Read(message1); + input2.Read(message2); + mutator->CrossOver(*message2, message1, + GetMaxSize(input1, *output, *message1)); + if (size_t new_size = output->Write(*message1)) { + assert(new_size <= output->size()); + GetCache()->Store(output->data(), new_size, message1); + return new_size; + } + return 0; +} + +template +size_t MutateFormattedMessage(Mutator* mutator, uint8_t* data, size_t size, + size_t max_size, protobuf::Message* message) { + Reader input(data, size); + Writer output(data, max_size); + return MutateMessage(mutator, input, &output, message); +} + +template +size_t CrossOverFormattedMessages(Mutator* mutator, const uint8_t* data1, + size_t size1, const uint8_t* data2, + size_t size2, uint8_t* out, + size_t max_out_size, + protobuf::Message* message1, + protobuf::Message* message2) { + Reader input1(data1, size1); + Reader input2(data2, size2); + Writer output(out, max_out_size); + return CrossOverMessages(mutator, input1, input2, &output, message1, + message2); +} + +} // namespace + +size_t MutateMessage(bool binary, Mutator* mutator, uint8_t* data, size_t size, + size_t max_size, protobuf::Message* message) { + auto mutate = + binary ? &MutateFormattedMessage + : &MutateFormattedMessage; + return mutate(mutator, data, size, max_size, message); +} + +size_t CrossOverMessages(bool binary, Mutator* mutator, const uint8_t* data1, + size_t size1, const uint8_t* data2, size_t size2, + uint8_t* out, size_t max_out_size, + protobuf::Message* message1, + protobuf::Message* message2) { + auto cross = + binary + ? &CrossOverFormattedMessages + : &CrossOverFormattedMessages; + return cross(mutator, data1, size1, data2, size2, out, max_out_size, message1, + message2); +} + +bool LoadProtoInput(bool binary, const uint8_t* data, size_t size, + Mutator* mutator, protobuf::Message* input) { + if (GetCache()->LoadIfSame(data, size, input)) return true; + if (!ParseMessage(binary, data, size, input)) return false; + mutator->Seed(size); + mutator->Fix(input); + return true; +} + +} // namespace protobuf_mutator diff --git a/src/mutation_io.h b/src/mutation_io.h new file mode 100644 index 0000000..87ffbc7 --- /dev/null +++ b/src/mutation_io.h @@ -0,0 +1,116 @@ +#ifndef SRC_MUTATION_IO_H_ +#define SRC_MUTATION_IO_H_ + +#include +#include + +#include +#include +#include +#include + +#include "port/protobuf.h" +#include "src/mutator.h" + +namespace protobuf_mutator { + +class InputReader { + public: + InputReader(const uint8_t* data, size_t size) : data_(data), size_(size) {} + virtual ~InputReader() = default; + + virtual bool Read(protobuf::Message* message) const = 0; + + const uint8_t* data() const { return data_; } + size_t size() const { return size_; } + + private: + const uint8_t* data_; + size_t size_; +}; + +class OutputWriter { + public: + OutputWriter(uint8_t* data, size_t size) : data_(data), size_(size) {} + virtual ~OutputWriter() = default; + + virtual size_t Write(const protobuf::Message& message) = 0; + + uint8_t* data() const { return data_; } + size_t size() const { return size_; } + + private: + uint8_t* data_; + size_t size_; +}; + +class TextInputReader : public InputReader { + public: + using InputReader::InputReader; + bool Read(protobuf::Message* message) const override; +}; + +class TextOutputWriter : public OutputWriter { + public: + using OutputWriter::OutputWriter; + size_t Write(const protobuf::Message& message) override; +}; + +class BinaryInputReader : public InputReader { + public: + using InputReader::InputReader; + bool Read(protobuf::Message* message) const override; +}; + +class BinaryOutputWriter : public OutputWriter { + public: + using OutputWriter::OutputWriter; + size_t Write(const protobuf::Message& message) override; +}; + +class LastMutationCache { + public: + void Store(const uint8_t* data, size_t size, protobuf::Message* message); + bool LoadIfSame(const uint8_t* data, size_t size, protobuf::Message* message); + + private: + std::vector data_; + std::unique_ptr message_; +}; + +LastMutationCache* GetCache(); + +size_t GetMaxSize(const InputReader& input, const OutputWriter& output, + const protobuf::Message& message); + +size_t MutateMessage(bool binary, Mutator* mutator, uint8_t* data, size_t size, + size_t max_size, protobuf::Message* message); +size_t CrossOverMessages(bool binary, Mutator* mutator, const uint8_t* data1, + size_t size1, const uint8_t* data2, size_t size2, + uint8_t* out, size_t max_out_size, + protobuf::Message* message1, + protobuf::Message* message2); + +bool LoadProtoInput(bool binary, const uint8_t* data, size_t size, + Mutator* mutator, protobuf::Message* input); + +bool ParseMessage(bool binary, const uint8_t* data, size_t size, + protobuf::Message* message); +std::string SaveMessage(bool binary, const protobuf::Message& message); + +namespace macro_internal { + +template +struct GetFirstParam; + +template +struct GetFirstParam { + using type = typename std::remove_const< + typename std::remove_reference::type>::type; +}; + +} // namespace macro_internal + +} // namespace protobuf_mutator + +#endif // SRC_MUTATION_IO_H_