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

ImpactDetails
IntegrityScope: Integrity

Modify Memory - Invalid pointer release corrupts heap metadata, potentially allowing memory modification.
AvailabilityScope: Availability

DoS: Crash, Exit, or Restart - Memory corruption typically causes crashes.
ConfidentialityScope: 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

  1. MITRE Corporation. "CWE-763: Release of Invalid Pointer or Reference." https://cwe.mitre.org/data/definitions/763.html
  2. CERT C Coding Standard. "MEM34-C. Only free memory allocated dynamically."
  3. CERT C Coding Standard. "MEM31-C. Free dynamically allocated memory when no longer needed."