diff --git a/tests/fuzzer/CMakeLists.txt b/tests/fuzzer/CMakeLists.txt index 272c121aab..668b994938 100644 --- a/tests/fuzzer/CMakeLists.txt +++ b/tests/fuzzer/CMakeLists.txt @@ -210,6 +210,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(reflection_fuzzer flatbuffers_reflection_fuzzer.cc) +target_link_libraries(reflection_fuzzer PRIVATE flatbuffers_fuzzed) + add_executable(flexverifier_fuzzer flexbuffers_verifier_fuzzer.cc) target_link_libraries(flexverifier_fuzzer PRIVATE flatbuffers_fuzzed) @@ -289,6 +292,12 @@ if(BUILD_DEBUGGER) monster_debug.cpp ) target_link_libraries(monster_debug PRIVATE flatbuffers_nonfuzz) + + add_executable(reflection_debug + flatbuffers_reflection_fuzzer.cc + reflection_debug.cpp + ) + target_link_libraries(reflection_debug PRIVATE flatbuffers_nonfuzz) add_custom_command( TARGET monster_debug PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy diff --git a/tests/fuzzer/flatbuffers_reflection_fuzzer.cc b/tests/fuzzer/flatbuffers_reflection_fuzzer.cc new file mode 100644 index 0000000000..369e2f13cc --- /dev/null +++ b/tests/fuzzer/flatbuffers_reflection_fuzzer.cc @@ -0,0 +1,50 @@ +/* + * Fuzzes the C++ reflection API with an untrusted schema and an untrusted + * data buffer, mirroring the documented runtime-schema use case: + * 1. reflection::VerifySchemaBuffer() on the schema, + * 2. flatbuffers::Verify(schema, root_object, ...) on the data, + * 3. flatbuffers::CopyTable() round-trip of the verified root table. + * + * Input format (all little-endian): + * u32 schema_length + * u8[schema_length] binary schema (.bfbs) + * u8[...] flatbuffer data verified against the schema + * + * Splitting the input this way lets the fuzzer mutate both the schema and + * the data, which is the attack surface of every schema-driven consumer. + */ +#include "flatbuffers/flatbuffers.h" +#include "flatbuffers/reflection.h" +#include "flatbuffers/util.h" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size < sizeof(flatbuffers::uoffset_t) * 2) return 0; + + const size_t schema_length = + flatbuffers::ReadScalar(data); + if (schema_length > size - sizeof(flatbuffers::uoffset_t)) return 0; + + const uint8_t *schema_buf = data + sizeof(flatbuffers::uoffset_t); + const size_t remaining = + size - sizeof(flatbuffers::uoffset_t) - schema_length; + if (remaining < sizeof(flatbuffers::uoffset_t)) return 0; + const uint8_t *data_buf = schema_buf + schema_length; + + // 1. The schema itself must verify before it is used. + flatbuffers::Verifier schema_verifier(schema_buf, schema_length); + if (!reflection::VerifySchemaBuffer(schema_verifier)) return 0; + + const auto *schema = reflection::GetSchema(schema_buf); + const auto *root_object = schema->root_table(); + if (root_object == nullptr) return 0; + + // 2. The data buffer must verify against the schema. + if (!flatbuffers::Verify(*schema, *root_object, data_buf, remaining)) + return 0; + + // 3. Consume the verified pair the way a schema-driven tool would. + const auto *root = flatbuffers::GetAnyRoot(data_buf); + flatbuffers::FlatBufferBuilder builder; + flatbuffers::CopyTable(builder, *schema, *root_object, *root); + return 0; +} diff --git a/tests/fuzzer/reflection_debug.cpp b/tests/fuzzer/reflection_debug.cpp new file mode 100644 index 0000000000..468b03e33a --- /dev/null +++ b/tests/fuzzer/reflection_debug.cpp @@ -0,0 +1,29 @@ +#include + +#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: reflection_debug \n"; + return 0; + } + std::string input_file_name(argv[1]); + std::string input_file_data; + auto done = + flatbuffers::LoadFile(input_file_name.c_str(), true, &input_file_data); + if (!done) { + std::cerr << "Can not load file: '" << input_file_name << "'"; + return -1; + } + if (input_file_data.size() < 8) { + std::cerr << "Invalid file data: '" << input_file_data << "'"; + return -2; + } + auto rc = LLVMFuzzerTestOneInput( + reinterpret_cast(input_file_data.data()), + input_file_data.size()); + std::cout << "LLVMFuzzerTestOneInput finished with code " << rc << "\n\n"; + return rc; +}