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
Root Cause
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;
}
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.cpp
Generate 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.bfbs
ASAN 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_t value 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's ForAllFields()
- CI/CD pipelines processing untrusted
.bfbs files 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.
asan_output.txt
oob_harness.cpp
patch_bfbs.py
Summary
FieldIdToIndex()insrc/bfbs_gen.handForAllFields()insrc/reflection.cppusefield->id()as a direct array index into a vector sized by the number of fields, without bounds checking. Sincefield->id()is auint16_tread from the binary FlatBuffer Schema (BFBS), a crafted.bfbsfile 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 constraintfield->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
94d6b80(flatc 25.12.19)src/bfbs_gen.h,src/reflection.cppRoot Cause
Location 1:
src/bfbs_gen.hL68-79Location 2:
src/reflection.cppL379-394Same pattern -
field_to_id_map[field->id()] = iwith no bounds check.Why Verification Doesn't Catch This
The generated verifier (
reflection_generated.hL683-706) only checks:This validates the
idfield is a properly-located uint16_t. It does NOT checkid() < parent_object->fields()->size().Reproduction
Build the harness
Generate the patched BFBS
Use the included
patch_bfbs.pyto setfield->id()to 65535 in a valid BFBS file.Run
ASAN Output
PoC Output
Impact
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:Huint32_tvalue at a controlled offset (up to 262,140 bytes past allocation)flatc --lua,flatc --nim,flatc --annotate, or any application using the reflection API'sForAllFields().bfbsfiles are directly exploitableSuggested Fix
Add bounds check on
field->id():Or add semantic validation to
VerifySchemaBuffer()checkingfield->id() < object->fields()->size().Related
VerifySchemaBuffer()semantic bypass enables this finding - the verifier passes on the malicious BFBS.asan_output.txt
oob_harness.cpp
patch_bfbs.py