mirror of
https://github.com/google/flatbuffers.git
synced 2026-06-09 06:30:54 +00:00
[C++] Add ParseJson(), Parser(Parser&&), update fuzzers (#6284)
- add a new method ParseJson to minimize failures during fuzzing - add default (conditional) move-constructor for Parser - add a new monster_fuzzer - switch fuzzers to C++17 and `test/cpp17` generated code
This commit is contained in:
@@ -29,7 +29,7 @@ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld")
|
||||
|
||||
add_compile_options(
|
||||
# -stdlib=libc++ # Use Clang libc++ instead of GNU.
|
||||
-std=c++14
|
||||
-std=c++17
|
||||
-Wall
|
||||
-pedantic
|
||||
-Werror
|
||||
@@ -52,7 +52,9 @@ add_library(fuzzer_config INTERFACE)
|
||||
target_compile_options(
|
||||
fuzzer_config
|
||||
INTERFACE
|
||||
#-fsanitize-coverage=edge,trace-cmp
|
||||
$<$<NOT:$<BOOL:${OSS_FUZZ}>>:
|
||||
-fsanitize-coverage=edge,trace-cmp
|
||||
>
|
||||
$<$<BOOL:${USE_ASAN}>:
|
||||
-fsanitize=fuzzer,undefined,address
|
||||
>
|
||||
@@ -131,6 +133,9 @@ 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(monster_fuzzer flatbuffers_monster_fuzzer.cc)
|
||||
target_link_libraries(monster_fuzzer PRIVATE flatbuffers_fuzzed)
|
||||
|
||||
# Build debugger for weird cases found with fuzzer.
|
||||
if(BUILD_DEBUGGER)
|
||||
add_library(flatbuffers_nonfuzz STATIC ${FlatBuffers_Library_SRCS})
|
||||
|
||||
118
tests/fuzzer/flatbuffers_monster_fuzzer.cc
Normal file
118
tests/fuzzer/flatbuffers_monster_fuzzer.cc
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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 <string>
|
||||
|
||||
#include "cpp17/generated_cpp17/monster_test_generated.h"
|
||||
#include "flatbuffers/idl.h"
|
||||
#include "test_init.h"
|
||||
|
||||
namespace {
|
||||
constexpr bool use_binary_schema = true;
|
||||
// should point to flatbuffers/tests/
|
||||
constexpr const char *test_data_path = "../../";
|
||||
constexpr const char *schema_file_name = "monster_test";
|
||||
|
||||
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;
|
||||
|
||||
flatbuffers::Parser make_parser(const flatbuffers::IDLOptions opts) {
|
||||
// once loaded from disk
|
||||
static const std::string schemafile = [&]() {
|
||||
std::string schemafile;
|
||||
TEST_EQ(
|
||||
flatbuffers::LoadFile((std::string(test_data_path) + schema_file_name +
|
||||
(use_binary_schema ? ".bfbs" : ".fbs"))
|
||||
.c_str(),
|
||||
use_binary_schema, &schemafile),
|
||||
true);
|
||||
|
||||
if (use_binary_schema) {
|
||||
flatbuffers::Verifier verifier(
|
||||
reinterpret_cast<const uint8_t *>(schemafile.c_str()),
|
||||
schemafile.size());
|
||||
TEST_EQ(reflection::VerifySchemaBuffer(verifier), true);
|
||||
}
|
||||
return schemafile;
|
||||
}();
|
||||
|
||||
// parse schema first, so we can use it to parse the data after
|
||||
flatbuffers::Parser parser;
|
||||
if (use_binary_schema) {
|
||||
TEST_EQ(parser.Deserialize(
|
||||
reinterpret_cast<const uint8_t *>(schemafile.c_str()),
|
||||
schemafile.size()),
|
||||
true);
|
||||
} else {
|
||||
auto include_test_path =
|
||||
flatbuffers::ConCatPathFileName(test_data_path, "include_test");
|
||||
const char *include_directories[] = { test_data_path,
|
||||
include_test_path.c_str(), nullptr };
|
||||
TEST_EQ(parser.Parse(schemafile.c_str(), include_directories), true);
|
||||
}
|
||||
// (re)define parser options
|
||||
parser.opts = opts;
|
||||
return parser;
|
||||
}
|
||||
|
||||
std::string do_test(const flatbuffers::IDLOptions &opts,
|
||||
const std::string input_json) {
|
||||
auto parser = make_parser(opts);
|
||||
std::string jsongen;
|
||||
if (parser.ParseJson(input_json.c_str())) {
|
||||
flatbuffers::Verifier verifier(parser.builder_.GetBufferPointer(),
|
||||
parser.builder_.GetSize());
|
||||
TEST_EQ(MyGame::Example::VerifyMonsterBuffer(verifier), true);
|
||||
TEST_ASSERT(
|
||||
GenerateText(parser, parser.builder_.GetBufferPointer(), &jsongen));
|
||||
}
|
||||
return jsongen;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// 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.empty()) 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);
|
||||
|
||||
const std::string jsongen_1 = do_test(opts, input);
|
||||
if (!jsongen_1.empty()) {
|
||||
const std::string jsongen_2 = do_test(opts, jsongen_1);
|
||||
TEST_EQ(jsongen_1, jsongen_2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -9,14 +9,9 @@
|
||||
#include "flatbuffers/idl.h"
|
||||
#include "test_init.h"
|
||||
|
||||
static constexpr uint8_t flags_strict_json = 0x01;
|
||||
static constexpr uint8_t flags_skip_unexpected_fields_in_json = 0x02;
|
||||
static constexpr uint8_t flags_allow_non_utf8 = 0x04;
|
||||
// static constexpr uint8_t flags_flag_3 = 0x08;
|
||||
// static constexpr uint8_t flags_flag_4 = 0x10;
|
||||
// static constexpr uint8_t flags_flag_5 = 0x20;
|
||||
// static constexpr uint8_t flags_flag_6 = 0x40;
|
||||
// static constexpr uint8_t flags_flag_7 = 0x80;
|
||||
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_;
|
||||
|
||||
@@ -1,6 +1,23 @@
|
||||
/*
|
||||
* 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>
|
||||
@@ -196,7 +213,7 @@ class ScalarReferenceResult {
|
||||
|
||||
bool Parse(flatbuffers::Parser &parser, const std::string &json,
|
||||
std::string *_text) {
|
||||
auto done = parser.Parse(json.c_str());
|
||||
auto done = parser.ParseJson(json.c_str());
|
||||
if (done) {
|
||||
TEST_EQ(GenerateText(parser, parser.builder_.GetBufferPointer(), _text),
|
||||
true);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
#include "monster_test_generated.h"
|
||||
#include "cpp17/generated_cpp17/monster_test_generated.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
flatbuffers::Verifier verifier(data, size);
|
||||
|
||||
60
tests/fuzzer/monster_json.dict
Normal file
60
tests/fuzzer/monster_json.dict
Normal file
@@ -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"
|
||||
101
tests/fuzzer/parser_fbs.dict
Normal file
101
tests/fuzzer/parser_fbs.dict
Normal file
@@ -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"
|
||||
@@ -29,27 +29,43 @@ 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 -reduce_depth=1 -use_value_profile=1 -shrink=1 ../.corpus_verifier/`
|
||||
`./verifier_fuzzer ../.corpus_verifier/ ../.seed_verifier/`
|
||||
|
||||
`./parser_fuzzer -reduce_depth=1 -use_value_profile=1 -shrink=1 ../.corpus_parser/`
|
||||
`./parser_fuzzer -only_ascii=1 -max_len=500 -dict=../parser_fbs.dict ../.corpus_parser/ ../.seed_parser/`
|
||||
|
||||
`./scalar_fuzzer -reduce_depth=1 -use_value_profile=1 -shrink=1 -max_len=3000 ../.corpus_parser/ ../.seed_parser/`
|
||||
`./monster_fuzzer -only_ascii=1 -max_len=500 -dict=../monster_json.dict ../.corpus_monster/ ../.seed_monster/`
|
||||
|
||||
Flag `-only_ascii=1` is useful for fast number-compatibility checking while run `scalar_fuzzer`:
|
||||
`./scalar_fuzzer -only_ascii=1 -reduce_depth=1 -use_value_profile=1 -shrink=1 -max_len=3000 -timeout=10 -rss_limit_mb=2048 -jobs=2 ../.corpus_parser/ ../.seed_parser/`
|
||||
`./scalar_fuzzer -use_value_profile=1 -max_len=500 -dict=../scalar_json.dict ../.corpus_scalar/ ../.seed_scalar/`
|
||||
|
||||
Run with a specific C-locale:
|
||||
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 seeds to one (a new collected corpus to the seed collection, for example):
|
||||
`./scalar_fuzzer -merge=1 ../.seed_parser/ ../.corpus_parser/`
|
||||
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`
|
||||
|
||||
23
tests/fuzzer/scalar_json.dict
Normal file
23
tests/fuzzer/scalar_json.dict
Normal file
@@ -0,0 +1,23 @@
|
||||
"-"
|
||||
"+"
|
||||
"."
|
||||
"e"
|
||||
"e+"
|
||||
"e-"
|
||||
"E"
|
||||
"E+"
|
||||
"E-"
|
||||
"0x"
|
||||
"-0x"
|
||||
"p"
|
||||
"a"
|
||||
"b"
|
||||
"c"
|
||||
"d"
|
||||
"e"
|
||||
"f"
|
||||
"nan"
|
||||
"inf"
|
||||
"-inf"
|
||||
"infinity"
|
||||
"-infinity"
|
||||
@@ -816,7 +816,7 @@ void ParseAndGenerateTextTest(bool binary) {
|
||||
} else {
|
||||
TEST_EQ(parser.Parse(schemafile.c_str(), include_directories), true);
|
||||
}
|
||||
TEST_EQ(parser.Parse(jsonfile.c_str(), include_directories), true);
|
||||
TEST_EQ(parser.ParseJson(jsonfile.c_str()), true);
|
||||
|
||||
// here, parser.builder_ contains a binary buffer that is the parsed data.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user