Free of Pointer not at Start of Buffer
Description
Free of Pointer not at Start of Buffer is a memory management vulnerability that occurs when software calls free() on a pointer to heap-allocated memory, but the pointer does not reference the start of the originally allocated buffer. This typically happens after pointer arithmetic moves the pointer to an interior location or past the end of the buffer. The heap memory allocator expects to receive the exact pointer that was returned by malloc(), calloc(), or realloc(). Passing a different address causes heap corruption, potentially leading to crashes, memory corruption, or exploitable conditions.
Risk
Freeing a pointer not at the buffer start corrupts the heap's internal data structures. This can cause immediate crashes, but more dangerously, it can lead to exploitable conditions. Attackers may be able to manipulate heap metadata to achieve arbitrary write capabilities, potentially leading to code execution. The vulnerability is particularly dangerous because it may not crash immediately—the heap corruption might only manifest later, making debugging difficult and creating time-of-check to time-of-use windows. In some cases, attackers can carefully craft conditions to exploit corrupted heap metadata for privilege escalation.
Solution
Always preserve the original pointer returned by allocation functions. Use a separate variable or index for traversing the buffer while keeping the original pointer for deallocation. In C++, use smart pointers (unique_ptr, shared_ptr) that automatically manage memory. When using functions like strtok() that modify pointers, remember that the returned tokens point into the original buffer—don't free them individually. Consider using container classes or memory-safe languages where possible. Use runtime tools like AddressSanitizer or Valgrind during development to detect these issues.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Integrity Modify Memory - Freeing wrong address corrupts heap metadata, potentially allowing memory modification. |
| Availability | Scope: Availability DoS: Crash, Exit, or Restart - Heap corruption typically causes crashes. |
| Confidentiality | Scope: Confidentiality, Integrity, Availability Execute Unauthorized Code or Commands - In some cases, heap corruption can be exploited for code execution. |
Example Code
Vulnerable Code
// Vulnerable: Freeing moved pointer after string search
#include <stdlib.h>
#include <string.h>
char searchChar(char c) {
char* str = malloc(20 * sizeof(char));
if (str == NULL) return -1;
strcpy(str, "Search Me!");
// Walk through string with pointer arithmetic
while (*str != '\0') {
if (*str == c) {
// Vulnerable: str has been incremented!
free(str); // Freeing interior pointer
return 1; // Found
}
str = str + 1; // Pointer moves away from original allocation
}
// Vulnerable: str now points past original allocation
free(str); // Freeing wrong address
return 0; // Not found
}
// Vulnerable: Freeing strtok result
void vulnerable_tokenize(char* input_copy) {
char* input = malloc(strlen(input_copy) + 1);
strcpy(input, input_copy);
char* tok = strtok(input, " \t");
while (tok != NULL) {
if (is_malformed(tok)) {
// Vulnerable: tok points INSIDE input buffer
free(tok); // Heap corruption!
}
tok = strtok(NULL, " \t");
}
// Original 'input' pointer is lost if we tried to free tokens
}
// Vulnerable: Arithmetic before free
void vulnerable_arithmetic() {
int* array = malloc(10 * sizeof(int));
// Fill array
for (int i = 0; i < 10; i++) {
array[i] = i;
}
// Process with pointer arithmetic
int* ptr = array;
ptr += 5; // Now points to middle of array
// ... later, programmer forgets ptr was moved
free(ptr); // Vulnerable: Freeing middle of buffer
}
// Vulnerable: Incrementing pointer in loop then freeing
char* vulnerable_skip_whitespace(char* original) {
char* str = malloc(100);
strcpy(str, original);
// Skip leading whitespace
while (*str == ' ' || *str == '\t') {
str++; // Pointer moves forward
}
// ... use str ...
process(str);
// Vulnerable: str no longer points to allocation start
free(str); // Heap corruption
return NULL;
}
// Vulnerable: Function returns interior pointer
char* vulnerable_find_substring(char* haystack, char* needle) {
char* buffer = malloc(strlen(haystack) + 1);
strcpy(buffer, haystack);
char* found = strstr(buffer, needle);
// Vulnerable: Returning pointer into buffer
// Caller might try to free this pointer
return found; // Points inside buffer, not at start
}
void use_vulnerable() {
char* result = vulnerable_find_substring("Hello World", "World");
if (result) {
printf("Found: %s\n", result);
free(result); // CRASH: Freeing interior pointer
}
}
Fixed Code
// Fixed: Use index instead of pointer arithmetic
#include <stdlib.h>
#include <string.h>
char searchChar_fixed(char c) {
char* str = malloc(20 * sizeof(char));
if (str == NULL) return -1;
strcpy(str, "Search Me!");
// Fixed: Use index, preserve original pointer
int i = 0;
while (i < strlen(str)) {
if (str[i] == c) {
free(str); // Fixed: Original pointer unchanged
return 1;
}
i = i + 1; // Index increments, not pointer
}
free(str); // Fixed: Still the original pointer
return 0;
}
// Fixed: Copy tokens instead of freeing them
void fixed_tokenize(char* input_copy) {
char* input = malloc(strlen(input_copy) + 1);
if (input == NULL) return;
strcpy(input, input_copy);
char* tok = strtok(input, " \t");
while (tok != NULL) {
if (!is_malformed(tok)) {
// Fixed: Copy token to new allocation
char* command = malloc((strlen(tok) + 1) * sizeof(char));
if (command != NULL) {
strcpy(command, tok);
add_to_command_queue(command); // Takes ownership
}
}
tok = strtok(NULL, " \t");
}
// Fixed: Free original buffer once, at the end
free(input);
}
// Fixed: Preserve original pointer
void fixed_arithmetic() {
int* array = malloc(10 * sizeof(int));
if (array == NULL) return;
// Fill array
for (int i = 0; i < 10; i++) {
array[i] = i;
}
// Fixed: Use separate pointer for traversal
int* ptr = array + 5; // Temporary pointer for access
// ... use ptr for reading ...
process(ptr);
// Fixed: Free original pointer
free(array); // Correct: freeing original allocation
}
// Fixed: Keep original pointer for cleanup
char* fixed_skip_whitespace(char* original, char** out_start) {
char* str = malloc(100);
if (str == NULL) return NULL;
strcpy(str, original);
// Fixed: Use separate pointer for traversal
char* current = str;
// Skip leading whitespace
while (*current == ' ' || *current == '\t') {
current++;
}
// Return both pointers
*out_start = current; // Where content starts
return str; // Original allocation (for freeing)
}
void use_fixed() {
char* content_start;
char* allocation = fixed_skip_whitespace(" Hello ", &content_start);
if (allocation) {
process(content_start); // Use content
free(allocation); // Free original pointer
}
}
// Fixed: Return offset or copy instead of interior pointer
typedef struct {
char* buffer;
size_t offset;
} SubstringResult;
SubstringResult fixed_find_substring(char* haystack, char* needle) {
SubstringResult result = {NULL, 0};
char* buffer = malloc(strlen(haystack) + 1);
if (buffer == NULL) return result;
strcpy(buffer, haystack);
char* found = strstr(buffer, needle);
result.buffer = buffer; // Original allocation
if (found) {
result.offset = found - buffer; // Return offset, not pointer
} else {
result.offset = (size_t)-1; // Not found indicator
}
return result;
}
void use_fixed_substring() {
SubstringResult result = fixed_find_substring("Hello World", "World");
if (result.buffer && result.offset != (size_t)-1) {
printf("Found at offset %zu: %s\n",
result.offset, result.buffer + result.offset);
}
free(result.buffer); // Fixed: Free original allocation
}
// Fixed: C++ using smart pointers
#include <memory>
#include <string>
#include <vector>
class SecureStringProcessor {
public:
bool searchChar(char c) {
// Fixed: unique_ptr manages memory automatically
auto str = std::make_unique<char[]>(20);
strcpy(str.get(), "Search Me!");
// Use the string safely
for (int i = 0; str[i] != '\0'; i++) {
if (str[i] == c) {
return true;
}
}
return false;
// Memory automatically freed when str goes out of scope
}
// Even better: use std::string
bool searchCharString(char c) {
std::string str = "Search Me!";
for (char ch : str) {
if (ch == c) {
return true;
}
}
return false;
// No manual memory management needed
}
};
CVE Examples
- CVE-2019-11930: Function internally called calloc and returned a pointer at an index inside the allocated buffer, leading to invalid memory deallocation.
- CVE-2010-2547: Double-free vulnerability caused by freeing an interior pointer.
References
- MITRE Corporation. "CWE-761: Free of Pointer not at Start of Buffer." https://cwe.mitre.org/data/definitions/761.html
- CERT C Coding Standard. "MEM34-C. Only free memory allocated dynamically."
- CERT C Coding Standard. "MEM31-C. Free dynamically allocated memory when no longer needed."