CWE-401: Missing Release of Memory After Effective Lifetime
Overview
Memory leaks occur when allocated memory or system resources aren't released after use, causing gradual resource exhaustion over time, leading to performance degradation, denial of service, application crashes, and in severe cases, enabling information disclosure through memory dumps.
While the specifics of memory management vary significantly by language (manual memory management in C/C++, resource management in Java/C#, goroutine leaks in Go), the security impact is consistent: resource exhaustion leading to denial of service and application instability.
Risk
Medium-High: Memory leaks cause application crashes (OutOfMemoryError), denial of service, degraded performance, resource exhaustion, potential information disclosure (leaked memory may contain sensitive data), and increased attack surface for memory-related exploits.
Remediation Steps
Core principle: Release resources deterministically; resource leaks are security bugs (DoS) and must be prevented.
Locate the memory leak in your code
- Review the flaw details to identify the specific file, line number, and code pattern that leaks memory
- Identify the leak source: unclosed resources (files, connections), event listeners, unbounded caches, circular references, goroutine leaks
- Determine what's being leaked: database connections, file handles, large objects, event listeners, timers, detached DOM nodes
- Use memory profiler to confirm the leak: heap dump analysis, track object allocation over time, monitor goroutine count
Use automatic resource management (Primary Defense)
Each language provides mechanisms for deterministic resource cleanup. The key is ensuring resources are released in all code paths, including error conditions:
- Java: Try-with-resources for AutoCloseable resources
- C++: RAII with smart pointers and destructors
- C#: Using statements for IDisposable resources
- Python: Context managers (with statement)
- Go: Defer for cleanup and context for cancellation
- JavaScript: Lifecycle cleanup functions (useEffect return, ngOnDestroy, beforeUnmount)
Implement proper cleanup patterns
- Close resources in all code paths: Ensure cleanup happens on normal exit, early returns, and exception paths
- Use language-specific cleanup mechanisms: Try-with-resources, RAII, using statements, context managers, defer
- Unsubscribe from events: Remove event listeners when components are destroyed
- Cancel long-running operations: Use timeouts, abort controllers, context cancellation
- Clear timers and intervals: Ensure timers don't run after component cleanup
Avoid common leak sources
- Unclosed resources: Files, database connections, network sockets, HTTP response bodies
- Event listeners not unregistered: DOM events, custom events, observables, subscriptions
- Unclosed timers/intervals: setInterval, setTimeout without corresponding clear calls
- Unbounded caches: Static collections, global caches without size limits or expiration
- Circular references: Parent-child relationships, closures capturing large objects
- Goroutine/thread leaks: Background tasks without termination signals
- Detached DOM nodes: Removed from document but still referenced in JavaScript
Monitor and test for leaks
- Use memory profilers: Language-specific profiling tools to identify leak sources
- Monitor production metrics: Heap usage, connection pool size, file descriptor count, goroutine count
- Load testing: Run sustained load tests for hours, monitor memory growth patterns
- Review object lifecycle: Ensure objects can be garbage collected when no longer needed
Test the memory leak fix
- Run memory profiler before and after: Verify leak is gone
- Load test for extended periods: Memory usage should stabilize, not grow continuously
- Verify resource closure: Check connection pool metrics, file descriptor count
- Test with heap dump analysis: Compare heap dumps before/after load test
- Re-scan with security scanner to confirm the issue is resolved
Common Leak Patterns
Memory leaks manifest differently across languages, but some patterns are universal:
Unclosed Resources
Files, database connections, network sockets, and other operating system resources must be explicitly closed. Relying on garbage collection is insufficient - these resources are limited and must be released deterministically.
Examples:
- Files opened without close() calls
- Database connections not returned to pool
- HTTP response bodies not closed (Go)
- Network sockets left open
Event Listener Leaks
Event listeners create strong references from publishers to subscribers. Without explicit unsubscription, subscribers remain in memory indefinitely, even when no longer needed.
Examples:
- DOM event listeners in destroyed components
- Static event publishers with component subscribers
- Observable subscriptions without unsubscribe
- Callback registrations without deregistration
Unbounded Collections
Caches, maps, and arrays that grow indefinitely eventually exhaust available memory. All long-lived collections must have size limits or expiration policies.
Examples:
- Static collections without cleanup
- Caches without eviction policies
- Maps keyed by unbounded user input
- Session managers without expiration
Background Task Leaks
Background threads, goroutines, timers, and intervals must have explicit termination conditions. Without cleanup, they accumulate over application lifetime.
Examples:
- Goroutines blocking without cancellation signal
- setInterval without clearInterval
- Worker threads without shutdown mechanism
- Polling loops without exit condition
Circular References
Reference cycles prevent garbage collection in some scenarios, especially when mixed with unmanaged resources or finalizers.
Examples:
- Parent-child bidirectional references with raw pointers
- Closures capturing objects that reference the closure
- DOM nodes referencing JavaScript objects that reference DOM nodes
- Finalizers preventing cycle collection
General Secure Patterns
While implementation details vary by language, these principles apply universally:
Deterministic Resource Cleanup
Resources must be released in a predictable, explicit manner rather than relying on garbage collection or finalization.
Implementation by language:
- Java: Try-with-resources - Example
- C++: RAII with smart pointers - Example
- C#: Using statements - Example
- Python: Context managers - Example
- Go: Defer - Example
- JavaScript: Lifecycle cleanup - Example
Bounded Data Structures
All long-lived collections should have maximum size limits and eviction policies.
Implementation by language:
- Java: LRU cache with LinkedHashMap - Example
- Python: functools.lru_cache - Example
- JavaScript: WeakMap for auto-eviction - Example
Weak References
Use weak references for caches and relationships that shouldn't prevent garbage collection.
Implementation by language:
- Java: WeakHashMap and WeakReference - Example
- C++: std::weak_ptr - Example
- C#: Weak event pattern - Example
- Python: weakref module - Example
- JavaScript: WeakMap/WeakSet - Example
Cancellation and Timeouts
Long-running operations must support cancellation to prevent resource leaks from abandoned operations.
Implementation by language:
- Go: Context cancellation - Example
- JavaScript: AbortController - Example
- C#: CancellationToken - Example
Security Checklist
General checklist applicable to all languages. See language-specific guides for detailed checklists:
- Use automatic resource management - Language-specific cleanup mechanisms (try-with-resources, RAII, using, with, defer)
- Close all resources - Files, connections, streams, sockets, response bodies
- Unregister event listeners - Remove subscriptions when components destroyed
- Clear timers and intervals - Cancel background tasks when no longer needed
- Implement bounded collections - Size limits and eviction for all caches
- Use weak references - For caches and relationships that shouldn't prevent GC
- Support cancellation - Timeouts and cancel signals for long-running operations
- Test with profilers - Memory profilers, heap dumps, goroutine dumps
- Monitor production metrics - Heap size, connection pools, file descriptors
- Load test for leaks - Extended runs (hours) to detect gradual growth
Language-Specific Guidance
Memory leak patterns and remediation strategies vary significantly by programming language and runtime environment. See the language-specific guides for detailed vulnerable patterns, secure implementations, and best practices:
- C++ - RAII, smart pointers (unique_ptr/shared_ptr), Rule of Five, custom deleters, breaking circular references
- C# - Using statements, IDisposable pattern, event unsubscription, weak event managers
- Go - Defer, context cancellation, goroutine lifecycle management, worker pool shutdown
- Java - Try-with-resources, AutoCloseable, bounded caches, weak references, connection pooling
- JavaScript/Node.js - Event listener cleanup, timer/interval clearing, WeakMap/WeakSet, React useEffect cleanup
- Python - Context managers (with statement), @contextmanager, weakref, functools.lru_cache