CWE-129: Improper Validation of Array Index
Overview
Improper validation of array index occurs when an application uses untrusted data as an array index without checking it against the array's actual bounds. In memory-unsafe languages this reads or writes arbitrary memory outside the array. In memory-safe languages it throws an unhandled exception, which is a denial-of-service condition, or corrupts application logic where the index is used to select a permission, record, or resource.
Relationship to Other CWEs
Improper Validation of Array Index is a Variant-level weakness - the specific case where the unvalidated value is used as an index rather than, say, a file path or SQL fragment.
- CWE-129 (this page) - the missing validation that lets an untrusted index reach a memory or collection access
- CWE-1285 (Improper Validation of Specified Index, Position, or Offset in Input) - MITRE's parent for this page. No page here
- CWE-20 (Improper Input Validation) - the ancestor above that, which MITRE's simplified view shows and which a scanner is more likely to name
- CWE-125 (Out-of-bounds Read), CWE-787 (Out-of-bounds Write) and CWE-823 (Use of Out-of-range Pointer Offset) - what an unvalidated index becomes in a memory-unsafe language once it reaches an actual memory access, depending on whether the resulting operation is a read, a write, or pointer arithmetic. This page is the missing validation that lets the index reach that operation in the first place
OWASP Classification
A05:2025 - Injection
Risk
High: Unvalidated array indices let attackers read adjacent memory (in C/C++, potentially leaking passwords or keys), overwrite unrelated data structures, or, even in memory-safe languages, select the wrong record, permission flag, or resource by supplying an index outside the intended range. Depending on what the index selects, this can enable privilege escalation or unauthorized data access, not just a crash.
Remediation Steps
Core Principle: Validate every index against the array's actual bounds - both the lower and upper limit - before it is used, regardless of how the index reached the code.
Trace the Data Path
- Source: Any index value influenced by user input, a file, a database, or a network request
- Sink: The array or buffer access that uses the index
- Missing Controls: No check that
index >= 0 && index < lengthbefore the access, or a calculated index that isn't re-validated after the calculation
Validate Both Bounds Before Use (Primary Defense)
- Check the lower bound and the upper bound separately:
index >= 0 && index < array.length- a single-sided check (only< length) still lets a negative index through - Reject invalid indices outright rather than silently clamping them to the nearest valid value, which hides a bug or an attack instead of surfacing it
- Validate as close to the point of use as possible, and re-validate after any calculation that transforms the index
Prefer Bounds-Checked Access (Defense in Depth)
A checked accessor throws or returns an error on an invalid index instead of performing the raw access. Where the language or library offers one, default to it, and use unchecked access only where the index is already provably in range.
Prevent Integer Overflow in Calculated Indices
- When an index is computed (
base + offset,index * element_size), validate the inputs and the result - the calculation itself can overflow and produce a small, in-range-looking value from out-of-range inputs - Write the check as a subtraction or division that cannot overflow:
length > max_size - offsetrather thanoffset + length > max_size
Test with Malicious Indices
- Negative values (
-1,-100) - should be rejected - Values at and beyond the upper boundary (
array_size,array_size + 1,MAX_INT) - should be rejected - Values that cause an intermediate calculation to overflow - should be rejected before the overflow, not after
- Valid boundary values (
0,array_size - 1) - should still work - Re-scan with the security scanner to confirm the finding is resolved
Common Vulnerable Patterns
// VULNERABLE - pseudo-code
value = array[user_supplied_index] // no check that index is within [0, length)
// Attack: user_supplied_index = -1 or far beyond the array's length
// Result: reads/writes outside the array - out-of-bounds access, wrong record selected, or crash
Why this is vulnerable: an index is an offset rather than a value, so getting it wrong does not produce an error - it produces an answer. The expression succeeds, returns something of the expected type, and the program carries on using it, which is why this survives in code that handles its exceptions carefully. In a memory-unsafe language the answer comes from whatever is adjacent in memory; in a memory-safe one the access either throws or, where the index merely selects the wrong element of a valid collection, silently returns another user's record.
The negative case is worth separating out because it fails differently. A negative index is not a large index - it addresses backwards from the start of the resource, so it reaches memory the allocation never included, and on a signed type it passes any check written only as index < length. Where the language treats a negative index as counting from the end, as Python and Ruby do, there is no error at all and the code returns a genuine element from the wrong end of the collection.
Secure Patterns
// SECURE - pseudo-code
if user_supplied_index < 0 or user_supplied_index >= length(array):
reject("index out of range")
value = array[user_supplied_index]
Why this works: Checking both the lower and upper bound before the access means only a value already known to be valid reaches the array. Rejecting (rather than clamping) an invalid index makes an out-of-range value visible as an error instead of silently substituting a different, unintended element.
Common Pitfalls
- Checking the upper bound but not the lower one:
if (index < array.length)alone still lets a negative index through, and reads or writes before the array starts. This holds wherever both sides of the comparison are signed - Java and C# (lengthis anint), and any C bound held in anintor written as a bare literal. The exception is a C bound of typesize_t, where the usual arithmetic conversions turn the negative into a huge unsigned value and the check rejects it; that is protection by accident of the type rather than by design, and it disappears as soon as the check is widened into a range check onoffset + length. - Validating the index before a calculation but not after: Checking
index >= 0 && index < lengthand then computingarray[index * element_size]orarray[base + index]without re-validating the result reopens the same gap - the multiplication or addition can overflow and produce an in-range-looking value from an out-of-range input. - Clamping instead of rejecting: Silently substituting the nearest valid index (
index = min(index, length - 1)) when validation fails avoids a crash but processes the wrong record or resource as if the request had been valid - the caller gets no indication anything was wrong, and an attacker probing boundary values gets no signal either. - Trusting a length or count field from the same untrusted source as the index: Validating an index against an upper bound that itself came from unvalidated input (rather than the array's actual allocated size) doesn't provide real protection - both values are attacker-controlled.