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; +}