VNX-674: Unconditional self-recursion or a disabled recursion guard
Reports accessors and methods that call themselves unconditionally with the same arguments (Python, JavaScript/TypeScript, Java), and Python sys.setrecursionlimit() raised to 50000 or more.
| Field | Value |
|---|---|
| Rule ID | VNX-674 |
| Severity | Medium |
| SARIF level | warning |
| Kind | sast |
| Languages | Java, JavaScript / TypeScript, Python |
| CWE | CWE-674 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:M/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C |
| Tags | uncontrolled-recursion cwe-674 |
| Source | internal/sast/rules/vnx-674.rego |
Overview
VNX-674 maps to CWE-674: Uncontrolled Recursion. It reports two concrete forms of recursion that nothing bounds:
- A function whose first statement calls itself with the same arguments. Every call recurses until the stack overflows. The common cause is an accessor that forgot its backing field:
- Python: an
@propertyreturningself.<same name>, an@<name>.setterassigningself.<same name>, a method returningself.<same name>(<same parameters>). - JavaScript/TypeScript: a getter returning
this.<same name>, a setter assigningthis.<same name>, a method returningthis.<same name>(<same parameters>). - Java: a method whose first statement is
<same name>(<same parameters>)orthis.<same name>(...).
- Python: an
- A disabled recursion guard.
sys.setrecursionlimit()raised to 50000 or more (or10**5+,2**16+,sys.maxsize). Deeply nested attacker input (JSON, GraphQL queries, XML) then overflows the C stack and kills the process instead of raisingRecursionError.
What Gets Flagged
@property
def name(self):
return self.name # FLAGGED: should be self._name
sys.setrecursionlimit(100000) # FLAGGED
set name(value) {
this.name = value; // FLAGGED: calls the setter again
}
public String getId() {
return getId(); // FLAGGED: should be this.id
}
public String getName() { return getName(); } // FLAGGED
What Is Not Flagged
- Accessors backed by another field (
self._name,this._name,this.id). - Delegation to another receiver or to
super, overloads called with different or converted arguments (log(msg, null),count((int) n)). - Recursion with a base case or a depth check, smaller recursion limits, comments and string literals.
General recursion over untrusted nested data without a depth limit cannot be decided line by line and is not reported.
Remediation
Use the backing field in accessors. Keep the default recursion limit and bound depth explicitly when walking untrusted structures:
def walk(node, depth=0):
if depth > MAX_DEPTH:
raise ValueError("input nested too deeply")
for child in node.children:
walk(child, depth + 1)
References
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-674
// nosec vnx-674
Or record an ignore rule: vulnetix ignore add --rule vnx-674 --reason "...". See Suppressing findings.