Missing Release of Memory after Effective Lifetime (Memory Leak)

Description

Missing Release of Memory after Effective Lifetime occurs when a program allocates memory but fails to release it after it is no longer needed. This leads to memory leaks where the program's memory usage grows continuously. In long-running applications, memory leaks can eventually exhaust available memory, causing crashes, system instability, or denial of service. Memory leaks are particularly dangerous in servers, daemons, and embedded systems where applications run continuously.

Risk

Memory leaks are a significant reliability and availability concern. In web servers, each request leaking memory can eventually crash the server. In embedded systems with limited memory, leaks quickly become critical. Memory exhaustion can lead to denial of service. Leaks may also mask other vulnerabilities—memory corruption might not crash immediately but only when memory becomes fragmented. Modern systems with large amounts of RAM may hide leaks until they suddenly become critical.

Solution

Use memory management tools (Valgrind, AddressSanitizer) during development and testing. Implement consistent allocation/deallocation patterns. Use RAII (Resource Acquisition Is Initialization) in C++ with smart pointers. Consider garbage-collected languages for complex applications. Implement reference counting for shared resources. Use static analysis tools to detect leaks. Monitor production systems for memory growth. Implement proper error handling that cleans up allocations on failure paths.

Common Consequences

ImpactDetails
AvailabilityScope: Denial of Service

Continuous memory leaks eventually exhaust available memory, crashing the application.
PerformanceScope: Degradation

Memory pressure causes swapping, garbage collection pressure, and overall slowdown.
ReliabilityScope: System Instability

Memory exhaustion can affect other processes on the same system.

Example Code + Solution Code

Vulnerable Code

// VULNERABLE: Memory allocated but never freed
void process_request(const char *data) {
    char *buffer = malloc(1024);
    strcpy(buffer, data);
    process(buffer);
    // Missing: free(buffer);
    // Memory leaks on every call!
}

// VULNERABLE: Early return without freeing
int read_config(const char *filename) {
    FILE *fp = fopen(filename, "r");
    if (!fp) return -1;

    char *buffer = malloc(4096);
    if (!buffer) {
        fclose(fp);
        return -1;
    }

    if (fread(buffer, 1, 4096, fp) == 0) {
        fclose(fp);
        return -1;  // Leaks buffer!
    }

    // Process config...

    free(buffer);
    fclose(fp);
    return 0;
}

// VULNERABLE: Lost pointer
void create_list(void) {
    Node *head = malloc(sizeof(Node));
    head->next = malloc(sizeof(Node));
    head->next->next = malloc(sizeof(Node));

    // Reassign head - lose reference to allocated memory!
    head = malloc(sizeof(Node));  // Original chain leaked!
}

// VULNERABLE: Exception-like path leaks
int complex_operation(void) {
    void *resource1 = malloc(100);
    void *resource2 = malloc(200);
    void *resource3 = malloc(300);

    if (step1(resource1) != 0) {
        free(resource1);
        return -1;  // Leaks resource2, resource3!
    }

    if (step2(resource2) != 0) {
        free(resource1);
        free(resource2);
        return -1;  // Leaks resource3!
    }

    // ... continue ...
    free(resource1);
    free(resource2);
    free(resource3);
    return 0;
}
// VULNERABLE: C++ raw pointers in exception path
class DataProcessor {
    int* data;

public:
    DataProcessor() {
        data = new int[1000];
    }

    void process() {
        // If exception thrown, destructor not called for local objects
        DataProcessor* temp = new DataProcessor();
        throw std::runtime_error("Error");  // temp leaked!
        delete temp;
    }

    ~DataProcessor() {
        delete[] data;
    }
};

// VULNERABLE: Container of raw pointers
void process_items() {
    std::vector<Item*> items;

    for (int i = 0; i < 100; i++) {
        items.push_back(new Item());
    }

    // Vector destroyed but Items not deleted - all leaked!
}
# VULNERABLE: Python circular reference (GC usually handles, but not always)
class Node:
    def __init__(self):
        self.ref = None
        self.data = bytearray(1024 * 1024)  # 1MB

def create_cycle():
    a = Node()
    b = Node()
    a.ref = b
    b.ref = a  # Circular reference
    # With custom __del__, garbage collector may not collect

# VULNERABLE: File handles not closed (resource leak)
def read_files(filenames):
    contents = []
    for fname in filenames:
        f = open(fname, 'r')  # Never closed!
        contents.append(f.read())
    return contents

Fixed Code

// SAFE: Always free allocated memory
void process_request_safe(const char *data) {
    char *buffer = malloc(1024);
    if (!buffer) return;

    strcpy(buffer, data);
    process(buffer);
    free(buffer);  // Always free
}

// SAFE: Single cleanup path
int read_config_safe(const char *filename) {
    FILE *fp = NULL;
    char *buffer = NULL;
    int result = -1;

    fp = fopen(filename, "r");
    if (!fp) goto cleanup;

    buffer = malloc(4096);
    if (!buffer) goto cleanup;

    if (fread(buffer, 1, 4096, fp) == 0) {
        goto cleanup;  // Single cleanup path
    }

    // Process config...
    result = 0;

cleanup:
    free(buffer);  // free(NULL) is safe
    if (fp) fclose(fp);
    return result;
}

// SAFE: Track all allocations
void create_list_safe(void) {
    Node *head = malloc(sizeof(Node));
    if (!head) return;

    head->next = malloc(sizeof(Node));
    if (!head->next) {
        free(head);
        return;
    }

    head->next->next = malloc(sizeof(Node));
    if (!head->next->next) {
        free(head->next);
        free(head);
        return;
    }

    // Use the list...

    // Free entire list
    free(head->next->next);
    free(head->next);
    free(head);
}

// SAFE: Consistent cleanup pattern
typedef struct {
    void *resource1;
    void *resource2;
    void *resource3;
} Resources;

void cleanup_resources(Resources *r) {
    free(r->resource1);
    free(r->resource2);
    free(r->resource3);
    memset(r, 0, sizeof(*r));
}

int complex_operation_safe(void) {
    Resources r = {0};

    r.resource1 = malloc(100);
    r.resource2 = malloc(200);
    r.resource3 = malloc(300);

    if (!r.resource1 || !r.resource2 || !r.resource3) {
        cleanup_resources(&r);
        return -1;
    }

    if (step1(r.resource1) != 0) {
        cleanup_resources(&r);
        return -1;
    }

    if (step2(r.resource2) != 0) {
        cleanup_resources(&r);
        return -1;
    }

    // Success path also cleans up
    cleanup_resources(&r);
    return 0;
}
// SAFE: Use smart pointers
#include <memory>
#include <vector>

class DataProcessorSafe {
    std::unique_ptr<int[]> data;

public:
    DataProcessorSafe() : data(std::make_unique<int[]>(1000)) {}

    void process() {
        // Smart pointer handles cleanup even with exceptions
        auto temp = std::make_unique<DataProcessorSafe>();
        throw std::runtime_error("Error");  // temp automatically cleaned up!
    }

    // Destructor not needed - unique_ptr handles it
};

// SAFE: Container of smart pointers
void process_items_safe() {
    std::vector<std::unique_ptr<Item>> items;

    for (int i = 0; i < 100; i++) {
        items.push_back(std::make_unique<Item>());
    }

    // Vector destroyed - all Items automatically deleted!
}

// SAFE: RAII wrapper for C resources
class FileHandle {
    FILE* fp;
public:
    explicit FileHandle(const char* name, const char* mode)
        : fp(fopen(name, mode)) {
        if (!fp) throw std::runtime_error("Cannot open file");
    }

    ~FileHandle() {
        if (fp) fclose(fp);
    }

    // Delete copy
    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;

    // Allow move
    FileHandle(FileHandle&& other) noexcept : fp(other.fp) {
        other.fp = nullptr;
    }

    FILE* get() { return fp; }
};

// Usage - automatic cleanup
void process_file(const char* filename) {
    FileHandle fh(filename, "r");
    // Use fh.get()...
    // Automatically closed when fh goes out of scope
}
# SAFE: Use context managers
def read_files_safe(filenames):
    contents = []
    for fname in filenames:
        with open(fname, 'r') as f:  # Auto-closes
            contents.append(f.read())
    return contents

# SAFE: Weak references to break cycles
import weakref

class Node:
    def __init__(self):
        self._ref = None
        self.data = bytearray(1024 * 1024)

    @property
    def ref(self):
        return self._ref() if self._ref else None

    @ref.setter
    def ref(self, value):
        self._ref = weakref.ref(value) if value else None

def create_no_cycle():
    a = Node()
    b = Node()
    a.ref = b  # Normal reference
    b.ref = a  # Weak reference - won't prevent collection

# SAFE: Explicit cleanup
class ManagedResource:
    def __init__(self):
        self.resource = allocate_large_resource()

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.cleanup()

    def cleanup(self):
        if self.resource:
            release_resource(self.resource)
            self.resource = None

# Usage
with ManagedResource() as r:
    use_resource(r)
# Automatically cleaned up

Exploited in the Wild

Microsoft Windows LSASS Memory Leak (2003)

Memory leaks in LSASS (Local Security Authority Subsystem Service) could be triggered remotely, eventually causing system crashes and denial of service.

OpenSSL Memory Leak DoS (2016)

CVE-2016-6304 was a memory leak in OpenSSL's OCSP response handling that could be exploited for denial of service by sending repeated status requests.

Firefox Memory Leaks

Multiple memory leaks in Firefox have been reported over the years, causing browser memory usage to grow significantly during long browsing sessions.


Tools to test/exploit


CVE Examples


References

  1. MITRE. "CWE-401: Missing Release of Memory after Effective Lifetime." https://cwe.mitre.org/data/definitions/401.html

  2. CERT. "MEM31-C: Free dynamically allocated memory when no longer needed." https://wiki.sei.cmu.edu/confluence/display/c/MEM31-C.+Free+dynamically+allocated+memory+when+no+longer+needed