diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h
index 9e58d4be95..b258562f94 100644
--- a/include/flatbuffers/idl.h
+++ b/include/flatbuffers/idl.h
@@ -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) {
diff --git a/src/idl_gen_text.cpp b/src/idl_gen_text.cpp
index 6908305535..514c675287 100644
--- a/src/idl_gen_text.cpp
+++ b/src/idl_gen_text.cpp
@@ -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
(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)
diff --git a/src/idl_parser.cpp b/src/idl_parser.cpp
index b1bdffa014..45209010bb 100644
--- a/src/idl_parser.cpp
+++ b/src/idl_parser.cpp
@@ -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(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(field_count);
+ std::vector 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;
@@ -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 =
@@ -4313,9 +4341,13 @@ Offset 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;
@@ -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;
diff --git a/tests/segv_poc_9144.bin b/tests/segv_poc_9144.bin
new file mode 100644
index 0000000000..2fe6a5aac3
Binary files /dev/null and b/tests/segv_poc_9144.bin differ
diff --git a/tests/test_deserialize_segv_9144.cpp b/tests/test_deserialize_segv_9144.cpp
new file mode 100644
index 0000000000..62c77c15f0
--- /dev/null
+++ b/tests/test_deserialize_segv_9144.cpp
@@ -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
+#include
+#include
+
+#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;
+}