Weakness reference
CWE-401

Missing Release of Memory after Effective Lifetime

A memory leak occurs when a program allocates memory but fails to release it after it is no longer needed. Over time, this causes memory consumption to grow…

01Summary

A memory leak occurs when a program allocates memory but fails to release it after it is no longer needed. Over time, this causes memory consumption to grow unchecked, eventually exhausting available system resources and causing the application to slow down, crash, or become unresponsive. Memory leaks are particularly dangerous in long-running services, daemons, and server applications where even small per-request leaks accumulate into critical failures.

02How It Happens

Memory leaks typically arise when developers allocate memory dynamically (via malloc, new, or similar mechanisms) but forget to deallocate it (via free, delete, or garbage collection) when the data is no longer needed. In languages with manual memory management, this is often an oversight in error-handling paths or complex control flows where cleanup code is skipped. In languages with garbage collection, leaks can still occur through circular references, retained event listeners, or objects held in global caches that are never cleared. The root cause is usually a mismatch between the lifetime of the allocated memory and the lifetime of the code that uses it.

03Real-World Impact

A memory leak in a web server, background worker, or API service can degrade performance over hours or days, causing response times to increase and eventually triggering out-of-memory errors that crash the process. In embedded systems or resource-constrained environments, even modest leaks can render the device unusable within minutes. Attackers can sometimes trigger memory leaks deliberately by sending specially crafted requests, turning a latent bug into a denial-of-service vector. The impact is often insidious because the application may function normally at first, making the bug difficult to detect during testing.

04Vulnerable & Fixed Patterns

Vulnerable pattern
import sqlite3

def fetch_user_data(user_id):
    conn = sqlite3.connect(':memory:')
    cursor = conn.cursor()
    cursor.execute('SELECT * FROM users WHERE id = ?', (user_id,))
    result = cursor.fetchone()
    # Connection and cursor are never closed
    return result

# Called repeatedly in a loop or request handler
for i in range(1000):
    data = fetch_user_data(i)

Why it's vulnerable:
The database connection and cursor are allocated but never explicitly closed. In a long-running process, repeated calls accumulate open connections and associated memory, eventually exhausting resources.

Fixed pattern
import sqlite3

def fetch_user_data(user_id):
    conn = sqlite3.connect(':memory:')
    try:
        cursor = conn.cursor()
        cursor.execute('SELECT * FROM users WHERE id = ?', (user_id,))
        result = cursor.fetchone()
        return result
    finally:
        conn.close()

# Or use context manager for automatic cleanup
def fetch_user_data_safe(user_id):
    with sqlite3.connect(':memory:') as conn:
        cursor = conn.cursor()
        cursor.execute('SELECT * FROM users WHERE id = ?', (user_id,))
        return cursor.fetchone()
Vulnerable pattern
<?php
function process_upload($file_path) {
    $image = imagecreatefromjpeg($file_path);
    $resized = imagescale($image, 800, 600);
    
    // Save resized image
    imagejpeg($resized, '/tmp/output.jpg');
    
    // Image resources are never freed
    return true;
}

// Called repeatedly in a loop
for ($i = 0; $i < 1000; $i++) {
    process_upload("file_$i.jpg");
}
?>

Why it's vulnerable:
GD image resources ($image and $resized) are allocated but never destroyed via imagedestroy(). In a request loop or batch process, memory accumulates and is not reclaimed.

Fixed pattern
<?php
function process_upload($file_path) {
    $image = imagecreatefromjpeg($file_path);
    $resized = imagescale($image, 800, 600);
    
    try {
        imagejpeg($resized, '/tmp/output.jpg');
    } finally {
        // Explicitly free resources
        imagedestroy($resized);
        imagedestroy($image);
    }
    
    return true;
}

// Resources are now properly cleaned up
for ($i = 0; $i < 1000; $i++) {
    process_upload("file_$i.jpg");
}
?>

05Prevention Checklist

Use context managers and try-finally blocks
to guarantee cleanup code runs, even if an exception occurs.
Audit long-running processes
(daemons, request handlers, batch jobs) for resource allocation without corresponding deallocation.
Enable memory profiling and monitoring
in development and production to detect gradual memory growth over time.
Review error-handling paths
carefully; memory leaks often hide in exception handlers or early-return statements where cleanup is skipped.
Use static analysis tools
(e.g., Valgrind, AddressSanitizer, or language-specific linters) to detect unreleased allocations during testing.
Document resource ownership
in code comments so developers know which function is responsible for freeing each allocation.

06Signs You May Already Be Affected

Monitor your application's memory usage over time; a steady, uninterrupted climb in resident memory despite stable request volume is a classic sign of a leak. Check system logs for out-of-memory errors, process crashes, or automatic restarts that occur after the application has been running for hours or days. If you notice that restarting the service temporarily restores performance, but degradation resumes shortly after, a memory leak is likely the culprit.

07Related Recent Vulnerabilities