Merge commit '0f6aab9da6fe982218a01f4a5b896e65fcced437' as 'third_party/flatbuffers'
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
# The generated fuzzers
|
||||
*_fuzzer
|
||||
|
||||
# Findings from the fuzzers
|
||||
oom-*
|
||||
slow-unit-*
|
||||
crash-*
|
||||
|
||||
# Individual fuzzer logs if job=N
|
||||
fuzz-*.log
|
||||
|
||||
.clangd/
|
||||
|
||||
# These are copied from tests/annotated_binary/ and should use the latest ones.
|
||||
annotated_binary.bfbs
|
||||
annotated_binary.bin
|
||||
|
||||
test_64bit.bin
|
||||
|
||||
monster_test.bfbs
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
cmake_minimum_required(VERSION 3.9)
|
||||
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
project(FlatBuffersFuzzerTests)
|
||||
|
||||
option(BUILD_DEBUGGER "Compile a debugger with main() and without libFuzzer" OFF)
|
||||
|
||||
if(NOT DEFINED FLATBUFFERS_MAX_PARSING_DEPTH)
|
||||
# Force checking of RecursionError in the test
|
||||
set(FLATBUFFERS_MAX_PARSING_DEPTH 24)
|
||||
endif()
|
||||
message(STATUS "FLATBUFFERS_MAX_PARSING_DEPTH: ${FLATBUFFERS_MAX_PARSING_DEPTH}")
|
||||
|
||||
# Usage '-fsanitize=address' doesn't allowed with '-fsanitize=memory'.
|
||||
# MemorySanitizer will not work out-of-the-box, and will instead report false
|
||||
# positives coming from uninstrumented code. Need to re-build both C++ standard
|
||||
# library: https://github.com/google/sanitizers/wiki/MemorySanitizerLibcxxHowTo
|
||||
option(USE_ASAN "Use fuzzers with ASASN" OFF)
|
||||
option(USE_MSAN "Use fuzzers with MSASN" OFF)
|
||||
option(OSS_FUZZ "Set this option to use flags by oss-fuzz" OFF)
|
||||
|
||||
# Use Clang linker.
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld")
|
||||
|
||||
# add_link_options(-stdlib=libc++)
|
||||
|
||||
add_compile_options(
|
||||
# -stdlib=libc++ # Use Clang libc++ instead of GNU.
|
||||
-std=c++17
|
||||
-Wall
|
||||
-pedantic
|
||||
-Werror
|
||||
-Wextra
|
||||
-Wno-unused-parameter
|
||||
-fsigned-char
|
||||
-fno-omit-frame-pointer
|
||||
-g # Generate source-level debug information
|
||||
# -flto # enable link-time optimisation
|
||||
)
|
||||
|
||||
# https://llvm.org/docs/Passes.html save IR to see call graph make one bitcode
|
||||
# file:> llvm-link *.bc -o out.bc print call-graph:> opt out.bc -analyze -print-
|
||||
# callgraph &> callgraph.txt set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -save-temps
|
||||
# -flto")
|
||||
|
||||
# A special target with fuzzer+sanitizer flags.
|
||||
add_library(fuzzer_config INTERFACE)
|
||||
|
||||
target_compile_options(
|
||||
fuzzer_config
|
||||
INTERFACE
|
||||
$<$<NOT:$<BOOL:${OSS_FUZZ}>>:
|
||||
-fsanitize-coverage=trace-cmp
|
||||
>
|
||||
$<$<BOOL:${USE_ASAN}>:
|
||||
-fsanitize=fuzzer,undefined,address
|
||||
>
|
||||
$<$<BOOL:${USE_MSAN}>:
|
||||
-fsanitize=fuzzer,undefined,memory
|
||||
-fsanitize-memory-track-origins=2
|
||||
>
|
||||
$<$<BOOL:${OSS_FUZZ}>:
|
||||
${CXX}
|
||||
${CXXFLAGS}
|
||||
>
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
fuzzer_config
|
||||
INTERFACE
|
||||
$<$<BOOL:${USE_ASAN}>:
|
||||
-fsanitize=fuzzer,undefined,address
|
||||
>
|
||||
$<$<BOOL:${USE_MSAN}>:
|
||||
-fsanitize=fuzzer,undefined,memory
|
||||
>
|
||||
$<$<BOOL:${OSS_FUZZ}>:
|
||||
$ENV{LIB_FUZZING_ENGINE}
|
||||
>
|
||||
)
|
||||
|
||||
set(FLATBUFFERS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../")
|
||||
|
||||
set(FlatBuffers_Library_SRCS
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/allocator.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/array.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/base.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/buffer.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/buffer_ref.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/default_allocator.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/detached_buffer.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/flatbuffer_builder.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/flatbuffers.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/flexbuffers.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/flex_flat_util.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/hash.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/idl.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/minireflect.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/reflection.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/reflection_generated.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/registry.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/stl_emulation.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/string.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/struct.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/table.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/util.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/vector.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/vector_downward.h
|
||||
${FLATBUFFERS_DIR}/include/flatbuffers/verifier.h
|
||||
${FLATBUFFERS_DIR}/src/idl_parser.cpp
|
||||
${FLATBUFFERS_DIR}/src/idl_gen_text.cpp
|
||||
${FLATBUFFERS_DIR}/src/reflection.cpp
|
||||
${FLATBUFFERS_DIR}/src/binary_annotator.h
|
||||
${FLATBUFFERS_DIR}/src/binary_annotator.cpp
|
||||
${FLATBUFFERS_DIR}/src/util.cpp
|
||||
${FLATBUFFERS_DIR}/tests/test_assert.cpp
|
||||
${FLATBUFFERS_DIR}/tests/64bit/test_64bit_bfbs_generated.h
|
||||
)
|
||||
|
||||
include_directories(${FLATBUFFERS_DIR}/include)
|
||||
include_directories(${FLATBUFFERS_DIR}/tests)
|
||||
include_directories(${FLATBUFFERS_DIR}/src)
|
||||
|
||||
add_library(flatbuffers_fuzzed STATIC ${FlatBuffers_Library_SRCS})
|
||||
# Use PUBLIC to force 'fuzzer_config' for all dependent targets
|
||||
target_link_libraries(flatbuffers_fuzzed PUBLIC fuzzer_config)
|
||||
|
||||
# FLATBUFFERS_ASSERT should assert in Release as well. Redefine
|
||||
# FLATBUFFERS_ASSERT macro definition. Declare as PUBLIC to cover asserts in all
|
||||
# included header files.
|
||||
target_compile_definitions(
|
||||
flatbuffers_fuzzed
|
||||
PUBLIC
|
||||
FLATBUFFERS_ASSERT=fuzzer_assert_impl
|
||||
FLATBUFFERS_ASSERT_INCLUDE="${CMAKE_CURRENT_SOURCE_DIR}/fuzzer_assert.h"
|
||||
PRIVATE
|
||||
FLATBUFFERS_MAX_PARSING_DEPTH=${FLATBUFFERS_MAX_PARSING_DEPTH}
|
||||
)
|
||||
|
||||
# Setup fuzzer tests.
|
||||
|
||||
add_executable(scalar_fuzzer flatbuffers_scalar_fuzzer.cc)
|
||||
target_link_libraries(scalar_fuzzer PRIVATE flatbuffers_fuzzed)
|
||||
|
||||
add_executable(parser_fuzzer flatbuffers_parser_fuzzer.cc)
|
||||
target_link_libraries(parser_fuzzer PRIVATE flatbuffers_fuzzed)
|
||||
|
||||
add_executable(verifier_fuzzer flatbuffers_verifier_fuzzer.cc)
|
||||
target_link_libraries(verifier_fuzzer PRIVATE flatbuffers_fuzzed)
|
||||
|
||||
add_executable(flexverifier_fuzzer flexbuffers_verifier_fuzzer.cc)
|
||||
target_link_libraries(flexverifier_fuzzer PRIVATE flatbuffers_fuzzed)
|
||||
|
||||
add_executable(monster_fuzzer flatbuffers_monster_fuzzer.cc)
|
||||
target_link_libraries(monster_fuzzer PRIVATE flatbuffers_fuzzed)
|
||||
add_custom_command(
|
||||
TARGET monster_fuzzer PRE_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_SOURCE_DIR}/../monster_test.bfbs
|
||||
${CMAKE_CURRENT_BINARY_DIR}/monster_test.bfbs)
|
||||
|
||||
add_executable(annotator_fuzzer flatbuffers_annotator_fuzzer.cc)
|
||||
target_link_libraries(annotator_fuzzer PRIVATE flatbuffers_fuzzed)
|
||||
add_custom_command(
|
||||
TARGET annotator_fuzzer PRE_BUILD
|
||||
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_SOURCE_DIR}/../annotated_binary/annotated_binary.bfbs
|
||||
${CMAKE_CURRENT_BINARY_DIR}/annotated_binary.bfbs
|
||||
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_SOURCE_DIR}/../annotated_binary/annotated_binary.bin
|
||||
${CMAKE_CURRENT_BINARY_DIR}/seed_annotator/annotated_binary.bin
|
||||
)
|
||||
|
||||
add_executable(64bit_fuzzer flatbuffers_64bit_fuzzer.cc)
|
||||
target_link_libraries(64bit_fuzzer PRIVATE flatbuffers_fuzzed)
|
||||
add_custom_command(
|
||||
TARGET 64bit_fuzzer PRE_BUILD
|
||||
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_SOURCE_DIR}/../64bit/test_64bit.bin
|
||||
${CMAKE_CURRENT_BINARY_DIR}/seed_64bit/test_64bit.bin
|
||||
)
|
||||
|
||||
# Build debugger for weird cases found with fuzzer.
|
||||
if(BUILD_DEBUGGER)
|
||||
add_library(flatbuffers_nonfuzz STATIC ${FlatBuffers_Library_SRCS})
|
||||
target_compile_options(
|
||||
flatbuffers_nonfuzz
|
||||
PUBLIC
|
||||
$<$<BOOL:${USE_ASAN}>:
|
||||
-fsanitize=undefined,address
|
||||
>
|
||||
-fno-limit-debug-info
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
flatbuffers_nonfuzz
|
||||
PUBLIC
|
||||
$<$<BOOL:${USE_ASAN}>:
|
||||
-fsanitize=undefined,address
|
||||
>
|
||||
)
|
||||
|
||||
target_compile_definitions(
|
||||
flatbuffers_nonfuzz
|
||||
PUBLIC
|
||||
FLATBUFFERS_ASSERT=fuzzer_assert_impl
|
||||
FLATBUFFERS_ASSERT_INCLUDE="${CMAKE_CURRENT_SOURCE_DIR}/fuzzer_assert.h"
|
||||
PRIVATE
|
||||
FLATBUFFERS_MAX_PARSING_DEPTH=${FLATBUFFERS_MAX_PARSING_DEPTH}
|
||||
)
|
||||
add_executable(scalar_debug
|
||||
flatbuffers_scalar_fuzzer.cc
|
||||
scalar_debug.cpp
|
||||
)
|
||||
target_link_libraries(scalar_debug PRIVATE flatbuffers_nonfuzz)
|
||||
|
||||
add_executable(monster_debug
|
||||
flatbuffers_monster_fuzzer.cc
|
||||
monster_debug.cpp
|
||||
)
|
||||
target_link_libraries(monster_debug PRIVATE flatbuffers_nonfuzz)
|
||||
add_custom_command(
|
||||
TARGET monster_debug PRE_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_SOURCE_DIR}/../monster_test.bfbs
|
||||
${CMAKE_CURRENT_BINARY_DIR}/monster_test.bfbs)
|
||||
|
||||
endif(BUILD_DEBUGGER)
|
||||
@@ -0,0 +1,121 @@
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <type_traits>
|
||||
|
||||
#include "64bit/test_64bit_bfbs_generated.h"
|
||||
#include "64bit/test_64bit_generated.h"
|
||||
#include "flatbuffers/base.h"
|
||||
#include "flatbuffers/flatbuffer_builder.h"
|
||||
#include "flatbuffers/flatbuffers.h"
|
||||
#include "flatbuffers/reflection.h"
|
||||
#include "flatbuffers/verifier.h"
|
||||
#include "test_assert.h"
|
||||
#include "test_init.h"
|
||||
|
||||
OneTimeTestInit OneTimeTestInit::one_time_init_;
|
||||
|
||||
static RootTableBinarySchema schema;
|
||||
|
||||
static constexpr uint8_t flags_sized_prefixed = 0b00000001;
|
||||
|
||||
static const uint64_t kFnvPrime = 0x00000100000001b3ULL;
|
||||
static const uint64_t kOffsetBasis = 0xcbf29ce484222645ULL;
|
||||
|
||||
namespace flatbuffers {
|
||||
|
||||
template<typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||
uint64_t Hash(T value, uint64_t hash) {
|
||||
return (hash * kFnvPrime) ^ value;
|
||||
}
|
||||
|
||||
uint64_t Hash(double value, uint64_t hash) {
|
||||
static_assert(sizeof(double) == sizeof(uint64_t));
|
||||
return (hash * kFnvPrime) ^ static_cast<uint64_t>(value);
|
||||
}
|
||||
|
||||
uint64_t Hash(const flatbuffers::String *value, uint64_t hash) {
|
||||
if (value == nullptr) { return hash * kFnvPrime; }
|
||||
for (auto &c : value->str()) { hash = Hash(static_cast<uint8_t>(c), hash); }
|
||||
return hash;
|
||||
}
|
||||
|
||||
uint64_t Hash(const LeafStruct *value, uint64_t hash) {
|
||||
if (value == nullptr) { return hash * kFnvPrime; }
|
||||
hash = Hash(value->a(), hash);
|
||||
hash = Hash(value->b(), hash);
|
||||
return hash;
|
||||
}
|
||||
|
||||
template<typename T> uint64_t Hash(const Vector<T> *value, uint64_t hash) {
|
||||
if (value == nullptr) { return hash * kFnvPrime; }
|
||||
for (const T c : *value) { hash = Hash(c, hash); }
|
||||
return hash;
|
||||
}
|
||||
|
||||
template<typename T> uint64_t Hash(const Vector64<T> *value, uint64_t hash) {
|
||||
if (value == nullptr) { return hash * kFnvPrime; }
|
||||
for (const T c : *value) { hash = Hash(c, hash); }
|
||||
return hash;
|
||||
}
|
||||
|
||||
uint64_t Hash(const RootTable *value, uint64_t hash) {
|
||||
if (value == nullptr) { return hash * kFnvPrime; }
|
||||
// Hash all the fields so we can exercise all parts of the code.
|
||||
hash = Hash(value->far_vector(), hash);
|
||||
hash = Hash(value->a(), hash);
|
||||
hash = Hash(value->far_string(), hash);
|
||||
hash = Hash(value->big_vector(), hash);
|
||||
hash = Hash(value->near_string(), hash);
|
||||
hash = Hash(value->nested_root(), hash);
|
||||
hash = Hash(value->far_struct_vector(), hash);
|
||||
hash = Hash(value->big_struct_vector(), hash);
|
||||
return hash;
|
||||
}
|
||||
|
||||
static int AccessBuffer(const uint8_t *data, size_t size,
|
||||
bool is_size_prefixed) {
|
||||
const RootTable *root_table =
|
||||
is_size_prefixed ? GetSizePrefixedRootTable(data) : GetRootTable(data);
|
||||
TEST_NOTNULL(root_table);
|
||||
|
||||
uint64_t hash = kOffsetBasis;
|
||||
hash = Hash(root_table, hash);
|
||||
hash = Hash(root_table->nested_root_nested_root(), hash);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerInitialize(int *, char ***argv) {
|
||||
Verifier verifier(schema.begin(), schema.size());
|
||||
TEST_EQ(true, reflection::VerifySchemaBuffer(verifier));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
if (size < FLATBUFFERS_MIN_BUFFER_SIZE) { return 0; }
|
||||
|
||||
// Take the first bit of data as a flag to control things.
|
||||
const uint8_t flags = data[0];
|
||||
data++;
|
||||
size--;
|
||||
|
||||
Verifier::Options options;
|
||||
options.assert = true;
|
||||
options.check_alignment = true;
|
||||
options.check_nested_flatbuffers = true;
|
||||
|
||||
Verifier verifier(data, size, options);
|
||||
|
||||
const bool is_size_prefixed = flags & flags_sized_prefixed;
|
||||
|
||||
// Filter out data that isn't valid.
|
||||
if ((is_size_prefixed && !VerifySizePrefixedRootTableBuffer(verifier)) ||
|
||||
!VerifyRootTableBuffer(verifier)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return AccessBuffer(data, size, is_size_prefixed);
|
||||
}
|
||||
|
||||
} // namespace flatbuffers
|
||||
@@ -0,0 +1,53 @@
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
#include "binary_annotator.h"
|
||||
#include "test_init.h"
|
||||
|
||||
static std::filesystem::path exe_path_;
|
||||
static const uint8_t *schema_bfbs_;
|
||||
static size_t schema_bfbs_length_;
|
||||
|
||||
bool TestFileExists(std::filesystem::path file_path) {
|
||||
if (file_path.has_filename() && std::filesystem::exists(file_path))
|
||||
return true;
|
||||
|
||||
TEST_OUTPUT_LINE("@DEBUG: file '%s' not found", file_path.string().c_str());
|
||||
for (const auto &entry :
|
||||
std::filesystem::directory_iterator(file_path.parent_path())) {
|
||||
TEST_OUTPUT_LINE("@DEBUG: parent path entry: '%s'",
|
||||
entry.path().string().c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string LoadBinarySchema(const char *file_name) {
|
||||
const auto file_path = exe_path_.parent_path() / file_name;
|
||||
TEST_EQ(true, TestFileExists(file_path));
|
||||
std::string schemafile;
|
||||
TEST_EQ(true,
|
||||
flatbuffers::LoadFile(file_path.string().c_str(), true, &schemafile));
|
||||
|
||||
flatbuffers::Verifier verifier(
|
||||
reinterpret_cast<const uint8_t *>(schemafile.c_str()), schemafile.size());
|
||||
TEST_EQ(true, reflection::VerifySchemaBuffer(verifier));
|
||||
return schemafile;
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerInitialize(int *, char ***argv) {
|
||||
exe_path_ = (*argv)[0];
|
||||
static const std::string schema_file =
|
||||
LoadBinarySchema("annotated_binary.bfbs");
|
||||
schema_bfbs_ = reinterpret_cast<const uint8_t *>(schema_file.c_str());
|
||||
schema_bfbs_length_ = schema_file.size();
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
flatbuffers::BinaryAnnotator annotator(schema_bfbs_, schema_bfbs_length_,
|
||||
data, size, false);
|
||||
|
||||
annotator.Annotate();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2014 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 <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <clocale>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
#include "cpp17/generated_cpp17/monster_test_generated.h"
|
||||
#include "flatbuffers/idl.h"
|
||||
#include "test_assert.h"
|
||||
#include "test_init.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// Utility for test run.
|
||||
OneTimeTestInit OneTimeTestInit::one_time_init_;
|
||||
// The current executable path (see LLVMFuzzerInitialize).
|
||||
static fs::path exe_path_;
|
||||
|
||||
static flatbuffers::Parser parser_;
|
||||
|
||||
namespace {
|
||||
|
||||
static constexpr size_t kMinInputLength = 1;
|
||||
static constexpr size_t kMaxInputLength = 16384;
|
||||
|
||||
static constexpr uint8_t flags_strict_json = 0x80;
|
||||
static constexpr uint8_t flags_skip_unexpected_fields_in_json = 0x40;
|
||||
static constexpr uint8_t flags_allow_non_utf8 = 0x20;
|
||||
|
||||
bool TestFileExists(fs::path file_path) {
|
||||
if (file_path.has_filename() && fs::exists(file_path)) return true;
|
||||
|
||||
TEST_OUTPUT_LINE("@DEBUG: file '%s' not found", file_path.string().c_str());
|
||||
for (const auto &entry : fs::directory_iterator(file_path.parent_path())) {
|
||||
TEST_OUTPUT_LINE("@DEBUG: parent path entry: '%s'",
|
||||
entry.path().string().c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string LoadBinarySchema(const char *file_name) {
|
||||
const auto file_path = exe_path_.parent_path() / file_name;
|
||||
TEST_EQ(true, TestFileExists(file_path));
|
||||
std::string schemafile;
|
||||
TEST_EQ(true,
|
||||
flatbuffers::LoadFile(file_path.string().c_str(), true, &schemafile));
|
||||
|
||||
flatbuffers::Verifier verifier(
|
||||
reinterpret_cast<const uint8_t *>(schemafile.c_str()), schemafile.size());
|
||||
TEST_EQ(true, reflection::VerifySchemaBuffer(verifier));
|
||||
return schemafile;
|
||||
}
|
||||
|
||||
std::string do_test(const flatbuffers::IDLOptions &opts,
|
||||
const std::string input_json, const bool check_parser) {
|
||||
// (re)define parser options
|
||||
parser_.opts = opts;
|
||||
|
||||
std::string jsongen;
|
||||
if (parser_.ParseJson(input_json.c_str())) {
|
||||
flatbuffers::Verifier verifier(parser_.builder_.GetBufferPointer(),
|
||||
parser_.builder_.GetSize());
|
||||
TEST_EQ(true, MyGame::Example::VerifyMonsterBuffer(verifier));
|
||||
TEST_NULL(
|
||||
GenText(parser_, parser_.builder_.GetBufferPointer(), &jsongen));
|
||||
} else if (check_parser) {
|
||||
TEST_OUTPUT_LINE("parser failed with JSON:\n%s", input_json.c_str());
|
||||
TEST_EQ_STR("", parser_.error_.c_str());
|
||||
TEST_ASSERT(false);
|
||||
}
|
||||
return jsongen;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// https://google.github.io/oss-fuzz/further-reading/fuzzer-environment/
|
||||
// Current working directory
|
||||
// You should not make any assumptions about the current working directory of
|
||||
// your fuzz target. If you need to load data files, please use argv[0] to get
|
||||
// the directory where your fuzz target executable is located.
|
||||
// You must not modify argv[0].
|
||||
extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) {
|
||||
(void)argc;
|
||||
exe_path_ = (*argv)[0];
|
||||
|
||||
static const std::string schemafile = LoadBinarySchema("monster_test.bfbs");
|
||||
// parse schema first, so we can use it to parse the data after
|
||||
parser_.Deserialize(reinterpret_cast<const uint8_t *>(schemafile.c_str()),
|
||||
schemafile.size());
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
// Reserve one byte for Parser flags and one byte for repetition counter.
|
||||
if (size < 3) return 0;
|
||||
const uint8_t flags = data[0];
|
||||
(void)data[1]; // reserved
|
||||
data += 2;
|
||||
size -= 2; // bypass
|
||||
|
||||
const std::string original(reinterpret_cast<const char *>(data), size);
|
||||
auto input = std::string(original.c_str()); // until '\0'
|
||||
if (input.size() < kMinInputLength || input.size() > kMaxInputLength)
|
||||
return 0;
|
||||
|
||||
flatbuffers::IDLOptions opts;
|
||||
opts.strict_json = (flags & flags_strict_json);
|
||||
opts.skip_unexpected_fields_in_json =
|
||||
(flags & flags_skip_unexpected_fields_in_json);
|
||||
opts.allow_non_utf8 = (flags & flags_allow_non_utf8);
|
||||
|
||||
do {
|
||||
const std::string jsongen_1 = do_test(opts, input, false);
|
||||
if (!jsongen_1.empty()) {
|
||||
const std::string jsongen_2 = do_test(opts, jsongen_1, true);
|
||||
if (jsongen_1 != jsongen_2 && !opts.output_default_scalars_in_json) {
|
||||
// This gets tricky when the jsongen_1 includes a default-value, as the
|
||||
// generated jsongen_2 doesn't emit default-values. So enable default
|
||||
// scalars and re-run it.
|
||||
opts.output_default_scalars_in_json = true;
|
||||
continue;
|
||||
}
|
||||
TEST_EQ(jsongen_1, jsongen_2);
|
||||
}
|
||||
} while (0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <clocale>
|
||||
#include <string>
|
||||
|
||||
#include "flatbuffers/idl.h"
|
||||
#include "test_init.h"
|
||||
|
||||
static constexpr size_t kMinInputLength = 1;
|
||||
static constexpr size_t kMaxInputLength = 16384;
|
||||
|
||||
static constexpr uint8_t flags_strict_json = 0x80;
|
||||
static constexpr uint8_t flags_skip_unexpected_fields_in_json = 0x40;
|
||||
static constexpr uint8_t flags_allow_non_utf8 = 0x20;
|
||||
|
||||
// Utility for test run.
|
||||
OneTimeTestInit OneTimeTestInit::one_time_init_;
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
// Reserve one byte for Parser flags and one byte for repetition counter.
|
||||
if (size < 3) return 0;
|
||||
const uint8_t flags = data[0];
|
||||
(void)data[1]; // reserved
|
||||
data += 2;
|
||||
size -= 2; // bypass
|
||||
|
||||
const std::string original(reinterpret_cast<const char *>(data), size);
|
||||
auto input = std::string(original.c_str()); // until '\0'
|
||||
if (input.size() < kMinInputLength || input.size() > kMaxInputLength)
|
||||
return 0;
|
||||
|
||||
flatbuffers::IDLOptions opts;
|
||||
opts.strict_json = (flags & flags_strict_json);
|
||||
opts.skip_unexpected_fields_in_json =
|
||||
(flags & flags_skip_unexpected_fields_in_json);
|
||||
opts.allow_non_utf8 = (flags & flags_allow_non_utf8);
|
||||
|
||||
flatbuffers::Parser parser(opts);
|
||||
|
||||
// Guarantee 0-termination in the input.
|
||||
auto parse_input = input.c_str();
|
||||
|
||||
// Check Parser.
|
||||
parser.Parse(parse_input);
|
||||
// TODO:
|
||||
// Need to add additional checks for inputs passed Parse(parse_input) successfully:
|
||||
// 1. Serialization to bfbs.
|
||||
// 2. Generation of a default object.
|
||||
// 3. Verification of the object using reflection.
|
||||
// 3. Printing to json.
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
/*
|
||||
* Copyright 2014 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 <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <clocale>
|
||||
#include <memory>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
|
||||
#include "flatbuffers/idl.h"
|
||||
#include "test_init.h"
|
||||
|
||||
static constexpr size_t kMinInputLength = 1;
|
||||
static constexpr size_t kMaxInputLength = 3000;
|
||||
|
||||
static constexpr uint8_t flags_scalar_type = 0x0F; // type of scalar value
|
||||
static constexpr uint8_t flags_quotes_kind = 0x10; // quote " or '
|
||||
// reserved for future: json {named} or [unnamed]
|
||||
// static constexpr uint8_t flags_json_bracer = 0x20;
|
||||
|
||||
// Find all 'subj' sub-strings and replace first character of sub-string.
|
||||
// BreakSequence("testest","tes", 'X') -> "XesXest".
|
||||
// BreakSequence("xxx","xx", 'Y') -> "YYx".
|
||||
static void BreakSequence(std::string &s, const char *subj, char repl) {
|
||||
size_t pos = 0;
|
||||
while (pos = s.find(subj, pos), pos != std::string::npos) {
|
||||
s.at(pos) = repl;
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all leading and trailing symbols matched with pattern set.
|
||||
// StripString("xy{xy}y", "xy") -> "{xy}"
|
||||
static std::string StripString(const std::string &s, const char *pattern,
|
||||
size_t *pos = nullptr) {
|
||||
if (pos) *pos = 0;
|
||||
// leading
|
||||
auto first = s.find_first_not_of(pattern);
|
||||
if (std::string::npos == first) return "";
|
||||
if (pos) *pos = first;
|
||||
// trailing
|
||||
auto last = s.find_last_not_of(pattern);
|
||||
assert(last < s.length());
|
||||
assert(first <= last);
|
||||
return s.substr(first, last - first + 1);
|
||||
}
|
||||
|
||||
class RegexMatcher {
|
||||
protected:
|
||||
virtual bool MatchNumber(const std::string &input) const = 0;
|
||||
|
||||
public:
|
||||
virtual ~RegexMatcher() = default;
|
||||
|
||||
struct MatchResult {
|
||||
size_t pos{ 0 };
|
||||
size_t len{ 0 };
|
||||
bool res{ false };
|
||||
bool quoted{ false };
|
||||
};
|
||||
|
||||
MatchResult Match(const std::string &input) const {
|
||||
MatchResult r;
|
||||
// strip leading and trailing "spaces" accepted by flatbuffer
|
||||
auto test = StripString(input, "\t\r\n ", &r.pos);
|
||||
r.len = test.size();
|
||||
// check quotes
|
||||
if (test.size() >= 2) {
|
||||
auto fch = test.front();
|
||||
auto lch = test.back();
|
||||
r.quoted = (fch == lch) && (fch == '\'' || fch == '\"');
|
||||
if (r.quoted) {
|
||||
// remove quotes for regex test
|
||||
test = test.substr(1, test.size() - 2);
|
||||
}
|
||||
}
|
||||
// Fast check:
|
||||
if (test.empty()) return r;
|
||||
// A string with a valid scalar shouldn't have non-ascii or non-printable
|
||||
// symbols.
|
||||
for (auto c : test) {
|
||||
if ((c < ' ') || (c > '~')) return r;
|
||||
}
|
||||
// Check with regex
|
||||
r.res = MatchNumber(test);
|
||||
return r;
|
||||
}
|
||||
|
||||
bool MatchRegexList(const std::string &input,
|
||||
const std::vector<std::regex> &re_list) const {
|
||||
auto str = StripString(input, " ");
|
||||
if (str.empty()) return false;
|
||||
for (auto &re : re_list) {
|
||||
std::smatch match;
|
||||
if (std::regex_match(str, match, re)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class IntegerRegex : public RegexMatcher {
|
||||
protected:
|
||||
bool MatchNumber(const std::string &input) const override {
|
||||
static const std::vector<std::regex> re_list = {
|
||||
std::regex{ R"(^[-+]?[0-9]+$)", std::regex_constants::optimize },
|
||||
|
||||
std::regex{ R"(^[-+]?0[xX][0-9a-fA-F]+$)",
|
||||
std::regex_constants::optimize }
|
||||
};
|
||||
return MatchRegexList(input, re_list);
|
||||
}
|
||||
|
||||
public:
|
||||
IntegerRegex() = default;
|
||||
virtual ~IntegerRegex() = default;
|
||||
};
|
||||
|
||||
class UIntegerRegex : public RegexMatcher {
|
||||
protected:
|
||||
bool MatchNumber(const std::string &input) const override {
|
||||
static const std::vector<std::regex> re_list = {
|
||||
std::regex{ R"(^[+]?[0-9]+$)", std::regex_constants::optimize },
|
||||
std::regex{ R"(^[+]?0[xX][0-9a-fA-F]+$)",
|
||||
std::regex_constants::optimize },
|
||||
// accept -0 number
|
||||
std::regex{ R"(^[-](?:0[xX])?0+$)", std::regex_constants::optimize }
|
||||
};
|
||||
return MatchRegexList(input, re_list);
|
||||
}
|
||||
|
||||
public:
|
||||
UIntegerRegex() = default;
|
||||
virtual ~UIntegerRegex() = default;
|
||||
};
|
||||
|
||||
class BooleanRegex : public IntegerRegex {
|
||||
protected:
|
||||
bool MatchNumber(const std::string &input) const override {
|
||||
if (input == "true" || input == "false") return true;
|
||||
return IntegerRegex::MatchNumber(input);
|
||||
}
|
||||
|
||||
public:
|
||||
BooleanRegex() = default;
|
||||
virtual ~BooleanRegex() = default;
|
||||
};
|
||||
|
||||
class FloatRegex : public RegexMatcher {
|
||||
protected:
|
||||
bool MatchNumber(const std::string &input) const override {
|
||||
static const std::vector<std::regex> re_list = {
|
||||
// hex-float
|
||||
std::regex{
|
||||
R"(^[-+]?0[xX](?:(?:[.][0-9a-fA-F]+)|(?:[0-9a-fA-F]+[.][0-9a-fA-F]*)|(?:[0-9a-fA-F]+))[pP][-+]?[0-9]+$)",
|
||||
std::regex_constants::optimize },
|
||||
// dec-float
|
||||
std::regex{
|
||||
R"(^[-+]?(?:(?:[.][0-9]+)|(?:[0-9]+[.][0-9]*)|(?:[0-9]+))(?:[eE][-+]?[0-9]+)?$)",
|
||||
std::regex_constants::optimize },
|
||||
|
||||
std::regex{ R"(^[-+]?(?:nan|inf|infinity)$)",
|
||||
std::regex_constants::optimize | std::regex_constants::icase }
|
||||
};
|
||||
return MatchRegexList(input, re_list);
|
||||
}
|
||||
|
||||
public:
|
||||
FloatRegex() = default;
|
||||
virtual ~FloatRegex() = default;
|
||||
};
|
||||
|
||||
class ScalarReferenceResult {
|
||||
private:
|
||||
ScalarReferenceResult(const char *_type, RegexMatcher::MatchResult _matched)
|
||||
: type(_type), matched(_matched) {}
|
||||
|
||||
public:
|
||||
// Decode scalar type and check if the input string satisfies the scalar type.
|
||||
static ScalarReferenceResult Check(uint8_t code, const std::string &input) {
|
||||
switch (code) {
|
||||
case 0x0: return { "double", FloatRegex().Match(input) };
|
||||
case 0x1: return { "float", FloatRegex().Match(input) };
|
||||
case 0x2: return { "int8", IntegerRegex().Match(input) };
|
||||
case 0x3: return { "int16", IntegerRegex().Match(input) };
|
||||
case 0x4: return { "int32", IntegerRegex().Match(input) };
|
||||
case 0x5: return { "int64", IntegerRegex().Match(input) };
|
||||
case 0x6: return { "uint8", UIntegerRegex().Match(input) };
|
||||
case 0x7: return { "uint16", UIntegerRegex().Match(input) };
|
||||
case 0x8: return { "uint32", UIntegerRegex().Match(input) };
|
||||
case 0x9: return { "uint64", UIntegerRegex().Match(input) };
|
||||
case 0xA: return { "bool", BooleanRegex().Match(input) };
|
||||
default: return { "float", FloatRegex().Match(input) };
|
||||
};
|
||||
}
|
||||
|
||||
const char *type;
|
||||
const RegexMatcher::MatchResult matched;
|
||||
};
|
||||
|
||||
bool Parse(flatbuffers::Parser &parser, const std::string &json,
|
||||
std::string *_text) {
|
||||
auto done = parser.ParseJson(json.c_str());
|
||||
if (done) {
|
||||
TEST_NULL(GenText(parser, parser.builder_.GetBufferPointer(), _text));
|
||||
} else {
|
||||
*_text = parser.error_;
|
||||
}
|
||||
return done;
|
||||
}
|
||||
|
||||
// Utility for test run.
|
||||
OneTimeTestInit OneTimeTestInit::one_time_init_;
|
||||
|
||||
// llvm std::regex have problem with stack overflow, limit maximum length.
|
||||
// ./scalar_fuzzer -max_len=3000
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
// Reserve one byte for Parser flags and one byte for repetition counter.
|
||||
if (size < 3) return 0;
|
||||
const uint8_t flags = data[0];
|
||||
// normalize to ascii alphabet
|
||||
const int extra_rep_number =
|
||||
std::max(5, (data[1] > '0' ? (data[1] - '0') : 0));
|
||||
data += 2;
|
||||
size -= 2; // bypass
|
||||
|
||||
// Guarantee 0-termination.
|
||||
const std::string original(reinterpret_cast<const char *>(data), size);
|
||||
auto input = std::string(original.c_str()); // until '\0'
|
||||
if (input.size() < kMinInputLength || input.size() > kMaxInputLength)
|
||||
return 0;
|
||||
|
||||
// Break comments in json to avoid complexity with regex matcher.
|
||||
// The string " 12345 /* text */" will be accepted if insert it to string
|
||||
// expression: "table X { Y: " + " 12345 /* text */" + "; }.
|
||||
// But strings like this will complicate regex matcher.
|
||||
// We reject this by transform "/* text */ 12345" to "@* text */ 12345".
|
||||
BreakSequence(input, "//", '@'); // "//" -> "@/"
|
||||
BreakSequence(input, "/*", '@'); // "/*" -> "@*"
|
||||
// { "$schema: "text" } is exceptional case.
|
||||
// This key:value ignored by the parser. Numbers can not have $.
|
||||
BreakSequence(input, "$schema", '@'); // "$schema" -> "@schema"
|
||||
// Break all known scalar functions (todo: add them to regex?):
|
||||
for (auto f : { "deg", "rad", "sin", "cos", "tan", "asin", "acos", "atan" }) {
|
||||
BreakSequence(input, f, '_'); // ident -> ident
|
||||
}
|
||||
|
||||
// Extract type of scalar from 'flags' and check if the input string satisfies
|
||||
// the scalar type.
|
||||
const auto ref_res =
|
||||
ScalarReferenceResult::Check(flags & flags_scalar_type, input);
|
||||
auto &recheck = ref_res.matched;
|
||||
|
||||
// Create parser
|
||||
flatbuffers::IDLOptions opts;
|
||||
opts.force_defaults = true;
|
||||
opts.output_default_scalars_in_json = true;
|
||||
opts.indent_step = -1;
|
||||
opts.strict_json = true;
|
||||
|
||||
flatbuffers::Parser parser(opts);
|
||||
auto schema =
|
||||
"table X { Y: " + std::string(ref_res.type) + "; } root_type X;";
|
||||
TEST_EQ_FUNC(parser.Parse(schema.c_str()), true);
|
||||
|
||||
// The fuzzer can adjust the number repetition if a side-effects have found.
|
||||
// Each test should pass at least two times to ensure that the parser doesn't
|
||||
// have any hidden-states or locale-depended effects.
|
||||
for (auto cnt = 0; cnt < (extra_rep_number + 2); cnt++) {
|
||||
// Each even run (0,2,4..) will test locale independed code.
|
||||
auto use_locale = !!OneTimeTestInit::test_locale() && (0 == (cnt % 2));
|
||||
// Set new locale.
|
||||
if (use_locale) {
|
||||
FLATBUFFERS_ASSERT(setlocale(LC_ALL, OneTimeTestInit::test_locale()));
|
||||
}
|
||||
|
||||
// Parse original input as-is.
|
||||
auto orig_scalar = "{\"Y\" : " + input + "}";
|
||||
std::string orig_back;
|
||||
auto orig_done = Parse(parser, orig_scalar, &orig_back);
|
||||
|
||||
if (recheck.res != orig_done) {
|
||||
// look for "does not fit" or "doesn't fit" or "out of range"
|
||||
auto not_fit =
|
||||
(true == recheck.res)
|
||||
? ((orig_back.find("does not fit") != std::string::npos) ||
|
||||
(orig_back.find("out of range") != std::string::npos))
|
||||
: false;
|
||||
|
||||
if (false == not_fit) {
|
||||
TEST_OUTPUT_LINE("Stage 1 failed: Parser(%d) != Regex(%d)", orig_done,
|
||||
recheck.res);
|
||||
TEST_EQ_STR(orig_back.c_str(),
|
||||
input.substr(recheck.pos, recheck.len).c_str());
|
||||
TEST_EQ_FUNC(orig_done, recheck.res);
|
||||
}
|
||||
}
|
||||
|
||||
// Try to make quoted string and test it.
|
||||
std::string qouted_input;
|
||||
if (true == recheck.quoted) {
|
||||
// we can't simply remove quotes, they may be nested "'12'".
|
||||
// Original string "\'12\'" converted to "'12'".
|
||||
// The string can be an invalid string by JSON rules, but after quotes
|
||||
// removed can transform to valid.
|
||||
assert(recheck.len >= 2);
|
||||
} else {
|
||||
const auto quote = (flags & flags_quotes_kind) ? '\"' : '\'';
|
||||
qouted_input = input; // copy
|
||||
qouted_input.insert(recheck.pos + recheck.len, 1, quote);
|
||||
qouted_input.insert(recheck.pos, 1, quote);
|
||||
}
|
||||
|
||||
// Test quoted version of the string
|
||||
if (!qouted_input.empty()) {
|
||||
auto fix_scalar = "{\"Y\" : " + qouted_input + "}";
|
||||
std::string fix_back;
|
||||
auto fix_done = Parse(parser, fix_scalar, &fix_back);
|
||||
|
||||
if (orig_done != fix_done) {
|
||||
TEST_OUTPUT_LINE("Stage 2 failed: Parser(%d) != Regex(%d)", fix_done,
|
||||
orig_done);
|
||||
TEST_EQ_STR(fix_back.c_str(), orig_back.c_str());
|
||||
}
|
||||
if (orig_done) { TEST_EQ_STR(fix_back.c_str(), orig_back.c_str()); }
|
||||
TEST_EQ_FUNC(fix_done, orig_done);
|
||||
}
|
||||
|
||||
// Create new parser and test default value
|
||||
if (true == orig_done) {
|
||||
flatbuffers::Parser def_parser(opts); // re-use options
|
||||
auto def_schema = "table X { Y: " + std::string(ref_res.type) + " = " +
|
||||
input + "; } root_type X;" +
|
||||
"{}"; // <- with empty json {}!
|
||||
|
||||
auto def_done = def_parser.Parse(def_schema.c_str());
|
||||
if (false == def_done) {
|
||||
TEST_OUTPUT_LINE("Stage 3.1 failed with _error = %s",
|
||||
def_parser.error_.c_str());
|
||||
FLATBUFFERS_ASSERT(false);
|
||||
}
|
||||
// Compare with print.
|
||||
std::string ref_string, def_string;
|
||||
FLATBUFFERS_ASSERT(!GenText(
|
||||
parser, parser.builder_.GetBufferPointer(), &ref_string));
|
||||
FLATBUFFERS_ASSERT(!GenText(
|
||||
def_parser, def_parser.builder_.GetBufferPointer(), &def_string));
|
||||
if (ref_string != def_string) {
|
||||
TEST_OUTPUT_LINE("Stage 3.2 failed: '%s' != '%s'", def_string.c_str(),
|
||||
ref_string.c_str());
|
||||
FLATBUFFERS_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore locale.
|
||||
if (use_locale) { FLATBUFFERS_ASSERT(setlocale(LC_ALL, "C")); }
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
#include "cpp17/generated_cpp17/monster_test_generated.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
flatbuffers::Verifier verifier(data, size);
|
||||
MyGame::Example::VerifyMonsterBuffer(verifier);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
#include "flatbuffers/flexbuffers.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
std::vector<uint8_t> reuse_tracker;
|
||||
// Check both with and without reuse tracker paths.
|
||||
flexbuffers::VerifyBuffer(data, size, &reuse_tracker);
|
||||
// FIXME: we can't really verify this path, because the fuzzer will
|
||||
// construct buffers that time out.
|
||||
// Add a simple #define to bound the number of steps just for the fuzzer?
|
||||
//flexbuffers::VerifyBuffer(data, size, nullptr);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef FUZZER_ASSERT_IMPL_H_
|
||||
#define FUZZER_ASSERT_IMPL_H_
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
extern "C" void __debugbreak();
|
||||
#define __builtin_trap __debugbreak
|
||||
#else // Clang
|
||||
extern "C" void __builtin_trap(void);
|
||||
#endif
|
||||
|
||||
// Declare Debug/Release independed assert macro.
|
||||
#define fuzzer_assert_impl(x) (!!(x) ? static_cast<void>(0) : __builtin_trap())
|
||||
|
||||
#endif // !FUZZER_ASSERT_IMPL_H_
|
||||
@@ -0,0 +1,30 @@
|
||||
#include <iostream>
|
||||
#include <assert.h>
|
||||
|
||||
#include "flatbuffers/util.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc < 2) {
|
||||
std::cerr << "Usage: monster_debug <path to fuzzer crash file>\n";
|
||||
return 0;
|
||||
}
|
||||
std::string crash_file_name(argv[1]);
|
||||
std::string crash_file_data;
|
||||
auto done =
|
||||
flatbuffers::LoadFile(crash_file_name.c_str(), true, &crash_file_data);
|
||||
if (!done) {
|
||||
std::cerr << "Can not load file: '" << crash_file_name << "'";
|
||||
return -1;
|
||||
}
|
||||
if (crash_file_data.size() < 3) {
|
||||
std::cerr << "Invalid file data: '" << crash_file_data << "'";
|
||||
return -2;
|
||||
}
|
||||
auto rc = LLVMFuzzerTestOneInput(
|
||||
reinterpret_cast<const uint8_t *>(crash_file_data.data()),
|
||||
crash_file_data.size());
|
||||
std::cout << "LLVMFuzzerTestOneInput finished with code " << rc << "\n\n";
|
||||
return rc;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
"{"
|
||||
"}"
|
||||
"["
|
||||
"]"
|
||||
"\""
|
||||
"'"
|
||||
"\\"
|
||||
"//"
|
||||
":"
|
||||
","
|
||||
" "
|
||||
"\\n"
|
||||
"\\r"
|
||||
"/*"
|
||||
"*/"
|
||||
"true"
|
||||
"false"
|
||||
"null"
|
||||
"\\u"
|
||||
"\\b"
|
||||
"\\f"
|
||||
"\\t"
|
||||
"."
|
||||
"e"
|
||||
"e+"
|
||||
"e-"
|
||||
"E"
|
||||
"E+"
|
||||
"E-"
|
||||
"0x"
|
||||
"p"
|
||||
"a"
|
||||
"b"
|
||||
"Monster"
|
||||
"pos"
|
||||
"hp"
|
||||
"name"
|
||||
"weapons"
|
||||
"damage"
|
||||
"equipped_type"
|
||||
"equipped"
|
||||
"inventory"
|
||||
"vector_of_longs"
|
||||
"vector_of_doubles"
|
||||
"test_type"
|
||||
"test"
|
||||
"test1"
|
||||
"test2"
|
||||
"test4"
|
||||
"test3"
|
||||
"test5"
|
||||
"enemy"
|
||||
"Weapon"
|
||||
"Green"
|
||||
"Red"
|
||||
"Blue"
|
||||
"testarrayofstring"
|
||||
"testarrayofbools"
|
||||
"testbool"
|
||||
"flex"
|
||||
@@ -0,0 +1,101 @@
|
||||
"struct"
|
||||
"table"
|
||||
"enum"
|
||||
"union"
|
||||
"include"
|
||||
"namespace"
|
||||
"attribute"
|
||||
"null"
|
||||
"NULL"
|
||||
"byte"
|
||||
"int8"
|
||||
"ubyte"
|
||||
"uint8"
|
||||
"bool"
|
||||
"short"
|
||||
"int16"
|
||||
"ushort"
|
||||
"uint16"
|
||||
"int"
|
||||
"int32"
|
||||
"uint"
|
||||
"uint32"
|
||||
"float"
|
||||
"float32"
|
||||
"long"
|
||||
"int64"
|
||||
"ulong"
|
||||
"uint64"
|
||||
"double"
|
||||
"float64"
|
||||
"root_type"
|
||||
"file_identifier"
|
||||
"file_extension"
|
||||
"{"
|
||||
"}"
|
||||
"["
|
||||
"]"
|
||||
"\""
|
||||
"'"
|
||||
"\\"
|
||||
"//"
|
||||
":"
|
||||
","
|
||||
" "
|
||||
"\\n"
|
||||
"\\r"
|
||||
"/*"
|
||||
"*/"
|
||||
"true"
|
||||
"false"
|
||||
"null"
|
||||
"\\u"
|
||||
"\\b"
|
||||
"\\f"
|
||||
"\\t"
|
||||
"."
|
||||
"e"
|
||||
"e+"
|
||||
"e-"
|
||||
"E"
|
||||
"E+"
|
||||
"E-"
|
||||
"0x"
|
||||
"p"
|
||||
"a"
|
||||
"b"
|
||||
"Monster"
|
||||
"pos"
|
||||
"hp"
|
||||
"name"
|
||||
"weapons"
|
||||
"damage"
|
||||
"equipped_type"
|
||||
"equipped"
|
||||
"inventory"
|
||||
"vector_of_longs"
|
||||
"vector_of_doubles"
|
||||
"test_type"
|
||||
"test"
|
||||
"test1"
|
||||
"test2"
|
||||
"test4"
|
||||
"test3"
|
||||
"test5"
|
||||
"enemy"
|
||||
"Weapon"
|
||||
"Green"
|
||||
"Red"
|
||||
"Blue"
|
||||
"testarrayofstring"
|
||||
"testarrayofbools"
|
||||
"testbool"
|
||||
"testhashs32_fnv1"
|
||||
"testhashu32_fnv1"
|
||||
"testhashs64_fnv1"
|
||||
"testhashu64_fnv1"
|
||||
"testhashs32_fnv1a"
|
||||
"testhashu32_fnv1a"
|
||||
"testhashs64_fnv1a"
|
||||
"testhashu64_fnv1a"
|
||||
"flex"
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
# Test Flatbuffers library with help of libFuzzer
|
||||
Test suite of Flatbuffers library has fuzzer section with tests are based on libFuzzer library.
|
||||
|
||||
> LibFuzzer is in-process, coverage-guided, evolutionary fuzzing engine.
|
||||
LibFuzzer is linked with the library under test, and feeds fuzzed inputs to the library via a specific fuzzing entrypoint (aka “target function”);
|
||||
the fuzzer then tracks which areas of the code are reached, and generates mutations on the corpus of input data in order to maximize the code coverage.
|
||||
The code coverage information for libFuzzer is provided by LLVM’s SanitizerCoverage instrumentation.
|
||||
|
||||
For details about **libFuzzer** see: https://llvm.org/docs/LibFuzzer.html
|
||||
|
||||
To build and run these tests LLVM compiler (with clang frontend) and CMake should be installed before.
|
||||
|
||||
The fuzzer section include four tests:
|
||||
- `annotator_fuzzer` checks that inputs given to the flatc --annotate are always parsable;
|
||||
- `verifier_fuzzer` checks stability of deserialization engine for `Monster` schema;
|
||||
- `parser_fuzzer` checks stability of schema and json parser under various inputs;
|
||||
- `scalar_parser` focused on validation of the parser while parse numeric scalars in schema and/or json files;
|
||||
- `flexverifier_fuzzer` checks stability of deserialization engine for FlexBuffers only;
|
||||
|
||||
## Build
|
||||
```sh
|
||||
cd tests/fuzzer
|
||||
CC=clang CXX=clang++ cmake . -DCMAKE_BUILD_TYPE=Debug -DUSE_ASAN=ON
|
||||
```
|
||||
|
||||
## Run tests with a specific locale
|
||||
The grammar of the Flatbuffers library is based on printable-ASCII characters.
|
||||
By design, the Flatbuffers library should be independent of the global or thread locales used by an end-user application.
|
||||
Set environment variable `FLATBUFFERS_TEST_LOCALE` to run a fuzzer with a specific C-locale:
|
||||
```sh
|
||||
>FLATBUFFERS_TEST_LOCALE="" ./scalar_parser
|
||||
>FLATBUFFERS_TEST_LOCALE="ru_RU.CP1251" ./parser_fuzzer
|
||||
```
|
||||
|
||||
## Run fuzzer
|
||||
These are examples of running a fuzzer.
|
||||
Flags may vary and depend on a version of the libFuzzer library.
|
||||
For details, run a fuzzer with `-help` flag: `./parser_fuzzer -help=1`
|
||||
|
||||
`./verifier_fuzzer ../.corpus_verifier/ ../.seed_verifier/`
|
||||
|
||||
`./parser_fuzzer -only_ascii=1 -max_len=500 -dict=../parser_fbs.dict ../.corpus_parser/ ../.seed_parser/`
|
||||
|
||||
`./monster_fuzzer -only_ascii=1 -max_len=500 -dict=../monster_json.dict ../.corpus_monster/ ../.seed_monster/`
|
||||
|
||||
`./scalar_fuzzer -use_value_profile=1 -max_len=500 -dict=../scalar_json.dict ../.corpus_scalar/ ../.seed_scalar/`
|
||||
|
||||
Flag `-only_ascii=1` is useful for fast number-compatibility checking while run `scalar_fuzzer`.
|
||||
|
||||
Run with a specific C-locale:
|
||||
`FLATBUFFERS_TEST_LOCALE="ru_RU.CP1251" ./scalar_fuzzer -reduce_depth=1 -use_value_profile=1 -shrink=1 -max_len=3000 -timeout=10 -rss_limit_mb=2048 ../.corpus_parser/ ../.seed_parser/`
|
||||
|
||||
|
||||
## Merge (minimize) corpus
|
||||
The **libFuzzer** allow to filter (minimize) corpus with help of `-merge` flag:
|
||||
> -merge
|
||||
If set to 1, any corpus inputs from the 2nd, 3rd etc. corpus directories that trigger new code coverage will be merged into the first corpus directory.
|
||||
Defaults to 0. This flag can be used to minimize a corpus.
|
||||
|
||||
Merge several corpuses to a seed directory (a new collected corpus to the seed collection, for example):
|
||||
`./verifier_fuzzer -merge=1 ../.seed_verifier/ ../.corpus_verifier/`
|
||||
`./parser_fuzzer -merge=1 ../.seed_parser/ ../.corpus_parser/`
|
||||
`./monster_fuzzer -merge=1 ../.seed_monster/ ../.corpus_monster/`
|
||||
`./scalar_fuzzer -merge=1 ../.seed_scalar/ ../.corpus_scalar/`
|
||||
|
||||
## Know limitations
|
||||
- LLVM 7.0 std::regex library has problem with stack overflow, maximum length of input for `scalar_fuzzer` run should be limited to 3000.
|
||||
Example: `./scalar_fuzzer -max_len=3000`
|
||||
|
||||
# Fuzzing control
|
||||
|
||||
## Set timeout or memory limit
|
||||
|
||||
`-timeout=10 -rss_limit_mb=2048 -jobs=4 -workers=4`.
|
||||
|
||||
## Force stop on first UBSAN error
|
||||
|
||||
- `export UBSAN_OPTIONS=halt_on_error=1`
|
||||
- `export ASAN_OPTIONS=halt_on_error=1`
|
||||
@@ -0,0 +1,28 @@
|
||||
#include <iostream>
|
||||
#include "flatbuffers/util.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc < 2) {
|
||||
std::cerr << "Usage: scalar_debug <path to fuzzer crash file>\n";
|
||||
return 0;
|
||||
}
|
||||
std::string crash_file_name(argv[1]);
|
||||
std::string crash_file_data;
|
||||
auto done =
|
||||
flatbuffers::LoadFile(crash_file_name.c_str(), true, &crash_file_data);
|
||||
if (!done) {
|
||||
std::cerr << "Can not load file: '" << crash_file_name << "'";
|
||||
return -1;
|
||||
}
|
||||
if (crash_file_data.size() < 3) {
|
||||
std::cerr << "Invalid file data: '" << crash_file_data << "'";
|
||||
return -2;
|
||||
}
|
||||
auto rc = LLVMFuzzerTestOneInput(
|
||||
reinterpret_cast<const uint8_t *>(crash_file_data.data()),
|
||||
crash_file_data.size());
|
||||
std::cout << "LLVMFuzzerTestOneInput finished with code " << rc << "\n\n";
|
||||
return rc;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
"-"
|
||||
"+"
|
||||
"."
|
||||
"e"
|
||||
"e+"
|
||||
"e-"
|
||||
"E"
|
||||
"E+"
|
||||
"E-"
|
||||
"0x"
|
||||
"-0x"
|
||||
"p"
|
||||
"0"
|
||||
"1"
|
||||
"2"
|
||||
"3"
|
||||
"4"
|
||||
"5"
|
||||
"6"
|
||||
"7"
|
||||
"8"
|
||||
"9"
|
||||
"a"
|
||||
"b"
|
||||
"c"
|
||||
"d"
|
||||
"e"
|
||||
"f"
|
||||
"nan"
|
||||
"-nan"
|
||||
"+nan"
|
||||
"inf"
|
||||
"+inf"
|
||||
"-inf"
|
||||
"infinity"
|
||||
"+infinity"
|
||||
"-infinity"
|
||||
@@ -0,0 +1,52 @@
|
||||
|
||||
#ifndef FUZZER_TEST_INIT_H_
|
||||
#define FUZZER_TEST_INIT_H_
|
||||
|
||||
#include "fuzzer_assert.h"
|
||||
#include "test_assert.h"
|
||||
|
||||
// Utility for test run.
|
||||
struct OneTimeTestInit {
|
||||
// Declare trap for the Flatbuffers test engine.
|
||||
// This hook terminate program both in Debug and Release.
|
||||
static bool TestFailListener(const char *expval, const char *val,
|
||||
const char *exp, const char *file, int line,
|
||||
const char *func = nullptr) {
|
||||
(void)expval;
|
||||
(void)val;
|
||||
(void)exp;
|
||||
(void)file;
|
||||
(void)line;
|
||||
(void)func;
|
||||
// FLATBUFFERS_ASSERT redefined to be fully independent of the Flatbuffers
|
||||
// library implementation (see test_assert.h for details).
|
||||
fuzzer_assert_impl(false); // terminate
|
||||
return false;
|
||||
}
|
||||
|
||||
OneTimeTestInit() : has_locale_(false) {
|
||||
// Fuzzer test should be independent of the test engine implementation.
|
||||
// This hook will terminate test if TEST_EQ/TEST_ASSERT asserted.
|
||||
InitTestEngine(OneTimeTestInit::TestFailListener);
|
||||
|
||||
// Read a locale for the test.
|
||||
if (flatbuffers::ReadEnvironmentVariable("FLATBUFFERS_TEST_LOCALE",
|
||||
&test_locale_)) {
|
||||
TEST_OUTPUT_LINE("The environment variable FLATBUFFERS_TEST_LOCALE=%s",
|
||||
test_locale_.c_str());
|
||||
test_locale_ = flatbuffers::RemoveStringQuotes(test_locale_);
|
||||
has_locale_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *test_locale() {
|
||||
return one_time_init_.has_locale_ ? nullptr
|
||||
: one_time_init_.test_locale_.c_str();
|
||||
}
|
||||
|
||||
bool has_locale_;
|
||||
std::string test_locale_;
|
||||
static OneTimeTestInit one_time_init_;
|
||||
};
|
||||
|
||||
#endif // !FUZZER_TEST_INIT_H_
|
||||
Reference in New Issue
Block a user