T09 · Insecure Skill Coding Practices
- Location
scripts/protobuf_linter.py:169- Finding
Unbounded Reserved-Range Expansion Causes Memory Exhaustion
- Content
View full analysis
Vulnerability Details
File Location:
scripts/protobuf_linter.py, lines 169–175
Vulnerability Type: Uncontrolled resource consumption through unbounded range materialization
Risk Level: HighVulnerable Code
python elif 'to' in part: try: a, b = part.split('to') a, b = int(a.strip()), b.strip() if b == 'max': b = 536870911 reserved_nums.extend(range(a, int(b)+1)) except ValueError: passTechnical Analysis
The parser stores every reserved field number as an individual integer. For a range declaration, it constructs a
rangeand passes it tolist.extend(), which materializes every value in memory.Protocol Buffers permits declarations such as:
proto reserved 1 to max;The implementation translates
maxto536870911, then attempts to append as many as 536,870,911 Python integers toreserved_nums. This requires many gigabytes of memory because each integer and list reference carries runtime overhead. No range-size limit, resource budget, or interval-based representation prevents this expansion.The issue is reachable through normal processing of an attacker-controlled
.protofile. It does not require command execution or elevated privileges.Attack Path
-
An attacker creates or modifies a
.protofile containing a large reserved range, for example:proto syntax = "proto3"; package example; message Payload { reserved 1 to max; } -
The attacker causes the file to be submitted to the Skill, or places it in a directory that the user scans recursively.
-
The user or Agent invokes
lintorvalidate. -
parse_proto()detects the reserved range and convertsmaxto536870911. -
reserved_nums.extend(range(...))attempts to materialize hundreds of millions of integers. -
The linter consumes excessive memory and may be terminated by the operating system or destabilize the host.
Impact Assessment
Success ...[truncated 524 chars]
-
- Remediation
View remediation
Remediation Suggestions
Represent reserved ranges as intervals rather than expanding them into individual integers. For example, maintain separate collections for individual numbers and
(start, end)tuples, then test field numbers using interval comparisons.Recommended hardening steps:
- Replace
reserved_nums.extend(range(a, end + 1))with storage of(a, end). - Validate that the range start and end are legal protobuf field numbers and that
start <= end. - Reject malformed declarations rather than silently ignoring all
ValueErrorexceptions. - If expansion must be retained, enforce a strict maximum range size before allocation.
- Apply process memory and execution-time limits as defense in depth when scanning untrusted files.
- Add regression tests for
reserved 1 to max;, very large numeric ranges, reversed ranges, and malformed range declarations.
A safe approach would resemble:
python reserved_ranges = [] elif 'to' in part: try: start_text, end_text = part.split('to', 1) start = int(start_text.strip()) end_text = end_text.strip() end = 536870911 if end_text == 'max' else int(end_text) if start < 1 or end > 536870911 or start > end: raise ValueError("Invalid reserved field range") reserved_ranges.append((start, end)) except ValueError: # Report a parse or validation issue. passReserved-number conflict checks should then use bounded interval comparisons:
python if any(start <= f.number <= end for start, end in reserved_ranges): # Report the reserved-number conflict.- Replace
