Skip to content

CWE-823: Out-of-range Pointer

Overview

Out-of-range Pointer occurs when pointer arithmetic or array indexing results in a pointer that references memory outside the intended buffer boundaries. This typically happens through unchecked pointer arithmetic, incorrect loop conditions, or unsafe buffer operations.

Risk

High to Critical: Using out-of-range pointers can cause memory corruption, crashes, information disclosure, or arbitrary code execution. Attackers may exploit this to read sensitive data, overwrite critical memory, or gain control of program execution.

Remediation Steps

Core principle: Never form pointers/addresses from unchecked values; validate ranges and use safe pointer abstractions.

Identify Out-of-Range Pointer Operations

When reviewing security scan results:

  • Check pointer arithmetic: Look for ptr + offset, ptr++, ptr-- operations
  • Review array indexing: Find array[index] without bounds checks
  • Check buffer operations: Identify strcpy, memcpy, strcat with pointers
  • Review loops: Find loops that increment pointers beyond bounds
  • Check function returns: Look for functions returning pointers to stack variables

Vulnerable patterns:

char buffer[100];
char *ptr = buffer;
ptr += user_input;  // No bounds check!
*ptr = 'X';  // May write outside buffer

// Loop overrun
for (int i = 0; i <= n; i++) {  // <= instead of <
    array[i] = value;  // Writes one past end!
}

Add Bounds Checking for Pointer Operations (Primary Defense)

// VULNERABLE - no bounds checking
void process_data(char *buffer, size_t size, int offset) {
    char *ptr = buffer + offset;  // No validation!
    *ptr = 'X';  // May write outside buffer
}

// SECURE - validate bounds
void process_data_safe(char *buffer, size_t size, int offset) {
    if (offset < 0 || offset >= size) {
        return ERROR_INVALID_OFFSET;
    }

    char *ptr = buffer + offset;
    *ptr = 'X';  // Safe - bounds checked
}

// Better - check before and after arithmetic
void write_at_offset(char *buffer, size_t size, int offset, char value) {
    // Check offset is valid
    if (offset < 0 || (size_t)offset >= size) {
        log_error("Invalid offset: %d, size: %zu", offset, size);
        return;
    }

    char *ptr = buffer + offset;

    // Additional check: verify pointer is within bounds
    if (ptr < buffer || ptr >= buffer + size) {
        log_error("Pointer out of range");
        return;
    }

    *ptr = value;
}

Array iteration with bounds checking:

// VULNERABLE
for (int i = 0; i <= count; i++) {  // Off-by-one!
    array[i] = value;
}

// SECURE
for (int i = 0; i < count; i++) {  // Correct bounds
    if (i >= MAX_SIZE) break;  // Additional safety check
    array[i] = value;
}

Use Safe String and Memory Functions

// VULNERABLE - no bounds checking
char dest[10];
char *src = user_input;
strcpy(dest, src);  // Buffer overflow!
strcat(dest, "suffix");  // More overflow!

// SECURE - bounded operations
char dest[10];
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';  // Ensure null termination

strncat(dest, "suffix", sizeof(dest) - strlen(dest) - 1);

// Better - use snprintf
snprintf(dest, sizeof(dest), "%s%s", src, "suffix");

// Best - use safer alternatives
#include <string.h>
strlcpy(dest, src, sizeof(dest));  // BSD
strlcat(dest, "suffix", sizeof(dest));

Memory copy with validation:

// VULNERABLE
memcpy(dest, src, user_size);  // No bounds check!

// SECURE
if (user_size > dest_size) {
    log_error("Copy size exceeds destination");
    return ERROR;
}

if (user_size == 0 || user_size > MAX_COPY_SIZE) {
    return ERROR;
}

memcpy(dest, src, user_size);

Use Modern C++ Smart Pointers and Containers

// VULNERABLE - manual pointer arithmetic
int* data = new int[size];
for (int i = 0; i < size; i++) {
    *(data + i) = value;  // Manual pointer arithmetic
}
delete[] data;

// SECURE - use std::vector with bounds checking
#include <vector>
std::vector<int> data(size);
for (size_t i = 0; i < data.size(); i++) {
    data.at(i) = value;  // Throws exception if out of range
}
// Automatically freed

// Even better - use iterators
for (auto& element : data) {
    element = value;
}

// Smart pointers for single objects
std::unique_ptr<Object> obj = std::make_unique<Object>();
obj->method();  // Safe, automatically deleted

// Arrays with bounds checking
std::array<int, 10> fixed_array;
fixed_array.at(5) = 42;  // Bounds checked

Enable Compiler Warnings and Runtime Checks

# Compile with warnings
gcc -Wall -Wextra -Wpointer-arith -Warray-bounds \

    -D_FORTIFY_SOURCE=2 -O2 \
    program.c -o program

# Enable AddressSanitizer during development
gcc -fsanitize=address -g program.c -o program
./program
# Detects out-of-bounds access, use-after-free, etc.

# Enable UndefinedBehaviorSanitizer
gcc -fsanitize=undefined -g program.c -o program

# Stack protector
gcc -fstack-protector-all program.c -o program

Static analysis:

# Use clang static analyzer
clang --analyze program.c

# Use cppcheck
cppcheck --enable=all program.c

# Use Coverity, PVS-Studio, or Veracode Static Analysis

Test with Fuzzing and Memory Sanitizers

// Test harness with bounds checking
void test_pointer_safety() {
    char buffer[100];

    // Test 1: Valid operations
    assert(write_at_offset(buffer, sizeof(buffer), 0, 'X') == SUCCESS);
    assert(write_at_offset(buffer, sizeof(buffer), 99, 'Y') == SUCCESS);

    // Test 2: Out-of-bounds (should fail gracefully)
    assert(write_at_offset(buffer, sizeof(buffer), 100, 'Z') == ERROR);
    assert(write_at_offset(buffer, sizeof(buffer), -1, 'A') == ERROR);

    // Test 3: Edge cases
    assert(write_at_offset(NULL, 0, 0, 'B') == ERROR);
    assert(write_at_offset(buffer, 0, 0, 'C') == ERROR);
}

Fuzzing with AFL or libFuzzer:

#ifdef FUZZING
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    if (size < 4) return 0;

    int offset = *(int*)data;
    char buffer[100];

    // Should not crash on any input
    write_at_offset(buffer, sizeof(buffer), offset, 'X');

    return 0;
}
#endif

Security Checklist

  • All pointer arithmetic has bounds checks
  • Array indices validated before access
  • Use at() instead of [] for std::vector in C++
  • Replaced strcpy/strcat with strncpy/strncat or snprintf
  • Compiled with AddressSanitizer during testing
  • Static analysis shows no pointer warnings
  • Fuzz testing passed without crashes
  • No pointer arithmetic on user-controlled offsets without validation

Additional Resources