CWE-125: Out-of-bounds Read
Overview
Out-of-bounds read happens when a program reads data past the end, or before the beginning, of the buffer or array it is reading from - returning whatever adjacent memory happens to hold instead of failing safely. It typically results from a missing bounds check, an off-by-one loop condition, or code that trusts a length or offset value from untrusted input without validating it against the buffer's real size. This is almost exclusively a manual-memory-management problem: languages with automatic bounds checking on normal indexing (Java, C#, Python, safe Rust) throw an exception instead of reading adjacent memory.
Relationship to Other CWEs
- CWE-125 (this page) - a read that reaches outside the bounds of the intended buffer
- CWE-787 (Out-of-bounds Write) - the write-side counterpart: the same missing-bounds-check root cause produces a write instead of a read
- CWE-119 (Improper Restriction of Operations within the Bounds of a Memory Buffer) - the Class both sit under as Base weaknesses, which MITRE marks Discouraged for mapping. Once the direction of the access is known, report the read or the write rather than the parent
- CWE-129 (Improper Validation of Array Index) - what this commonly follows, when an unvalidated index or offset is used directly to form the read
- CWE-823 (Use of Out-of-range Pointer Offset) - the other common predecessor, when the out-of-range pointer is dereferenced for a read rather than a write
CWE-125 is itself Allowed for mapping, so there is no need to reach for its variants - over-read CWE-126, under-read CWE-127 - unless the finding turns on which end of the buffer was passed.
Risk
High: Out-of-bounds reads can expose passwords, keys, and tokens from adjacent memory, crash the application, or leak stack and heap addresses that defeat ASLR and open the way to a follow-on exploit. Heartbleed (CVE-2014-0160) was an out-of-bounds read that disclosed server memory.
Remediation Steps
Core Principle: Never read past the bounds of the buffer that was actually allocated; validate both the lower and upper bound before every read that uses an untrusted index, offset, or length.
Trace the Data Path
- Source: Any index, offset, or length value influenced by user input, file data, network data, or an attacker-controlled calculation
- Sink: The raw memory read - an array index access, pointer dereference, or a copy function reading from a source with an attacker-influenced offset or length
- Missing Controls: No check that the index/offset stays within
[0, size), or a length taken from input without validating it against the buffer's actual size
Validate Every Read Against the Buffer's Real Size (Primary Defense)
- Check
index >= 0 && index < sizebefore every read - For a range read, confirm
offset <= buffer_sizefirst, then reject the read whenlength > buffer_size - offset. Do not writeoffset + length <= buffer_size: the addition happens before the comparison, so a large enoughoffsetwraps the sum back into range and the check passes - Never trust a length or offset field taken directly from user input, a file, or the network - validate it against the buffer's real allocated or received size, not just against the sender's claimed size
Prefer Bounds-Checked Abstractions (Defense in Depth)
Where a language or library offers both a checked and an unchecked way to read from a buffer, default to the checked one, and only drop to the unchecked form with an explicit, verified bounds check immediately before it.
Harden the Runtime as Defense in Depth
- Enable compiler protections and runtime bounds-checking modes during development and testing
- Run sanitizers and fuzzers in development and CI so out-of-bounds reads are caught before release
Test with Malicious Inputs
- Oversized length and offset values, including values crafted to overflow a size calculation
- Negative or very large index values where the code assumes a small positive number
- Exact boundary values (
size - 1,size,size + 1) - Re-scan with the security scanner to confirm the finding is resolved
Common Vulnerable Patterns
// VULNERABLE - pseudo-code
buffer = allocate(fixed_size)
value = buffer[index] // no check that index is within [0, fixed_size)
// Attack: index is taken from untrusted input and exceeds fixed_size, or is negative
// Result: the read returns data from outside buffer, disclosing adjacent memory
Why this is vulnerable: the read succeeds. Nothing faults, nothing throws, and there is no return code to check - value receives whatever bytes sit at that address and the program carries on using them as if they were its own. That silence is why the weakness survives review and testing: the wrong answer looks exactly like the right one, and only the contents of the adjacent memory on that run separate them.
index in the sketch stands for three values that a scanner may report separately and that fail identically here: an array index, an offset added to a base pointer, and a length or count driving a copy or a loop. The third is the one most often missed, because that code usually does have a bounds check - it just validates the length against the size the sender claimed rather than against the bytes actually received. Whichever form the finding takes, the value has to be checked against the buffer's real allocated size, and against both ends of it.
Secure Patterns
// SECURE - pseudo-code
buffer = allocate(fixed_size)
if index < 0 or index >= fixed_size:
reject("index out of range")
value = buffer[index]
Why this works: The index is checked against the buffer's real size, in both directions, before the read happens, so an out-of-range value is rejected instead of silently returning data from outside the buffer. A bounds-checked container or accessor that does this automatically is better than a hand-written check because no call site can forget it.
Common Pitfalls
- Checking only the upper bound: Adding
if index < sizestops reads past the end, but whether it stops a negative index depends on the types involved - where the index and the bound are both signed (anintlength, or a bare numeric literal), the negative passes that single check and reads before the buffer starts. Write both bounds explicitly rather than depending on a conversion rule to cover one of them; the C page has the specific case. - Validating a claimed length against another claimed value: Checking a length field from a packet or file against a second field from the same untrusted source, rather than the buffer's actual allocated or received size, doesn't verify anything - both numbers are attacker-controlled.
- Adding the bounds check after the read, or in a different function than the one performing it: A check that runs after the memory access, or that validates the index in a caller while a callee performs the actual indexed read without re-checking, doesn't prevent the out-of-bounds access - it only detects it after the fact, if at all.
- Trusting
offset + length <= buffer_sizeas written: This form performs the addition before the check runs, so a large enoughoffsetcan wrap the sum around and pass the check while the actual read is nowhere near the buffer. Confirmingoffset <= buffer_sizeand then rearranging tolength > buffer_size - offsetavoids the overflow instead of just moving it.
Language-Specific Guidance
- C - explicit bounds validation,
memcpy/length-field handling, AddressSanitizer and compiler hardening flags - C++ -
.at(),std::span, avoiding raw pointer arithmetic and uncheckedoperator[]