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_