Access of Memory Location After End of Buffer
Description
Access of Memory Location After End of Buffer is a memory safety vulnerability where software reads or writes to a memory location beyond the end of an allocated buffer. This typically occurs when a pointer or array index is incremented past the buffer's end, when pointer arithmetic produces an offset beyond the valid range, or when the size used for copying or accessing exceeds the buffer's actual size. This is one of the most common and dangerous vulnerability classes, encompassing classic buffer overflows.
Risk
Accessing memory beyond a buffer's end is extremely dangerous. Writing past the buffer end (buffer overflow) can overwrite adjacent data including function return addresses, function pointers, heap metadata, or security flags, potentially enabling arbitrary code execution. Reading past the buffer end can expose sensitive information from adjacent memory, including passwords, cryptographic keys, or memory layout information useful for bypassing ASLR. This vulnerability class has been responsible for countless critical security exploits across all types of software.
Solution
Always validate that buffer accesses stay within bounds. Use size-checking string functions (strncpy, snprintf) instead of unbounded versions (strcpy, sprintf). Validate all size parameters and array indices before use. Use compiler-provided buffer overflow protections (stack canaries, FORTIFY_SOURCE). Enable Address Space Layout Randomization (ASLR) and Data Execution Prevention (DEP). Use memory-safe languages or containers with automatic bounds checking. Employ static analysis tools and dynamic testing (AddressSanitizer, Valgrind) during development. For parsing protocols, validate length fields before copying data.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Confidentiality Read Memory - Out-of-bounds reads expose sensitive data from adjacent memory. |
| Integrity | Scope: Integrity Modify Memory - Buffer overflows corrupt adjacent data structures, function pointers, or return addresses. |
| Availability | Scope: Availability DoS: Crash - Memory corruption typically causes crashes. |
| Integrity | Scope: Integrity, Confidentiality, Availability Execute Unauthorized Code - Attackers can achieve arbitrary code execution through buffer overflow exploitation. |
Example Code
Vulnerable Code
// Vulnerable: Classic buffer overflow with strcpy
void vulnerable_host_lookup(char *user_supplied_addr) {
struct hostent *hp;
char hostname[64]; // Fixed-size buffer
// Assume validation only checks address format, not length
validate_addr_form(user_supplied_addr);
hp = gethostbyaddr(inet_addr(user_supplied_addr),
sizeof(struct in_addr), AF_INET);
// Vulnerable: No size check before copy
strcpy(hostname, hp->h_name); // Overflow if h_name > 64 chars
}
// Vulnerable: Reading past buffer with length mismatch
void vulnerable_process_data(char* data, int declared_length) {
char buffer[256];
// Vulnerable: Trust user-provided length without validation
for (int i = 0; i < declared_length; i++) {
buffer[i] = data[i]; // Overflow if declared_length > 256
}
}
// Vulnerable: Off-by-one overflow
char* vulnerable_copy_string(const char* src) {
size_t len = strlen(src);
char* dest = malloc(len); // Vulnerable: Forgot +1 for null terminator
strcpy(dest, src); // Writes len+1 bytes, overflow by 1
return dest;
}
// Vulnerable: Integer overflow leads to small buffer
void vulnerable_alloc(unsigned int count) {
// Vulnerable: Integer overflow
unsigned int size = count * sizeof(int); // Wraps if count is large
int* buffer = malloc(size); // Allocates small buffer
if (!buffer) return;
// Accesses past actual allocation
for (unsigned int i = 0; i < count; i++) {
buffer[i] = 0; // Writes past allocated size
}
}
// Vulnerable: Trusting network data length
typedef struct {
uint32_t length;
char data[];
} NetworkPacket;
void vulnerable_process_packet(char* raw_packet) {
NetworkPacket* packet = (NetworkPacket*)raw_packet;
char local_buffer[1024];
// Vulnerable: Trust length field from attacker-controlled data
memcpy(local_buffer, packet->data, packet->length); // Overflow!
}
// Vulnerable: Loop bound error
void vulnerable_init_array(int* array, int size) {
// Vulnerable: <= instead of <
for (int i = 0; i <= size; i++) {
array[i] = 0; // Writes one element past end
}
}
Fixed Code
// Fixed: Size-limited string copy
void fixed_host_lookup(char *user_supplied_addr) {
struct hostent *hp;
char hostname[64];
validate_addr_form(user_supplied_addr);
hp = gethostbyaddr(inet_addr(user_supplied_addr),
sizeof(struct in_addr), AF_INET);
if (hp == NULL) return;
// Fixed: Use strncpy with explicit size limit
strncpy(hostname, hp->h_name, sizeof(hostname) - 1);
hostname[sizeof(hostname) - 1] = '\0'; // Ensure null termination
}
// Fixed: Validate length before use
void fixed_process_data(char* data, int declared_length, int actual_data_size) {
char buffer[256];
// Fixed: Validate length against both buffer and actual data
if (declared_length <= 0 || declared_length > 256) {
return; // Invalid length
}
if (declared_length > actual_data_size) {
return; // Declared length exceeds available data
}
memcpy(buffer, data, declared_length);
}
// Fixed: Correct allocation size
char* fixed_copy_string(const char* src) {
size_t len = strlen(src);
char* dest = malloc(len + 1); // Fixed: +1 for null terminator
if (dest == NULL) return NULL;
strcpy(dest, src); // Now safe
return dest;
}
// Fixed: Check for integer overflow
#include <stdint.h>
void fixed_alloc(unsigned int count) {
// Fixed: Check for overflow before multiplication
if (count > SIZE_MAX / sizeof(int)) {
return; // Would overflow
}
size_t size = count * sizeof(int);
int* buffer = malloc(size);
if (!buffer) return;
for (unsigned int i = 0; i < count; i++) {
buffer[i] = 0;
}
free(buffer);
}
// Fixed: Validate network data length
#define MAX_PACKET_DATA 1024
typedef struct {
uint32_t length;
char data[];
} NetworkPacket;
int fixed_process_packet(char* raw_packet, size_t raw_packet_size) {
// Fixed: Validate packet structure
if (raw_packet_size < sizeof(NetworkPacket)) {
return -1; // Packet too small
}
NetworkPacket* packet = (NetworkPacket*)raw_packet;
// Fixed: Validate length field
uint32_t data_length = ntohl(packet->length); // Convert from network byte order
if (data_length > MAX_PACKET_DATA) {
return -1; // Length exceeds maximum
}
if (data_length > raw_packet_size - sizeof(NetworkPacket)) {
return -1; // Length exceeds available data
}
char local_buffer[MAX_PACKET_DATA];
memcpy(local_buffer, packet->data, data_length);
return 0;
}
// Fixed: Correct loop bounds
void fixed_init_array(int* array, int size) {
if (size <= 0) return;
// Fixed: < instead of <=
for (int i = 0; i < size; i++) {
array[i] = 0;
}
}
CVE Examples
- CVE-2009-2550: Stack-based buffer overflow in media player via long playlist entry.
- CVE-2009-2403: Heap-based buffer overflow in media player via long playlist entry.
- CVE-2009-0689: Large precision value in format string triggers buffer overflow.
- CVE-2009-0558: Attacker-controlled array index leads to code execution.
- CVE-2008-4113: OS kernel trusts userland length value, enabling sensitive data reading.
- CVE-2007-4268: Integer signedness error leads to heap overflow.
References
- MITRE Corporation. "CWE-788: Access of Memory Location After End of Buffer." https://cwe.mitre.org/data/definitions/788.html
- CERT C Coding Standard. "ARR30-C. Do not form or use out-of-bounds pointers or array subscripts."
- CWE. "CWE-787: Out-of-bounds Write." Related weakness.