VNX-839: Numeric Range Comparison Without Minimum Check
Detects a signed integer from untrusted input that is checked only against a maximum and then used as an array or list index, so a negative value passes. Python, Java, C and C++.
| Field | Value |
|---|---|
| Rule ID | VNX-839 |
| Severity | Medium |
| SARIF level | warning |
| Kind | sast |
| Languages | Python, Java, C, C++ |
| CWE | CWE-839 |
| 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 | numeric-range bounds-check negative-index cwe-839 |
| Source | internal/sast/rules/vnx-839.rego |
Overview
VNX-839 maps to CWE-839: Numeric Range Comparison Without Minimum Check. if (idx < size) is only half a bounds check when idx is signed. A negative number satisfies it, and then indexes from the end of a Python list, throws in Java, or reads and writes before the start of a C array.
The rule follows a value read from untrusted input (Python int() of request.*, sys.argv or input(); Java Integer.parseInt/valueOf or Long.parseLong of getParameter()/getHeader() or of a variable assigned from one just above, Scanner.nextInt(); C/C++ atoi/atol/strtol, std::stoi/stol, or sscanf with %d into a signed variable). It reports the comparison when the value is checked against an upper bound within six lines, then used as an index (or, in Java, passed to List.get) within eight lines of the check, with no lower-bound check in between.
What Gets Flagged
idx = int(request.args.get("i", "0"))
if idx < len(ITEMS): # FLAGGED: idx = -1 returns the last item
return ITEMS[idx]
int idx = atoi(arg);
if (idx >= 16) /* FLAGGED: idx = -5 writes before names[] */
return -1;
names[idx] = value;
What Is Not Flagged
- Range checks that also test the minimum:
0 <= i < n,i < 0 || i >= n,i >= 0 && i < n. - Values clamped with
max(0, ...),Math.max(0, ...)orabs(), or validated withisdigit(). unsigned/size_tvariables andstrtoul, constants, and values compared but never used as an index.
Remediation
Check both ends of the range, or parse into an unsigned type:
if not 0 <= idx < len(ITEMS):
abort(404)
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-839
// nosec vnx-839
Or record an ignore rule: vulnetix ignore add --rule vnx-839 --reason "...". See Suppressing findings.