Use of Out-of-range Pointer Offset

Description

Use of Out-of-range Pointer Offset is a memory safety vulnerability where software performs pointer arithmetic using an offset that can point beyond the intended range of valid memory locations for the resulting pointer. While pointers can technically reference any memory address, programs typically intend to access only specific memory regions such as contiguous array elements or fields within a structure. When offsets originate from untrusted sources, result from incorrect calculations, or stem from other programming errors, the computed pointer may reference memory outside the intended boundaries, leading to out-of-bounds reads or writes.

Risk

This vulnerability enables attackers to read or write to unintended memory locations. Out-of-bounds reads can expose sensitive data such as cryptographic keys, passwords, or security tokens stored adjacent to the target buffer. Out-of-bounds writes can corrupt critical data structures, overwrite function pointers, or modify return addresses to achieve arbitrary code execution. Even without exploitation, accessing invalid memory causes crashes resulting in denial of service. The vulnerability is particularly dangerous when the offset value comes from external input, allowing attackers direct control over which memory location is accessed.

Solution

Validate all offset values before using them in pointer arithmetic. Ensure offsets are non-negative and within the bounds of the target data structure. For array access, verify the index is less than the array size. Use safe APIs that perform bounds checking automatically. In C/C++, prefer container classes with bounds checking or use sanitizers during development. Implement defense-in-depth with ASLR, stack canaries, and DEP. When processing structured data from untrusted sources, validate that all size and offset fields are consistent with the total data size. Consider using static analysis tools to detect potential out-of-range pointer arithmetic at compile time.

Common Consequences

ImpactDetails
ConfidentialityScope: Confidentiality

Read Memory - Attackers may read sensitive portions of memory through out-of-bounds pointer reads.
AvailabilityScope: Availability

DoS: Crash, Exit, or Restart - Accessing invalid memory locations causes unexpected application termination.
Integrity, Confidentiality, AvailabilityScope: Integrity, Confidentiality, Availability

Execute Unauthorized Code or Commands - Out-of-bounds writes can modify function pointers or return addresses, enabling code execution.

Example Code

Vulnerable Code

// Vulnerable: Offset from untrusted source without validation
void vulnerable_process_data(char *buffer, size_t buffer_size) {
    int offset;

    // Offset read from buffer itself
    memcpy(&offset, buffer, sizeof(offset));

    // Vulnerable: No validation of offset
    char *data_ptr = buffer + offset;

    // Could point outside buffer if offset is too large or negative
    process_string(data_ptr);
}
// Vulnerable: Array index used for pointer arithmetic
struct item {
    char name[32];
    int value;
};

void vulnerable_get_item(struct item *items, int count, int socket) {
    int32_t requested_index;

    recv(socket, &requested_index, sizeof(requested_index), 0);

    // Vulnerable: No bounds check
    struct item *selected = items + requested_index;

    // If requested_index < 0 or >= count, out-of-bounds access
    printf("Name: %s, Value: %d\n", selected->name, selected->value);
}
// Vulnerable: Structure field offset without validation
struct packet {
    uint16_t header_length;
    uint16_t data_offset;
    char data[1024];
};

void vulnerable_parse_packet(struct packet *pkt) {
    // Vulnerable: data_offset not validated against structure size
    char *payload = ((char *)pkt) + pkt->data_offset;

    // Could read memory beyond packet structure
    printf("Payload: %s\n", payload);
}
// Vulnerable: Integer overflow in offset calculation
void vulnerable_multi_array(int rows, int cols, int row, int col) {
    int *matrix = malloc(rows * cols * sizeof(int));

    // Vulnerable: Multiplication can overflow, creating wrong offset
    int *element = matrix + (row * cols + col);

    *element = 42;  // Writes to wrong location if overflow occurred
}
// Vulnerable: Negative offset not checked
void vulnerable_history_buffer(char *buffer, int size, int current_pos) {
    int lookback;

    scanf("%d", &lookback);  // User provides lookback value

    // Vulnerable: Negative offset might go before buffer start
    char *historical = buffer + current_pos - lookback;

    // If lookback > current_pos, accesses before buffer
    printf("Historical value: %c\n", *historical);
}
// Vulnerable: Iterator arithmetic without bounds
void vulnerable_iterator(std::vector<int>& vec, int skip) {
    auto it = vec.begin();

    // Vulnerable: No check if skip exceeds vector size
    it += skip;

    // Dereferencing past-the-end iterator is undefined behavior
    std::cout << *it << std::endl;
}

Fixed Code

// Fixed: Validate offset against buffer bounds
int fixed_process_data(char *buffer, size_t buffer_size) {
    int offset;

    if (buffer_size < sizeof(offset)) {
        return -1;  // Buffer too small for offset field
    }

    memcpy(&offset, buffer, sizeof(offset));

    // Fixed: Validate offset is within buffer
    if (offset < 0 || (size_t)offset >= buffer_size) {
        return -1;  // Invalid offset
    }

    char *data_ptr = buffer + offset;

    // Additional check: ensure we can safely read from data_ptr
    size_t remaining = buffer_size - offset;
    process_string_bounded(data_ptr, remaining);

    return 0;
}
// Fixed: Bounds-checked array access
struct item {
    char name[32];
    int value;
};

int fixed_get_item(struct item *items, size_t count, int socket) {
    int32_t requested_index;

    recv(socket, &requested_index, sizeof(requested_index), 0);

    // Fixed: Validate index bounds
    if (requested_index < 0 || (size_t)requested_index >= count) {
        return -1;  // Invalid index
    }

    struct item *selected = items + requested_index;
    printf("Name: %s, Value: %d\n", selected->name, selected->value);
    return 0;
}
// Fixed: Validate structure offsets
struct packet {
    uint16_t header_length;
    uint16_t data_offset;
    char data[1024];
};

int fixed_parse_packet(struct packet *pkt, size_t total_size) {
    // Fixed: Validate data_offset against actual structure size
    if (pkt->data_offset < sizeof(pkt->header_length) + sizeof(pkt->data_offset)) {
        return -1;  // Offset points into header
    }

    if (pkt->data_offset >= total_size) {
        return -1;  // Offset beyond packet
    }

    char *payload = ((char *)pkt) + pkt->data_offset;
    size_t remaining = total_size - pkt->data_offset;

    printf("Payload (%zu bytes available)\n", remaining);
    return 0;
}
// Fixed: Check for overflow in offset calculation
#include <stdint.h>

int fixed_multi_array(size_t rows, size_t cols, size_t row, size_t col) {
    // Fixed: Check bounds first
    if (row >= rows || col >= cols) {
        return -1;  // Out of bounds
    }

    // Check for multiplication overflow
    if (rows > SIZE_MAX / cols || rows * cols > SIZE_MAX / sizeof(int)) {
        return -1;  // Would overflow
    }

    int *matrix = malloc(rows * cols * sizeof(int));
    if (!matrix) return -1;

    // Safe: bounds already validated
    int *element = matrix + (row * cols + col);
    *element = 42;

    free(matrix);
    return 0;
}
// Fixed: Validate lookback doesn't go negative
int fixed_history_buffer(char *buffer, size_t size, size_t current_pos) {
    int lookback;

    if (scanf("%d", &lookback) != 1) {
        return -1;  // Invalid input
    }

    // Fixed: Ensure lookback is valid
    if (lookback < 0 || (size_t)lookback > current_pos) {
        return -1;  // Would go before buffer start
    }

    if (current_pos >= size) {
        return -1;  // current_pos itself invalid
    }

    char *historical = buffer + current_pos - lookback;
    printf("Historical value: %c\n", *historical);
    return 0;
}
// Fixed: Bounds-checked iterator arithmetic
#include <vector>
#include <iostream>

bool fixed_iterator(std::vector<int>& vec, size_t skip) {
    // Fixed: Validate skip against vector size
    if (skip >= vec.size()) {
        return false;  // Would go past end
    }

    auto it = vec.begin();
    std::advance(it, skip);  // or: it += skip;

    // Safe: we validated skip is within bounds
    std::cout << *it << std::endl;
    return true;
}

// Better: Use at() for bounds-checked access
void better_access(std::vector<int>& vec, size_t index) {
    try {
        int value = vec.at(index);  // Throws if out of bounds
        std::cout << value << std::endl;
    } catch (const std::out_of_range&) {
        std::cerr << "Index out of range" << std::endl;
    }
}
// Safe pattern: Using safe accessor functions
#include <stdlib.h>
#include <string.h>

typedef struct {
    int *data;
    size_t size;
} safe_array;

int safe_array_get(safe_array *arr, size_t index, int *out) {
    if (index >= arr->size) {
        return -1;  // Out of bounds
    }
    *out = arr->data[index];
    return 0;
}

int safe_array_set(safe_array *arr, size_t index, int value) {
    if (index >= arr->size) {
        return -1;  // Out of bounds
    }
    arr->data[index] = value;
    return 0;
}

  • CWE-119: Improper Restriction of Operations within Bounds of Memory Buffer (parent)
  • CWE-125: Out-of-bounds Read (can follow)
  • CWE-787: Out-of-bounds Write (can follow)
  • CWE-129: Improper Validation of Array Index (can precede)
  • CWE-822: Untrusted Pointer Dereference (related)
  • CWE-190: Integer Overflow or Wraparound (can precede - offset calculation)

References

  1. MITRE Corporation. "CWE-823: Use of Out-of-range Pointer Offset." https://cwe.mitre.org/data/definitions/823.html
  2. CERT C Secure Coding Standard. "ARR30-C. Do not form or use out-of-bounds pointers or array subscripts."
  3. Google. "AddressSanitizer." https://github.com/google/sanitizers/wiki/AddressSanitizer