Skip to content

CWE-823: Use of Out-of-range Pointer Offset

Overview

Use of Out-of-range Pointer Offset occurs when a program performs pointer arithmetic using an offset that can point outside the buffer the pointer was meant to reference. This typically comes from unchecked array indexing, off-by-one loop bounds, or an offset value taken from user input, file data, or a calculation without validating it against the buffer's actual size. It is a manual-memory-management issue: it does not occur in languages with automatic bounds checking on normal indexing (Java, C#, Python, safe Rust).

Relationship to Other CWEs

CWE-823 is a MITRE Base weakness under CWE-119 (Improper Restriction of Operations within the Bounds of a Memory Buffer), which in turn sits under CWE-118 - both of those are Class-level and marked DISCOURAGED for mapping, so a finding that fits this page should be recorded here rather than against either parent.

The offset itself is the intermediate cause, and the read or write the consequence:

Risk

High to Critical: Dereferencing an out-of-range pointer reads or writes memory outside the intended buffer. Depending on what lies adjacent, that means disclosure of nearby data, a crash, or - where the corrupted memory holds control data - arbitrary code execution.

Remediation Steps

Core Principle: Never form a pointer from an offset that hasn't been validated against the buffer's actual bounds; prefer bounds-checked abstractions over raw pointer arithmetic.

Trace the Data Path

  • Source: Any offset, index, or length used in pointer arithmetic that originates from user input, file data, network data, or a calculation
  • Sink: The pointer arithmetic itself (ptr + offset) or the dereference that follows it
  • Missing Controls: No check that the resulting pointer stays within [buffer, buffer + size) before it is formed or dereferenced

Validate the Offset Before Forming the Pointer (Primary Defense)

  • Check the offset against the buffer's size before doing the arithmetic, not after: 0 <= offset && offset < size
  • Keep every part of the check in integer arithmetic. A check written on the formed pointer, such as ptr + offset < buffer + size, cannot be relied on to survive compilation in C or C++ - see the vulnerable pattern below for why
  • Where the offset and a length are checked together, reject offset > size first and only then test len > size - offset, so the subtraction cannot wrap. The single-expression form offset + len > size can wrap and pass
  • Prefer bounds-checked containers, spans, or accessor functions that reject an out-of-range offset over hand-computed pointer arithmetic

Fix Loop and Index Boundaries (Defense in Depth)

  • Use strict less-than (<) against the element count in loop conditions, not less-than-or-equal (<=)
  • Use unsigned index types consistently, and check for wraparound before it happens, not after

Harden and Test (Defense in Depth)

  • Compile with bounds-checking and pointer-arithmetic warnings enabled, and treat them as build failures
  • Run memory sanitizers and fuzzers during development to catch offsets that slip past manual review

Test with Malicious Inputs

  • Offsets equal to, and one greater than, the buffer size (exact boundary)
  • Negative offsets where the code assumes a small positive number
  • Offsets crafted to overflow a size calculation before the bounds check runs
  • Re-scan with the security scanner to confirm the finding is resolved

Common Vulnerable Patterns

// VULNERABLE - pseudo-code
ptr = buffer + offset       // offset taken directly from input, never checked
write(ptr, value)
// Attack: offset is larger than the buffer's size
// Result: ptr points outside buffer; the write corrupts adjacent memory

Why this is vulnerable: the offset is applied and the result stored before anything examines it, so any bounds check is separated from the arithmetic by however much code sits between them. offset is also commonly a signed type, which lets the pointer move backwards; a check phrased as "is it past the end" leaves the entire space before the buffer open.

A second problem in C and C++ defeats the obvious fix. Merely forming a pointer outside [buffer, buffer + size] is undefined behaviour in those languages, before any dereference happens - so the natural guard, if (ptr < buffer + size), has already done the forbidden thing in order to test for it. A compiler is entitled to assume undefined behaviour does not occur and to delete the comparison as always true, and optimizing compilers do exactly that. The check that survives compilation is the one performed on the offset as integer arithmetic, against the size, before the pointer is computed at all.

Secure Patterns

// SECURE - pseudo-code
if offset < 0 or offset >= size(buffer):
    reject("offset out of range")
ptr = buffer + offset
write(ptr, value)

Why this works: the offset is tested against the buffer's real size before the arithmetic, so an out-of-range value never reaches the pointer computation. A bounds-checked container can do that test for you at the call site that forgets it - but only through the accessor that checks. std::vector::at() does; std::span::operator[] does not, and std::span has no .at() before C++26, so a span carries the size without enforcing it. Deleting a manual check because "the container handles it" is right for .at() and introduces the bug for span; the C++ page has the detail.

Language-Specific Guidance

  • C - offset validation as integer arithmetic, why a bounds check written on the formed pointer is deleted by the optimizer, ordering the offset and length tests so neither wraps, AddressSanitizer
  • C++ - std::span, std::vector::at(), avoiding raw pointer arithmetic

Additional Resources