Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions tests/fuzzer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down
50 changes: 50 additions & 0 deletions tests/fuzzer/flatbuffers_reflection_fuzzer.cc
Original file line number Diff line number Diff line change
@@ -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<flatbuffers::uoffset_t>(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;
}
29 changes: 29 additions & 0 deletions tests/fuzzer/reflection_debug.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#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: reflection_debug <path to fuzzer input file>\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<const uint8_t*>(input_file_data.data()),
input_file_data.size());
std::cout << "LLVMFuzzerTestOneInput finished with code " << rc << "\n\n";
return rc;
}