/src/flatbuffers/tests/fuzzer/flatbuffers_monster_fuzzer.cc
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright 2014 Google Inc. All rights reserved. |
3 | | * |
4 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | | * you may not use this file except in compliance with the License. |
6 | | * You may obtain a copy of the License at |
7 | | * |
8 | | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | | * |
10 | | * Unless required by applicable law or agreed to in writing, software |
11 | | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | | * See the License for the specific language governing permissions and |
14 | | * limitations under the License. |
15 | | */ |
16 | | |
17 | | #include <stddef.h> |
18 | | #include <stdint.h> |
19 | | |
20 | | #include <clocale> |
21 | | #include <filesystem> |
22 | | #include <string> |
23 | | |
24 | | #include "cpp17/generated_cpp17/monster_test_generated.h" |
25 | | #include "flatbuffers/idl.h" |
26 | | #include "test_assert.h" |
27 | | #include "test_init.h" |
28 | | |
29 | | namespace fs = std::filesystem; |
30 | | |
31 | | // Utility for test run. |
32 | | OneTimeTestInit OneTimeTestInit::one_time_init_; |
33 | | // The current executable path (see LLVMFuzzerInitialize). |
34 | | static fs::path exe_path_; |
35 | | |
36 | | static flatbuffers::Parser parser_; |
37 | | |
38 | | namespace { |
39 | | |
40 | | static constexpr size_t kMinInputLength = 1; |
41 | | static constexpr size_t kMaxInputLength = 16384; |
42 | | |
43 | | static constexpr uint8_t flags_strict_json = 0x80; |
44 | | static constexpr uint8_t flags_skip_unexpected_fields_in_json = 0x40; |
45 | | static constexpr uint8_t flags_allow_non_utf8 = 0x20; |
46 | | |
47 | 2 | bool TestFileExists(fs::path file_path) { |
48 | 2 | if (file_path.has_filename() && fs::exists(file_path)) return true; |
49 | | |
50 | 0 | TEST_OUTPUT_LINE("@DEBUG: file '%s' not found", file_path.string().c_str()); |
51 | 0 | for (const auto &entry : fs::directory_iterator(file_path.parent_path())) { |
52 | 0 | TEST_OUTPUT_LINE("@DEBUG: parent path entry: '%s'", |
53 | 0 | entry.path().string().c_str()); |
54 | 0 | } |
55 | 0 | return false; |
56 | 2 | } |
57 | | |
58 | 2 | std::string LoadBinarySchema(const char *file_name) { |
59 | 2 | const auto file_path = exe_path_.parent_path() / file_name; |
60 | 2 | TEST_EQ(true, TestFileExists(file_path)); |
61 | 2 | std::string schemafile; |
62 | 2 | TEST_EQ(true, |
63 | 2 | flatbuffers::LoadFile(file_path.string().c_str(), true, &schemafile)); |
64 | | |
65 | 2 | flatbuffers::Verifier verifier( |
66 | 2 | reinterpret_cast<const uint8_t *>(schemafile.c_str()), schemafile.size()); |
67 | 2 | TEST_EQ(true, reflection::VerifySchemaBuffer(verifier)); |
68 | 2 | return schemafile; |
69 | 2 | } |
70 | | |
71 | | std::string do_test(const flatbuffers::IDLOptions &opts, |
72 | 21.4k | const std::string input_json, const bool check_parser) { |
73 | | // (re)define parser options |
74 | 21.4k | parser_.opts = opts; |
75 | | |
76 | 21.4k | std::string jsongen; |
77 | 21.4k | if (parser_.ParseJson(input_json.c_str())) { |
78 | 9.23k | flatbuffers::Verifier verifier(parser_.builder_.GetBufferPointer(), |
79 | 9.23k | parser_.builder_.GetSize()); |
80 | 9.23k | TEST_EQ(true, MyGame::Example::VerifyMonsterBuffer(verifier)); |
81 | 9.23k | TEST_NULL( |
82 | 9.23k | GenText(parser_, parser_.builder_.GetBufferPointer(), &jsongen)); |
83 | 12.1k | } else if (check_parser) { |
84 | 0 | TEST_OUTPUT_LINE("parser failed with JSON:\n%s", input_json.c_str()); |
85 | 0 | TEST_EQ_STR("", parser_.error_.c_str()); |
86 | 0 | TEST_ASSERT(false); |
87 | 0 | } |
88 | 21.4k | return jsongen; |
89 | 21.4k | }; |
90 | | } // namespace |
91 | | |
92 | | // https://google.github.io/oss-fuzz/further-reading/fuzzer-environment/ |
93 | | // Current working directory |
94 | | // You should not make any assumptions about the current working directory of |
95 | | // your fuzz target. If you need to load data files, please use argv[0] to get |
96 | | // the directory where your fuzz target executable is located. |
97 | | // You must not modify argv[0]. |
98 | 6 | extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) { |
99 | 6 | (void)argc; |
100 | 6 | exe_path_ = (*argv)[0]; |
101 | | |
102 | 6 | static const std::string schemafile = LoadBinarySchema("monster_test.bfbs"); |
103 | | // parse schema first, so we can use it to parse the data after |
104 | 6 | parser_.Deserialize(reinterpret_cast<const uint8_t *>(schemafile.c_str()), |
105 | 6 | schemafile.size()); |
106 | 6 | return 0; |
107 | 6 | } |
108 | | |
109 | 16.8k | extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { |
110 | | // Reserve one byte for Parser flags and one byte for repetition counter. |
111 | 16.8k | if (size < 3) return 0; |
112 | 16.8k | const uint8_t flags = data[0]; |
113 | 16.8k | (void)data[1]; // reserved |
114 | 16.8k | data += 2; |
115 | 16.8k | size -= 2; // bypass |
116 | | |
117 | 16.8k | const std::string original(reinterpret_cast<const char *>(data), size); |
118 | 16.8k | auto input = std::string(original.c_str()); // until '\0' |
119 | 16.8k | if (input.size() < kMinInputLength || input.size() > kMaxInputLength) |
120 | 25 | return 0; |
121 | | |
122 | 16.7k | flatbuffers::IDLOptions opts; |
123 | 16.7k | opts.strict_json = (flags & flags_strict_json); |
124 | 16.7k | opts.skip_unexpected_fields_in_json = |
125 | 16.7k | (flags & flags_skip_unexpected_fields_in_json); |
126 | 16.7k | opts.allow_non_utf8 = (flags & flags_allow_non_utf8); |
127 | | |
128 | 16.7k | do { |
129 | 16.7k | const std::string jsongen_1 = do_test(opts, input, false); |
130 | 16.7k | if (!jsongen_1.empty()) { |
131 | 4.61k | const std::string jsongen_2 = do_test(opts, jsongen_1, true); |
132 | 4.61k | if (jsongen_1 != jsongen_2 && !opts.output_default_scalars_in_json) { |
133 | | // This gets tricky when the jsongen_1 includes a default-value, as the |
134 | | // generated jsongen_2 doesn't emit default-values. So enable default |
135 | | // scalars and re-run it. |
136 | 686 | opts.output_default_scalars_in_json = true; |
137 | 686 | continue; |
138 | 686 | } |
139 | 3.92k | TEST_EQ(jsongen_1, jsongen_2); |
140 | 3.92k | } |
141 | 16.7k | } while (0); |
142 | | |
143 | 0 | return 0; |
144 | 16.8k | } |