Release of Invalid Pointer or Reference
Description
Release of Invalid Pointer or Reference is a memory management vulnerability where software attempts to return a memory resource to the system, but either calls the wrong release function or calls the appropriate release function with an invalid pointer. This encompasses both mismatched allocation/deallocation pairs (like using free() on new-allocated memory) and passing incorrect pointers to correct functions (like freeing a pointer that has been moved via pointer arithmetic). The result is undefined behavior that can lead to memory corruption, crashes, or exploitable conditions.
Risk
Releasing invalid pointers corrupts heap metadata and internal memory management structures. This can cause immediate crashes, but more dangerously, it can silently corrupt memory that's later used by the program. Attackers may be able to exploit heap corruption for arbitrary code execution by manipulating freed memory structures. The vulnerability is particularly insidious because the corruption may not manifest until much later in program execution, making debugging extremely difficult. Functions like strtok() and strsep() are common sources of this bug because they return pointers into an existing buffer that callers may mistakenly try to free.
Solution
Always free exactly the pointer that was returned by the allocation function. Don't free pointers that have been modified by pointer arithmetic—keep the original pointer for deallocation. Understand which functions return newly allocated memory versus pointers into existing memory. Use separate variables for traversal while preserving the original allocation pointer. In C++, use smart pointers that automatically manage memory correctly. Use runtime tools like AddressSanitizer or Valgrind to detect invalid frees during development.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Integrity Modify Memory - Invalid pointer release corrupts heap metadata, potentially allowing memory modification. |
| Availability | Scope: Availability DoS: Crash, Exit, or Restart - Memory corruption typically causes crashes. |
| Confidentiality | Scope: Confidentiality, Integrity, Availability Execute Unauthorized Code or Commands - Heap corruption can sometimes be exploited for code execution. |
Example Code
Vulnerable Code
// Vulnerable: Freeing strsep token (points into original buffer)
#include <stdlib.h>
#include <string.h>
void vulnerable_strsep() {
char* inputstring = strdup("first second third fourth");
char* ap[4];
char** inputstringp = &inputstring;
// strsep modifies inputstring and returns pointers INTO it
ap[0] = strsep(inputstringp, " "); // Points into inputstring
ap[1] = strsep(inputstringp, " ");
ap[2] = strsep(inputstringp, " ");
ap[3] = strsep(inputstringp, " ");
// Vulnerable: ap[0-3] point INSIDE inputstring, not separate allocations!
free(ap[0]); // Corrupts heap - freeing interior pointer
free(ap[1]); // Double corruption
free(ap[2]); // More corruption
free(ap[3]); // Even more corruption
}
// Vulnerable: Freeing strtok result
void vulnerable_strtok() {
char* input = malloc(100);
strcpy(input, "one two three");
char* tok = strtok(input, " ");
while (tok != NULL) {
if (is_invalid(tok)) {
// Vulnerable: tok points inside input buffer
free(tok); // Invalid free!
}
tok = strtok(NULL, " ");
}
// Original input pointer may be lost
}
// Vulnerable: Pointer arithmetic before free
void vulnerable_arithmetic() {
char* str = malloc(20);
strcpy(str, "Search Me!");
// Walk through string
while (*str != '\0') {
if (*str == 'M') {
// Vulnerable: str has been incremented
free(str); // Freeing interior pointer!
return;
}
str = str + 1; // Pointer moves
}
free(str); // Still wrong - str is at end of buffer
}
// Vulnerable: Mismatched allocation/deallocation
void vulnerable_mismatch() {
// Allocated with new
BarObj* ptr = new BarObj();
// Vulnerable: Freed with wrong function
free(ptr); // Should be delete
}
// Vulnerable: Freeing stack memory
void vulnerable_stack_free() {
int localVar = 42;
int* ptr = &localVar;
// Vulnerable: Can't free stack memory
free(ptr); // Undefined behavior
}
// Vulnerable: Double free through aliasing
void vulnerable_double_free() {
char* ptr1 = (char*)malloc(100);
char* ptr2 = ptr1; // Alias
// ... use memory ...
free(ptr1);
// Vulnerable: ptr2 is now dangling
free(ptr2); // Double free!
}
// Vulnerable: Function returns interior pointer
char* vulnerable_find_word(const char* text, const char* word) {
char* buffer = strdup(text);
char* found = strstr(buffer, word);
// Vulnerable: Caller might try to free returned pointer
// which is inside buffer, not at its start
return found; // Interior pointer or NULL
// buffer is leaked, and found can't be properly freed
}
void use_vulnerable() {
char* result = vulnerable_find_word("Hello World", "World");
if (result) {
printf("Found: %s\n", result);
free(result); // CRASH: Invalid free
}
}
Fixed Code
// Fixed: Free only the original allocation
#include <stdlib.h>
#include <string.h>
void fixed_strsep() {
char* inputstring = strdup("first second third fourth");
char* original = inputstring; // Keep original for freeing
char* ap[4];
ap[0] = strsep(&inputstring, " ");
ap[1] = strsep(&inputstring, " ");
ap[2] = strsep(&inputstring, " ");
ap[3] = strsep(&inputstring, " ");
// Use the tokens...
for (int i = 0; i < 4 && ap[i] != NULL; i++) {
process(ap[i]);
}
// Fixed: Free only the original allocation
free(original);
}
// Fixed: Copy tokens if you need to own them
void fixed_strtok() {
char* input = malloc(100);
strcpy(input, "one two three");
char* tok = strtok(input, " ");
while (tok != NULL) {
if (!is_invalid(tok)) {
// Fixed: Copy token to own allocation
char* copy = strdup(tok);
add_to_list(copy); // List takes ownership of copy
}
tok = strtok(NULL, " ");
}
// Fixed: Free original buffer once
free(input);
}
// Fixed: Use index instead of moving pointer
void fixed_arithmetic() {
char* str = malloc(20);
if (str == NULL) return;
strcpy(str, "Search Me!");
// Fixed: Use index, don't move pointer
int i = 0;
while (str[i] != '\0') {
if (str[i] == 'M') {
break;
}
i++;
}
// Fixed: str is still the original pointer
free(str);
}
// Alternative: Save original pointer
void fixed_save_original() {
char* original = malloc(20);
if (original == NULL) return;
strcpy(original, "Search Me!");
char* current = original; // Separate traversal pointer
while (*current != '\0') {
if (*current == 'M') {
break;
}
current++;
}
// Fixed: Free original, not traversal pointer
free(original);
}
// Fixed: Matching allocation/deallocation
void fixed_matching() {
// new -> delete
BarObj* ptr1 = new BarObj();
delete ptr1;
// malloc -> free
BarObj* ptr2 = (BarObj*)malloc(sizeof(BarObj));
free(ptr2);
// new[] -> delete[]
int* arr = new int[100];
delete[] arr;
}
// Fixed: Use smart pointers
#include <memory>
void fixed_smart_pointer() {
auto ptr = std::make_unique<BarObj>();
// Automatic correct deallocation
}
// Fixed: Prevent double free with null assignment
void fixed_double_free() {
char* ptr = (char*)malloc(100);
if (ptr == NULL) return;
// ... use memory ...
free(ptr);
ptr = NULL; // Prevent accidental reuse
// Safe: free(NULL) is defined as no-op
// But better to not call at all
}
// Fixed: Return properly allocated result
typedef struct {
char* buffer; // Original allocation
char* found; // Position within buffer (or NULL)
} FindResult;
FindResult fixed_find_word(const char* text, const char* word) {
FindResult result = {NULL, NULL};
result.buffer = strdup(text);
if (result.buffer == NULL) return result;
result.found = strstr(result.buffer, word);
return result;
}
void use_fixed() {
FindResult result = fixed_find_word("Hello World", "World");
if (result.found) {
printf("Found: %s\n", result.found);
}
// Fixed: Free the buffer, not the interior pointer
free(result.buffer);
}
// Alternative: Return a copy or offset
size_t fixed_find_offset(const char* text, const char* word) {
char* found = strstr(text, word);
if (found == NULL) {
return (size_t)-1; // Not found indicator
}
return found - text; // Return offset, not pointer
}
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 when caller freed it.
- CVE-2015-0821: Release of invalid pointer causing memory corruption.
References
- MITRE Corporation. "CWE-763: Release of Invalid Pointer or Reference." https://cwe.mitre.org/data/definitions/763.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."