Return of Pointer Value Outside of Expected Range
Description
Return of Pointer Value Outside of Expected Range is a vulnerability where a function can return a pointer to memory that is outside of the buffer that the pointer is expected to reference. When functions return pointers that reference memory locations beyond the intended buffer boundaries, subsequent operations using these pointers can read or write to unintended memory locations, causing information disclosure, data corruption, or code execution.
Risk
Returning out-of-range pointers creates severe memory safety vulnerabilities. Reading through such pointers can leak sensitive data from adjacent memory. Writing through them can corrupt critical data structures or overwrite code pointers to achieve arbitrary code execution. The unpredictable nature of which memory locations are affected makes debugging difficult and exploitation potentially reliable. In security-critical applications, these vulnerabilities can completely undermine memory isolation and access controls.
Solution
Implement rigorous bounds checking on all pointer arithmetic and calculations before returning pointers. Use static analysis tools (SAST) to analyze source code for vulnerable data flow patterns. Employ dynamic analysis tools like AddressSanitizer (ASan) to detect out-of-bounds memory access at runtime. Consider using safer abstractions like array slices or span types that carry length information. Validate all indices and offsets before computing pointer values. Return error codes or null rather than invalid pointers when bounds violations are detected.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Confidentiality Read Memory - Out-of-range pointers allow reading memory outside intended buffer boundaries, potentially leaking sensitive data. |
| Integrity | Scope: Integrity Modify Memory - Writing through out-of-range pointers can modify arbitrary memory locations, corrupting data or control structures. |
Example Code
Vulnerable Code
// Vulnerable: Pointer calculation can go out of bounds
#include <stdio.h>
#include <stdlib.h>
char* vulnerable_get_element(char* array, size_t array_size, int index) {
// Vulnerable: No bounds checking
// index could be negative or >= array_size
return &array[index];
// If index is -1, returns pointer before array
// If index is array_size, returns pointer after array
}
// Vulnerable: Integer overflow in pointer calculation
char* vulnerable_offset_pointer(char* base, size_t offset) {
// Vulnerable: offset * sizeof(element) could overflow
// Resulting in pointer wrapping around
return base + offset;
// Large offset could wrap pointer back into valid-looking
// but unintended memory
}
// Vulnerable: Returning pointer past end of buffer
typedef struct {
char data[256];
int length;
} Buffer;
char* vulnerable_find_end(Buffer* buf) {
// Vulnerable: Returns pointer past valid data
return buf->data + buf->length;
// If length > 256, returns pointer into adjacent memory
// Even if length == 256, pointer is one past end
}
// Vulnerable: Search function returning invalid pointer
char* vulnerable_find_char(char* str, size_t len, char target) {
for (size_t i = 0; i <= len; i++) { // Vulnerable: <= instead of <
if (str[i] == target) {
return &str[i];
}
}
// Vulnerable: Returns pointer past end on not-found
return &str[len]; // One past end, invalid for dereferencing
}
// Vulnerable: Array access with calculated index
int* vulnerable_matrix_element(int* matrix, int rows, int cols, int row, int col) {
// Vulnerable: No validation of row and col
int index = row * cols + col;
// If row or col is out of range, index is wrong
// If row * cols overflows, index wraps around
return &matrix[index];
}
// Vulnerable: String manipulation returning out-of-bounds
char* vulnerable_skip_prefix(char* str, const char* prefix) {
size_t prefix_len = strlen(prefix);
size_t str_len = strlen(str);
// Vulnerable: Doesn't verify prefix exists
// Vulnerable: Doesn't verify str is long enough
return str + prefix_len;
// If prefix_len > str_len, returns pointer past str
}
// Vulnerable: Iterator returning past-end pointer
class VulnerableContainer {
int* data;
size_t size;
public:
// Vulnerable: No bounds checking
int* at(size_t index) {
return &data[index]; // Could be past end
}
// Vulnerable: begin/end without proper validation
int* begin() { return data; }
int* end() { return data + size; } // One past end - careful!
// Vulnerable: Returns pointer that could be invalid
int* find(int value) {
for (size_t i = 0; i < size; i++) {
if (data[i] == value) {
return &data[i];
}
}
return end(); // Past-end, but commonly used pattern
// Dangerous if caller dereferences without checking
}
};
Fixed Code
// Fixed: Proper bounds checking
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
// Fixed: Return NULL for invalid indices
char* secure_get_element(char* array, size_t array_size, size_t index) {
// Fixed: Validate bounds
if (array == NULL || index >= array_size) {
return NULL;
}
return &array[index];
}
// Fixed: Check for overflow in pointer calculation
char* secure_offset_pointer(char* base, size_t base_size, size_t offset) {
// Fixed: Validate offset is within bounds
if (base == NULL || offset >= base_size) {
return NULL;
}
// Fixed: Check for pointer arithmetic overflow
uintptr_t base_addr = (uintptr_t)base;
if (offset > SIZE_MAX - base_addr) {
return NULL; // Would overflow
}
return base + offset;
}
// Fixed: Validate buffer length
typedef struct {
char data[256];
size_t length; // Changed to size_t
} SafeBuffer;
// Fixed: Return information about validity
typedef struct {
char* ptr;
int valid;
} PointerResult;
PointerResult secure_find_end(SafeBuffer* buf) {
PointerResult result = {NULL, 0};
if (buf == NULL) {
return result;
}
// Fixed: Validate length is within bounds
if (buf->length > sizeof(buf->data)) {
return result; // Invalid length
}
// Fixed: Return last valid character, not one-past-end
if (buf->length > 0) {
result.ptr = &buf->data[buf->length - 1];
result.valid = 1;
}
return result;
}
// Fixed: Search function with proper return value
// Returns index instead of pointer, -1 for not found
ssize_t secure_find_char(const char* str, size_t len, char target) {
if (str == NULL) {
return -1;
}
for (size_t i = 0; i < len; i++) { // Fixed: < instead of <=
if (str[i] == target) {
return (ssize_t)i;
}
}
return -1; // Fixed: Return sentinel value, not invalid pointer
}
// Fixed: Safe matrix access
typedef struct {
int* data;
int valid;
} ElementResult;
ElementResult secure_matrix_element(int* matrix, size_t rows, size_t cols,
size_t row, size_t col) {
ElementResult result = {NULL, 0};
// Fixed: Validate all bounds
if (matrix == NULL || row >= rows || col >= cols) {
return result;
}
// Fixed: Check for multiplication overflow
if (row > SIZE_MAX / cols) {
return result;
}
size_t index = row * cols + col;
// Fixed: Verify total index is valid
if (index >= rows * cols) {
return result;
}
result.data = &matrix[index];
result.valid = 1;
return result;
}
// Fixed: String manipulation with validation
const char* secure_skip_prefix(const char* str, const char* prefix) {
if (str == NULL || prefix == NULL) {
return NULL;
}
size_t prefix_len = strlen(prefix);
size_t str_len = strlen(str);
// Fixed: Verify string has the prefix
if (str_len < prefix_len) {
return NULL; // String shorter than prefix
}
if (strncmp(str, prefix, prefix_len) != 0) {
return NULL; // Prefix doesn't match
}
// Fixed: Safe to return offset pointer
return str + prefix_len;
}
// Fixed: Safe container with bounds checking
#include <optional>
#include <stdexcept>
class SafeContainer {
int* data;
size_t capacity;
size_t count;
public:
// Fixed: Returns optional to indicate validity
std::optional<int*> at(size_t index) {
if (index >= count) {
return std::nullopt;
}
return &data[index];
}
// Fixed: Throwing version for when exception is appropriate
int& at_checked(size_t index) {
if (index >= count) {
throw std::out_of_range("Index out of bounds");
}
return data[index];
}
// Fixed: Iterator interface is safe when used correctly
// but document the contract
class iterator {
int* ptr;
int* end;
public:
iterator(int* p, int* e) : ptr(p), end(e) {}
// Fixed: Check before dereference
int& operator*() {
if (ptr >= end) {
throw std::out_of_range("Dereferencing past-end iterator");
}
return *ptr;
}
bool valid() const { return ptr < end; }
};
// Fixed: find returns optional
std::optional<size_t> find(int value) {
for (size_t i = 0; i < count; i++) {
if (data[i] == value) {
return i; // Return index, not pointer
}
}
return std::nullopt;
}
};
CVE Examples
No specific CVEs are listed in the MITRE database for this CWE. However, the pattern is related to:
- Buffer over-read vulnerabilities (CWE-125)
- Out-of-bounds write vulnerabilities (CWE-787)
- Seven Pernicious Kingdoms: "Illegal Pointer Value"
References
- MITRE Corporation. "CWE-466: Return of Pointer Value Outside of Expected Range." https://cwe.mitre.org/data/definitions/466.html
- CERT C Secure Coding Standard. "ARR30-C. Do not form or use out-of-bounds pointers or array subscripts."
- Google. "AddressSanitizer: A Fast Address Sanity Checker."