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: 6 additions & 3 deletions include/flatbuffers/idl.h
Original file line number Diff line number Diff line change
Expand Up @@ -531,15 +531,18 @@ inline bool IsString(const Type& type) {
}

inline bool IsStruct(const Type& type) {
return type.base_type == BASE_TYPE_STRUCT && type.struct_def->fixed;
return type.base_type == BASE_TYPE_STRUCT && type.struct_def != nullptr &&
type.struct_def->fixed;
}

inline bool IsIncompleteStruct(const Type& type) {
return type.base_type == BASE_TYPE_STRUCT && type.struct_def->predecl;
return type.base_type == BASE_TYPE_STRUCT && type.struct_def != nullptr &&
type.struct_def->predecl;
}

inline bool IsTable(const Type& type) {
return type.base_type == BASE_TYPE_STRUCT && !type.struct_def->fixed;
return type.base_type == BASE_TYPE_STRUCT && type.struct_def != nullptr &&
!type.struct_def->fixed;
}

inline bool IsUnion(const Type& type) {
Expand Down
12 changes: 11 additions & 1 deletion src/idl_gen_text.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -447,8 +447,18 @@ const char* GenTextFile(const Parser& parser, const std::string& path,
: "SaveFile failed";
}
if (!parser.builder_.GetSize() || !parser.root_struct_def_) return nullptr;
// Verify the buffer before traversal to prevent out-of-bounds reads from
// corrupted offsets or vector lengths in untrusted binary FlatBuffers.
// Without this, GenText trusts serialized field offsets and vector lengths
// which can cause heap OOB reads or crashes (see issue #9051).
auto buf = parser.builder_.GetBufferPointer();
auto buf_size = parser.builder_.GetSize();
flatbuffers::Verifier verifier(buf, buf_size);
if (!verifier.VerifyBuffer<Table>(nullptr)) {
return "buffer verification failed: invalid or corrupted FlatBuffer";
}
std::string text;
auto err = GenText(parser, parser.builder_.GetBufferPointer(), &text);
auto err = GenText(parser, buf, &text);
if (err) return err;
return parser.opts.file_saver->SaveFile(TextFileName(path, file_name).c_str(),
text, false)
Expand Down
49 changes: 41 additions & 8 deletions src/idl_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4130,15 +4130,32 @@ bool StructDef::Deserialize(Parser& parser, const reflection::Object* object) {
name = parser.UnqualifiedName(object->name()->str());
predecl = false;
sortbysize = attributes.Lookup("original_order") == nullptr && !fixed;
const auto& of = *(object->fields());
auto indexes = std::vector<uoffset_t>(of.size());
for (uoffset_t i = 0; i < of.size(); i++) {
uint16_t field_id = of.Get(i)->id();
if (field_id >= of.size()) {
auto fields_ptr = object->fields();
if (!fields_ptr || fields_ptr->size() == 0) {
parser.error_ = "Object has no fields";
return false;
}
const auto& of = *fields_ptr;
const auto field_count = of.size();
auto indexes = std::vector<uoffset_t>(field_count);
std::vector<bool> id_used(field_count, false);
for (uoffset_t i = 0; i < field_count; i++) {
auto field_ptr = of.Get(i);
if (!field_ptr) {
parser.error_ = "Null field at index " + std::to_string(i);
return false;
}
uint16_t field_id = field_ptr->id();
if (field_id >= field_count) {
parser.error_ = "Field ID " + std::to_string(field_id) +
" exceeds field count " + std::to_string(of.size());
" exceeds field count " + std::to_string(field_count);
return false;
}
if (id_used[field_id]) {
parser.error_ = "Duplicate field ID " + std::to_string(field_id);
return false;
}
id_used[field_id] = true;
indexes[field_id] = i;
}
size_t tmp_struct_size = 0;
Expand All @@ -4159,6 +4176,17 @@ bool StructDef::Deserialize(Parser& parser, const reflection::Object* object) {
has_key = true;
}
if (fixed) {
// Validate that struct-typed fields have resolved struct_def
// references before computing layout. A malformed bfbs schema may
// declare BASE_TYPE_STRUCT without a valid cross-reference, which
// would cause a null-pointer dereference in InlineSize/IsStruct.
if (field_def->value.type.base_type == BASE_TYPE_STRUCT &&
field_def->value.type.struct_def == nullptr) {
parser.error_ = "unresolved struct reference in field '" +
field_def->name + "'";
delete field_def;
return false;
}
// Recompute padding since that's currently not serialized.
auto size = InlineSize(field_def->value.type);
auto next_field =
Expand Down Expand Up @@ -4313,9 +4341,13 @@ Offset<reflection::Enum> EnumDef::Serialize(FlatBufferBuilder* builder,

bool EnumDef::Deserialize(Parser& parser, const reflection::Enum* _enum) {
name = parser.UnqualifiedName(_enum->name()->str());
for (uoffset_t i = 0; i < _enum->values()->size(); ++i) {
auto values_ptr = _enum->values();
if (!values_ptr) {
return false;
}
for (uoffset_t i = 0; i < values_ptr->size(); ++i) {
auto val = new EnumVal();
if (!val->Deserialize(parser, _enum->values()->Get(i)) ||
if (!val->Deserialize(parser, values_ptr->Get(i)) ||
vals.Add(val->name, val)) {
delete val;
return false;
Expand Down Expand Up @@ -4520,6 +4552,7 @@ bool Parser::Deserialize(const reflection::Schema* schema) {
if (schema->fbs_files())
for (auto s = schema->fbs_files()->begin(); s != schema->fbs_files()->end();
++s) {
if (!s->included_filenames()) continue;
for (auto f = s->included_filenames()->begin();
f != s->included_filenames()->end(); ++f) {
IncludedFile included_file;
Expand Down
Binary file added tests/segv_poc_9144.bin
Binary file not shown.
76 changes: 76 additions & 0 deletions tests/test_deserialize_segv_9144.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2026 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Regression test for GitHub issue #9144:
// SEGV in StructDef::Deserialize via IsStruct on a bfbs schema that
// passes VerifySchemaBuffer but has unresolved struct cross-references.

#include <cstdint>
#include <cstdio>
#include <vector>

#include "flatbuffers/idl.h"
#include "flatbuffers/reflection.h"
#include "flatbuffers/verifier.h"

// PoC binary from issue #9144 (base64-decoded, 134 bytes).
// This schema passes VerifySchemaBuffer but declares a BASE_TYPE_STRUCT
// field whose struct_def cross-reference is never resolved, causing a
// null-pointer dereference in IsStruct() -> StructDef::Deserialize().
static const uint8_t kPocData[] = {
0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x42, 0x46, 0x42, 0x53, 0x08, 0x00,
0x0c, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0c, 0x00,
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x14, 0x00, 0x08, 0x00,
0x0c, 0x00, 0x07, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00,
0x0e, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x18, 0x00,
0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x08, 0x00,
0x07, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x01, 0x00,
0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x53,
0x00, 0x00, 0x00,
};

static const size_t kPocSize = sizeof(kPocData);

int main() {
printf("=== Regression test for #9144 ===\n");

// Step 1: Verify the PoC passes VerifySchemaBuffer (as reported).
flatbuffers::Verifier verifier(kPocData, kPocSize);
bool verify_ok = reflection::VerifySchemaBuffer(verifier);
printf("VerifySchemaBuffer: %s\n", verify_ok ? "PASSED" : "FAILED");

if (!verify_ok) {
printf("PoC did not pass verifier — test inconclusive.\n");
return 0;
}

// Step 2: Deserialize should return false (not crash with SEGV).
// Before the fix, this would crash with a null-pointer dereference
// in IsStruct() at idl.h:533.
flatbuffers::Parser parser;
bool deserialize_ok = parser.Deserialize(kPocData, kPocSize);

printf("Parser::Deserialize: %s\n", deserialize_ok ? "OK" : "FAILED (expected)");

if (deserialize_ok) {
printf("UNEXPECTED: Deserialize succeeded on malformed schema.\n");
return 1;
}

printf("PASS: Deserialize correctly rejected malformed schema without crashing.\n");
return 0;
}
Loading