CWE-401: Missing Release of Memory after Effective Lifetime
Overview
Memory leaks occur when allocated memory or system resources are not released after use. What is never released accumulates: performance degrades as the process grows, and it eventually exhausts the resource and crashes. Leaked memory still holds whatever was written into it, so it adds to what a memory dump can expose.
How a leak arises depends on the language - manual memory management in C and C++, unreleased resources in Java and C#, goroutine leaks in Go - but the security impact is the same: resource exhaustion ending in denial of service.
Relationship to Other CWEs
Keep the finding here when what leaked is memory. CWE-401 sits under CWE-772 (Missing Release of Resource after Effective Lifetime), which has no page here, and CWE-404 (Improper Resource Shutdown or Release) - which of the two is the immediate parent depends on the MITRE view, and CWE-404's page sets out where they disagree. When the unreleased resource is a file descriptor, a connection or a socket, CWE-404 is the page with the guidance and CWE-772 the more precise number to file it under.
This page and its nearest neighbour differ by what happened to the allocation, not by what was allocated:
- CWE-401 (this page) - the release never happens, so allocations accumulate for the life of the process
- CWE-415 (Double Free) - the release happens twice. The two sit close enough together in C++ ownership bugs to be confused for each other: a constructor that throws part-way through leaks a raw
new-ed member rather than freeing it twice, a case CWE-415's C++ page works through
A scanner that reports unmanaged resources as CWE-398 (Indicator of Poor Code Quality) is quoting a category rather than a weakness, and MITRE prohibits categories for mapping. CWE-401 is the member weakness to file in its place when the resource that went unreleased is memory.
Risk
Medium-High: A leak degrades performance while it accumulates and ends in resource exhaustion: a crash (OutOfMemoryError) or a denial of service. Leaked memory may still hold sensitive data, so it adds to what a memory dump or a memory-related exploit can reach.
Remediation Steps
Core Principle: Release resources deterministically. A resource leak is a denial-of-service bug.
Locate the memory leak
- Read the finding for the file, line, and code pattern that leaks
- Work out which kind of leak it is: an unclosed resource such as a file or connection, an event listener left registered, an unbounded cache, a reference cycle, or a leaked goroutine
- Work out what is being retained: database connections, file handles, large objects, timers, detached DOM nodes
- Confirm it with a memory profiler - heap dump analysis, allocation tracking over time, goroutine counts
Use automatic resource management (Primary Defense)
Each language has a construct that ties release to scope. Use it, so that resources are released on every path out of the function, including the ones that throw:
- 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
- Release on every path out of the function: normal return, early return, and exception
- Remove event listeners when the component that registered them is destroyed
- Give long-running operations a way to stop: timeouts, abort controllers, context cancellation
- Clear timers and intervals so nothing fires after cleanup
Avoid common leak sources
Most findings are one of the patterns described below:
- Resources left open: files, database connections, network sockets, HTTP response bodies
- Event listeners never unregistered: DOM events, custom events, observables, subscriptions
- setInterval or setTimeout without the matching clear call
- Collections that grow without a size limit or expiry: static collections, global caches, session managers
- Reference cycles - parent-child relationships, closures capturing large objects - under a reference-counting runtime
- Goroutines and threads with no termination signal
- DOM nodes removed from the document but still referenced from JavaScript
Monitor and test for leaks
- Profile with the language's own memory tooling to find where allocations are being retained
- Watch production metrics: heap usage, connection pool size, file descriptor count, goroutine count
- Run sustained load tests for hours and watch how memory grows
- Check that objects become unreachable once the code is finished with them
Test the memory leak fix
- Profile before and after the change, and confirm the retained objects are gone
- Load test for an extended period; memory should level off rather than climb
- Check connection pool metrics and file descriptor counts to confirm resources are being released
- Compare heap dumps taken before and after a load test
- Re-scan with the security scanner to confirm the issue is resolved
Common Vulnerable Patterns
The first four patterns below recur in every language. The fifth does not - whether a reference cycle leaks at all depends on how the runtime reclaims memory.
Unclosed Resources
// VULNERABLE - pseudo-code
handle = open_resource(path)
data = read(handle)
return parse(data) // no close, on this path or on the one where parse() throws
// Result: release is left to whatever the runtime does eventually, if anything
Why this is vulnerable: the release stops being something the code controls. What happens next depends on the runtime and on the specific type, and the range is wide: some wrappers attach a finalizer or cleaner that closes the handle once the object is collected, some runtimes free it promptly when the last reference drops, and some resources - pooled connections, locks, native handles behind a thin binding - have no fallback at all and are held until the process exits. No language guarantees a finalizer runs before exit, so the best case here is late and the worst case is never.
Late is enough to cause the outage. The operating system quota for descriptors is small - typically a few thousand - and completely unrelated to memory pressure, which is the only thing a collector reacts to. A process with gigabytes free therefore has no reason to run a collection, and can exhaust its descriptors while every memory metric looks healthy. Where a finalizer does exist it is a backstop against a slow leak, not a release mechanism, and code that relies on it is depending on timing nobody specified.
The exhaustion is also hard to trace back. It surfaces at whichever unrelated piece of code next asks for a descriptor - a log write, a new connection, a config reload - by which time the leaking function is long finished and appears nowhere in the report. Acquire inside the language's scope-bound construct - try-with-resources, using, with, defer, RAII - so that release is unconditional and happens at a point the code decides.
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
// VULNERABLE - pseudo-code
function attach(component):
publisher.on("update", component.handleUpdate)
// component is discarded when its view closes; publisher still holds the handler
// Result: the component, and everything its handler captured, stays reachable
Why this is vulnerable: the reference points the opposite way to the dependency. The component is what needs the publisher, so a reader expects the component to hold it - but subscribing makes the long-lived publisher hold the short-lived component, and lifetime is now decided by the wrong end. The component becomes unreachable from the application while remaining reachable from the collector's roots, which is exactly what a leak looks like in a managed language: nothing is corrupt, nothing errors, and the memory is never returned.
The retained set is also far larger than the handler suggests. A closure or bound method keeps its entire enclosing scope alive, so one listener on a static publisher can hold a whole view, its model, and any response data that was in scope when it was registered. Because each attach adds another, the growth tracks how often the view is opened.
Examples:
- DOM event listeners in destroyed components
- Static event publishers with component subscribers
- Observable subscriptions without unsubscribe
- Callback registrations without deregistration
Unbounded Collections
// VULNERABLE - pseudo-code
cache = {} // lives for the process, no bound, no expiry
function lookup(key):
if key not in cache:
cache[key] = expensive_query(key)
return cache[key]
// Attack: send distinct keys that will never be requested twice
Why this is vulnerable: everything in the map is live by the collector's definition, because it is referenced - so no amount of memory pressure reclaims any of it, and the only thing deciding how large it grows is who chooses the keys. Where the key space is attacker-controlled and unbounded - a URL, a header, a search term, a session identifier - the cache stops being a cache and becomes an append-only record of every request, with a hit rate near zero as a second effect.
This is the pattern in this list an attacker can drive directly, so it is worth triaging differently from the others: the question is not whether the collection is bounded in practice today, but whether anything in the code bounds it. A key space bounded by something the application controls - a fixed enum, a table of known identifiers - is a different finding from one bounded only by how much traffic arrives.
Examples:
- Static collections without cleanup
- Caches without eviction policies
- Maps keyed by unbounded user input
- Session managers without expiration
Background Task Leaks
// VULNERABLE - pseudo-code
function startPolling(session):
every(5_seconds): // no handle kept, no stop condition
refresh(session)
// Result: the timer outlives the session, keeps it reachable, and keeps working
Why this is vulnerable: one line leaks twice. The scheduler holds the task, so the task is never reclaimed; the task holds session, so the session and everything it references are retained with it. Each call to startPolling adds another, and the accumulation is proportional to how many sessions the process has ever seen rather than how many are open.
The second cost is easy to overlook because it is not memory. The task keeps running - refreshing a session nobody is using, issuing the queries and downstream calls that refresh implies - so a service that leaks these gets slower and noisier as it ages, and the load it generates is invisible in any per-request measurement. Note also that no handle was kept, so there is nothing to cancel with even once the bug is understood: the fix has to change the function's shape, not add a call to it.
Examples:
- Goroutines blocking without cancellation signal
- setInterval without clearInterval
- Worker threads without shutdown mechanism
- Polling loops without exit condition
Circular References
// VULNERABLE - pseudo-code, reference-counted runtime only
parent.child = child
child.parent = parent // each holds a strong reference to the other
release(parent); release(child) // both counts fall to 1, neither reaches 0
// Result: unreachable from the program, never freed
Why this is vulnerable: this one depends entirely on how the runtime reclaims memory, and treating it as universal produces false positives. A tracing collector starts from the roots and reclaims whatever it cannot reach, so an unreachable cycle is ordinary garbage and needs no special handling - that covers Java, C#, Go, and every current JavaScript engine, including the DOM-to-script cycles that genuinely did leak in browsers two decades ago. Where a cycle does leak is under reference counting, because a count that never falls to zero is never freed: std::shared_ptr in C++, and ARC in Swift and Objective-C. The fix there is to make one direction non-owning - weak_ptr, or a weak/unowned capture.
CPython sits between the two and is the case most often described out of date. It reference-counts and runs a cycle detector, and since PEP 442 in Python 3.4 that detector also collects cycles whose members define __del__ - so the old advice about finalizers making a cycle permanently uncollectable no longer applies. What remains true is that collection happens on a periodic pass rather than at the last release, so anything the cycle holds - an open file, a socket, a lock - stays held until then, which is a resource problem rather than a memory one.
Examples:
std::shared_ptrcycles in C++, broken withweak_ptron the back-reference- Strong
selfcaptures in Swift or Objective-C closures under ARC - Python cycles holding a file or socket, released only when the collector next runs
- Not a leak under a tracing collector: Java, C#, Go and current JavaScript engines reclaim unreachable cycles like any other garbage
Secure Patterns
These principles hold in every language; only the mechanism changes.
Deterministic Resource Cleanup
Release resources explicitly, at a point the code decides, rather than leaving it to 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
Give every long-lived collection a maximum size and an eviction policy.
Implementation by language:
- Java: LRU cache with LinkedHashMap - Example
- Python: functools.lru_cache - Example
- JavaScript: WeakMap for auto-eviction - Example
Weak and Soft References
Use a non-strong reference for caches and relationships that should not prevent garbage collection - but pick the right one. A weak reference is cleared as soon as nothing strongly reaches the object, regardless of free memory, so it suits a side-table keyed by objects someone else owns. A cache wants a soft reference, which is cleared only under memory pressure; a weak-referenced cache is functionally a cache with no hits.
Implementation by language:
- Java: SoftReference for cache values, WeakHashMap for object-keyed side tables - Example
- C++: std::weak_ptr - Example
- C#: Weak event pattern - Example
- Python: weakref module - Example
- JavaScript: WeakMap/WeakSet - Example
Cancellation and Timeouts
Give long-running operations a way to be cancelled. Abandoning one that has none leaks whatever it was holding.
Implementation by language:
- Go: Context cancellation - Example
- JavaScript: AbortController - Example
- C#: CancellationToken - Example
Language-Specific Guidance
Leak patterns and their fixes depend on the language and runtime:
- 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, soft and 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