-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Description
Summary
FieldIdToIndex() in src/bfbs_gen.h and ForAllFields() in src/reflection.cpp use field->id() as a direct array index into a vector sized by the number of fields, without bounds checking. Since field->id() is a uint16_t read from the binary FlatBuffer Schema (BFBS), a crafted .bfbs file can specify field IDs up to 65535 while having only 2 fields - causing a heap out-of-bounds write of up to 262,132 bytes past the allocation.
The FlatBuffers binary verifier (reflection::VerifySchemaBuffer) validates structural integrity but does not enforce the semantic constraint field->id() < object->fields()->size(). A malicious BFBS file passes verification, then triggers the OOB write when processed.
This issue was reported to Google VRP and closed with permission to publicly disclose.
Affected Version
- Repository: google/flatbuffers
- Commit:
94d6b80(flatc 25.12.19) - Affected files:
src/bfbs_gen.h,src/reflection.cpp
Root Cause
Location 1: src/bfbs_gen.h L68-79
static std::vector<uint32_t> FieldIdToIndex(const reflection::Object* object) {
std::vector<uint32_t> field_index_by_id;
field_index_by_id.resize(object->fields()->size()); // e.g., size = 2
for (uint32_t i = 0; i < object->fields()->size(); ++i) {
auto field = object->fields()->Get(i);
field_index_by_id[field->id()] = i; // BUG: field->id() can be 0..65535
}
return field_index_by_id;
}Location 2: src/reflection.cpp L379-394
Same pattern - field_to_id_map[field->id()] = i with no bounds check.
Why Verification Doesn't Catch This
The generated verifier (reflection_generated.h L683-706) only checks:
VerifyField<uint16_t>(verifier, VT_ID, 2)This validates the id field is a properly-located uint16_t. It does NOT check id() < parent_object->fields()->size().
Reproduction
Build the harness
g++ -std=c++17 -fsanitize=address -g -I include/ -o oob_harness oob_harness.cppGenerate the patched BFBS
Use the included patch_bfbs.py to set field->id() to 65535 in a valid BFBS file.
Run
ASAN_OPTIONS=detect_leaks=0 ./oob_harness oob_test_patched.bfbsASAN Output
AddressSanitizer:DEADLYSIGNAL
==70269==ERROR: AddressSanitizer: BUS on unknown address
==70269==The signal is caused by a WRITE memory access.
#0 0x1021dd968 in FieldIdToIndex_Vulnerable(reflection::Object const*) oob_harness.cpp:47
#1 0x1021dcfe8 in main oob_harness.cpp:111
Register values:
x[1] = 0x000000000000ffff ← field->id() = 65535
SUMMARY: AddressSanitizer: BUS oob_harness.cpp:47 in FieldIdToIndex_Vulnerable
PoC Output
[*] Object: OOBTest.TestTable (2 fields)
Field[0]: field_a, id=0, offset=4
Field[1]: field_b, id=65535, offset=6
[*] Calling FieldIdToIndex (equivalent to bfbs_gen.h:68-79)...
[!] OOB DETECTED: field->id()=65535 >= array_size=2
[!] About to write to index 65535 (OOB by 65533 elements = 262132 bytes)
Impact
- CWE-787 (Out-of-bounds Write)
- CVSS 3.1: 8.1 HIGH -
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H - Controlled write of a
uint32_tvalue at a controlled offset (up to 262,140 bytes past allocation) - Triggers via
flatc --lua,flatc --nim,flatc --annotate, or any application using the reflection API'sForAllFields() - CI/CD pipelines processing untrusted
.bfbsfiles are directly exploitable
Suggested Fix
Add bounds check on field->id():
for (uint32_t i = 0; i < num_fields; ++i) {
auto field = object->fields()->Get(i);
if (field->id() < num_fields) {
field_index_by_id[field->id()] = i;
}
}Or add semantic validation to VerifySchemaBuffer() checking field->id() < object->fields()->size().
Related
- VRP-FB-014:
VerifySchemaBuffer()semantic bypass enables this finding - the verifier passes on the malicious BFBS.