Safeguard
Application Security•

CWE-129 Improper Validation of Array Index: Causes and Fixes

Untrusted values used as array indexes can read or write the wrong memory. See the pattern in C, what it enables, and how to validate indexes properly.

Safeguard Security Team
5 min read

CWE-129, Improper Validation of Array Index, applies when the product uses untrusted input to calculate or use an array index but does not validate it, or validates it incorrectly, so the index may point outside the array.

MITRE lists C, C++ and also "Not Language-Specific" for the platforms. Bounds-checked languages throw an exception instead of corrupting memory, but the logic bug remains: an attacker still picks which element your code touches.

How it happens

The input is a number that selects an element. It could be an ID in a packet, a command argument, a field in a file, or a count tracked alongside a maximum. The code uses it directly.

// Vulnerable: id comes straight from the packet
struct handler table[32];

void dispatch(const uint8_t *pkt) {
    int id = pkt[0];
    table[id].run(pkt + 1);    // id up to 255, table has 32 entries
}

MITRE's observed examples show the variety.

  • CVE-2005-0369: a large ID in a packet used as an array index.
  • CVE-2001-1009: a negative array index as an argument to the POP LIST command.
  • CVE-2003-0721: an integer signedness error leading to a negative array index.
  • CVE-2004-1189: the product does not properly track a count and a maximum number, which can lead to an array index overflow.

Two mistakes dominate. The first is checking only the upper bound. A signed index of minus one passes id < 32 and then reads or writes before the table. The second is checking the wrong thing, for example comparing against the byte size of the array instead of the element count.

MITRE also notes that a single fault can allow either an overflow of the index (CWE-788) or an underflow (CWE-786), and what happens next depends on the operation.

What an attacker gains

The consequences in the record are wide, because an index can lead to a read, a write, or an operation on the wrong object.

  • Crashes. An index outside the array likely corrupts relevant memory and perhaps instructions, leading to a crash if the values fall outside valid memory.
  • Data corruption. If the memory corrupted is data, the system keeps running with improper values.
  • Exposure or modification of information. An out-of-bounds index can trigger out-of-bounds reads or writes, or operations on the wrong objects. MITRE stresses that "buffer overflows" are not always the result.
  • Code execution. If the attacker can control the accessible memory, they may execute arbitrary code, possibly without large inputs if a precise index can be specified. The impact depends on the type of operation performed.

That last point is notable. An index bug needs no huge payload. One small, precise number can be enough to hit the target.

How to find it

MITRE lists these methods.

  1. Static analysis. Data flow and constraint-based tools can often find the weakness and keep false positives down, though they may not account for environmental details.
  2. Dynamic testing. Large test suites with diverse inputs, including fuzzing, stress testing and fault injection. The software may slow down, but it should find index problems.
  3. Compiler runtime checks. AddressSanitizer inserts checks for memory safety errors. Crafted inputs are still needed to reach the code with the error.
  4. Black box testing. Possible, but MITRE warns it may not achieve needed coverage in limited time, and a dynamic test might show no visible side effect even when it succeeds.

A direct check in code review: for every array[expr] where expr includes external data, find the line that bounds expr on both sides.

How to fix it

The record has no mitigations. Use these steps, which follow from the definition.

  1. Validate the index against both bounds, using the real element count.
  2. Use unsigned types for indexes, or check for negative values explicitly.
  3. Prefer lookups that fail safely, such as std::vector::at, std::array::at, or a function that returns an error code for an unknown ID.
  4. Keep counts and maximums consistent, and update them in one place.
  5. Use an allow list when the index represents a small set of known IDs.
// Fixed: bounded on both sides, error path is explicit
#define NHANDLERS (sizeof table / sizeof table[0])

int dispatch(const uint8_t *pkt, size_t len) {
    if (len < 1) return -1;
    size_t id = pkt[0];                 // unsigned, cannot be negative
    if (id >= NHANDLERS || table[id].run == NULL) return -1;
    table[id].run(pkt + 1);
    return 0;
}

Where this meets your dependency list

Index bugs often sit in protocol and file parsers inside libraries you did not write. If you know which components you ship, an advisory is easy to place. If the component is deep in your tree, knowing whether your code actually reaches the vulnerable function helps decide what to fix first. The free in-browser dependency scan is a quick way to start: no login, the file stays on your machine, up to 100 dependencies.

Quick check

Why is checking only index less than length not enough?

A signed index can be negative. A value like minus one passes the upper bound check and then points before the array. Check both bounds or use an unsigned type.

Is an array index bug always a buffer overflow?

No. MITRE notes it can also cause out-of-bounds reads or operations on the wrong objects, so the result may be data exposure or modification of unrelated state.

Does this only affect C and C++?

The memory corruption is a C and C++ concern, but the logic flaw of an attacker choosing the element exists in any language. MITRE lists the platforms as not language-specific.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.