From 29f6b0709ed7ce0545d0f846faf8825222ce965a Mon Sep 17 00:00:00 2001 From: steadytao Date: Fri, 3 Apr 2026 12:33:47 +1000 Subject: [PATCH 1/2] Avoid double-free on duplicate enum names during BFBS deserialization SymbolTable::Add() appended objects to its ownership vector before checking for duplicate names. During binary schema deserialization this allowed duplicate enum names to leave a freed pointer behind, which was later deleted again during teardown. Check for duplicates before storing the pointer and add a regression test that verifies a malformed BFBS fixture is accepted by VerifySchemaBuffer() but rejected safely by Parser::Deserialize(). Tested with flattests.exe; all tests passed. --- include/flatbuffers/idl.h | 2 +- tests/test.cpp | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h index 9e58d4be95d..c15145ffe41 100644 --- a/include/flatbuffers/idl.h +++ b/include/flatbuffers/idl.h @@ -245,9 +245,9 @@ class SymbolTable { } bool Add(const std::string& name, T* e) { - vec.emplace_back(e); auto it = dict.find(name); if (it != dict.end()) return true; + vec.emplace_back(e); dict[name] = e; return false; } diff --git a/tests/test.cpp b/tests/test.cpp index 5a43546f534..11befd59e6d 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -1203,6 +1203,34 @@ void EmbeddedSchemaAccess() { EmbeddedSchemaAccessByType(); } +void DuplicateEnumNameBinarySchemaTest() { + static const uint8_t duplicate_enum_name_schema[] = { + 0x10, 0x00, 0x00, 0x00, 0x42, 0x46, 0x42, 0x53, 0x08, 0x00, 0x0c, 0x00, + 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x10, 0x00, + 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x12, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0c, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x05, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06, 0x00, 0x08, 0x00, 0x07, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x44, 0x75, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x75, 0x6d, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x44, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x65, 0x45, 0x6e, 0x75, 0x6d, 0x00, 0x00, 0x00}; + + flatbuffers::Verifier verifier(duplicate_enum_name_schema, + sizeof(duplicate_enum_name_schema)); + TEST_EQ(reflection::VerifySchemaBuffer(verifier), true); + + flatbuffers::Parser parser; + TEST_EQ(parser.Deserialize(duplicate_enum_name_schema, + sizeof(duplicate_enum_name_schema)), + false); +} + void NestedVerifierTest() { // Create a nested monster. flatbuffers::FlatBufferBuilder nested_builder; @@ -1848,6 +1876,7 @@ int FlatBufferTests(const std::string& tests_data_path) { FlexBuffersFloatingPointTest(); FlatbuffersIteratorsTest(); WarningsAsErrorsTest(); + DuplicateEnumNameBinarySchemaTest(); NestedVerifierTest(); SizeVerifierTest(); PrivateAnnotationsLeaks(); From 767128b06aefba7e5ec5fb742ede000a147654fb Mon Sep 17 00:00:00 2001 From: steadytao Date: Sat, 4 Apr 2026 01:20:20 +1000 Subject: [PATCH 2/2] Free duplicate enums rejected during parsing Delete the temporary EnumDef in Parser::StartEnum() when duplicate-name registration fails so duplicate enum/union declarations do not leak during parsing. --- src/idl_parser.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/idl_parser.cpp b/src/idl_parser.cpp index b1bdffa0140..0026ffcba9e 100644 --- a/src/idl_parser.cpp +++ b/src/idl_parser.cpp @@ -3126,8 +3126,10 @@ CheckedError Parser::StartEnum(const std::string& name, bool is_union, enum_def.is_union = is_union; enum_def.defined_namespace = current_namespace_; const auto qualified_name = current_namespace_->GetFullyQualifiedName(name); - if (enums_.Add(qualified_name, &enum_def)) + if (enums_.Add(qualified_name, &enum_def)) { + delete &enum_def; return Error("enum already exists: " + qualified_name); + } enum_def.underlying_type.base_type = is_union ? BASE_TYPE_UTYPE : BASE_TYPE_INT; enum_def.underlying_type.enum_def = &enum_def;