Skip to content

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.

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 < length before 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 - offset rather than offset + 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# (length is an int), and any C bound held in an int or written as a bare literal. The exception is a C bound of type size_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 on offset + length.
  • Validating the index before a calculation but not after: Checking index >= 0 && index < length and then computing array[index * element_size] or array[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.

Additional Resources