flatbuffers::Parser::Deserialize() performs BFBS identifier probing before running schema verification. Could cause an out-of-bounds read
- Dominant language
- C++
- Stars
- 26.5k
- Forks
- 3.7k
- PR merge metrics
- No merged PRs in 30d
Description
### Details
Vulnerability Details
- Project: `flatbuffers`
- Reproduced on local checkout: `e223d69b`
Location
- Public declaration:
- `include/flatbuffers/idl.h:1079`
- Vulnerable path:
- `src/idl_parser.cpp:4415`
- `include/flatbuffers/reflection_generated.h:1501`
- `include/flatbuffers/buffer.h:192`
Vulnerability Details
- Location:
- `src/idl_parser.cpp:4415`
- `include/flatbuffers/reflection_generated.h:1501`
- `include/flatbuffers/buffer.h:192`
- Root cause:
- `Parser::Deserialize()` checks `SchemaBufferHasIdentifier(buf)` and the size-prefixed fallback before running the verifier. These identifier helpers perform `strncmp()` on `buf + 4` / `buf + 8` without first confirming the input is large enough.
- Reachability:
- Public API: `flatbuffers::Parser::Deserialize(const uint8_t*, size_t)`
- Real CLI reachability exists through `flatc --cpp evil.bfbs`, but the wrapper path was non-triggering in the tested setup.
Root Cause
`Parser::Deserialize()` checks whether the input looks like a BFBS schema by calling identifier helpers before it runs normal verification. Those helpers eventually call `BufferHasIdentifier()`, which uses the buffer as though bytes at offsets `+4` or `+8` exist. When the caller passes a very short buffer, this happens before any size-based rejection, so the API performs an out-of-bounds read.
The problem is therefore not in the verifier itself; it is in the pre-verification format probe order.
Because the bug is reproducible through the public API without modifying exports, hooks, or visibility, I believe this should still be treated as a real library vulnerability.
Proof of Concept
## Reproduction Environment
- OS: X64 Debian Trixie container
## PoC Files
- [target5_deserialize_size_poc.cpp](https://github.com/user-attachments/files/26485861/target5_deserialize_size_poc.cpp)
## PoC Build
```bash
/usr/bin/clang++ -DFLATBUFFERS_LOCALE_INDEPENDENT=1 -I/work/flatbuffers/include -I/work/flatbuffers/grpc -g -std=c++17 -fPIE -fno-omit-frame-pointer -fsanitize=address,undefined -o /tmp/target5_deserialize_size_poc /tmp/target5_deserialize_size_poc.cpp /work/flatbuffers/build-asan/CMakeFiles/flatc.dir/src/idl_parser.cpp.o /work/flatbuffers/build-asan/CMakeFiles/flatc.dir/src/util.cpp.o
```
ASAN Result
```shell
root@ac391c057a41:/work/flatbuffers# /tmp/target5_deserialize_size_poc
=================================================================
==536==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x502000000014 at pc 0x55ff3964905b bp 0x7fff7ec04880 sp 0x7fff7ec04028
READ of size 1 at 0x502000000014 thread T0
==536==WARNING: invalid path to external symbolizer!
==536==WARNING: Failed to use and restart external symbolizer!
#0 0x55ff3964905a (/tmp/target5_deserialize_size_poc+0x16005a) (BuildId: ede4dc7f326bbd56ddc7b63b82a6ca27ed572b14)
#1 0x55ff3981d1ba (/tmp/target5_deserialize_size_poc+0x3341ba) (BuildId: ede4dc7f326bbd56ddc7b63b82a6ca27ed572b14)
#2 0x55ff3981d10e (/tmp/target5_deserialize_size_poc+0x33410e) (BuildId: ede4dc7f326bbd56ddc7b63b82a6ca27ed572b14)
#3 0x55ff397dc5f0 (/tmp/target5_deserialize_size_poc+0x2f35f0) (BuildId: ede4dc7f326bbd56ddc7b63b82a6ca27ed572b14)
#4 0x55ff3971390d (/tmp/target5_deserialize_size_poc+0x22a90d) (BuildId: ede4dc7f326bbd56ddc7b63b82a6ca27ed572b14)
#5 0x7f55180f9ca7 (/lib/x86_64-linux-gnu/libc.so.6+0x29ca7) (BuildId: 58749c528985eab03e6700ebc1469fa50aa41219)
#6 0x7f55180f9d64 (/lib/x86_64-linux-gnu/libc.so.6+0x29d64) (BuildId: 58749c528985eab03e6700ebc1469fa50aa41219)
#7 0x55ff3962fba0 (/tmp/target5_deserialize_size_poc+0x146ba0) (BuildId: ede4dc7f326bbd56ddc7b63b82a6ca27ed572b14)
0x502000000014 is located 3 bytes after 1-byte region [0x502000000010,0x502000000011)
allocated by thread T0 here:
#0 0x55ff396cf9d3 (/tmp/target5_deserialize_size_poc+0x1e69d3) (BuildId: ede4dc7f326bbd56ddc7b63b82a6ca27ed572b14)
#1 0x55ff39713838 (/tmp/target5_deserialize_size_poc+0x22a838) (BuildId: ede4dc7f326bbd56ddc7b63b82a6ca27ed572b14)
#2 0x7f55180f9ca7 (/lib/x86_64-linux-gnu/libc.so.6+0x29ca7) (BuildId: 58749c528985eab03e6700ebc1469fa50aa41219)
SUMMARY: AddressSanitizer: heap-buffer-overflow (/tmp/target5_deserialize_size_poc+0x16005a) (BuildId: ede4dc7f326bbd56ddc7b63b82a6ca27ed572b14)
Shadow bytes around the buggy address:
0x501ffffffd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x501ffffffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x501ffffffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x501fffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x501fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x502000000000: fa fa[01]fa fa fa 00 fa fa fa fa fa fa fa fa fa
0x502000000080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x502000000100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x502000000180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x502000000200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x502000000280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
```
### Attack scenario
Reachability
There are two relevant paths:
1. Public API path — reproducible
- `flatbuffers::Parser parser;`
- `parser.Deserialize(buf, size);`
- This path is sufficient to reproduce the out-of-bounds read with exact-size caller-owned buffers.
2. `flatc` CLI path — reachable but non-triggering in my environment
Confirmed impact
- Out-of-bounds read via a public library API
Conservative security impact
- Denial of service / crash
Potential impact still unproven
- Information disclosure is plausible for an out-of-bounds read, but I have not demonstrated a controlled disclosure primitive
- I have not demonstrated code execution
Why the CLI may not crash
In my testing, the direct API PoC reliably triggers ASan, but `flatc --cpp evil.bfbs` does not. This appears to be because `flatc` loads the file through a wrapper that stores contents in `std::string`, and the resulting backing allocation masks the short overread for the tested sizes. That does not remove the underlying API bug.
Contributor guide
Assessment
This issue has not been assessed yet.