Improper Resource Shutdown or Release
Description
Improper Resource Shutdown or Release occurs when software fails to properly close, release, or cleanup resources after they are no longer needed. This includes file handles, database connections, network sockets, memory allocations, locks, and other system resources. Resources may leak gradually, leading to exhaustion, or may remain locked, causing deadlocks. Improper cleanup can also leave sensitive data in memory or files accessible after intended lifetime.
Risk
Resource leaks cause progressive degradation of system performance and stability. File handle exhaustion prevents new files from being opened. Connection pool exhaustion blocks database access. Memory leaks eventually crash applications or entire systems. Socket leaks lead to port exhaustion. Lock leaks cause deadlocks. In security contexts, resources not properly released may leave sensitive data accessible. Attackers can exploit resource exhaustion for denial of service by triggering code paths that leak resources.
Solution
Implement deterministic resource cleanup using try-finally blocks, context managers, or RAII patterns. Use connection pools with proper configuration. Set timeouts on all resource operations. Implement resource limits and monitoring. Use static analysis to detect resource leaks. Employ memory-safe languages where possible. Clear sensitive data before releasing memory. Close resources in reverse order of acquisition. Test resource cleanup under error conditions.
Common Consequences
| Impact | Details |
|---|---|
| Availability | Scope: Resource Exhaustion Leaked resources eventually exhaust system capacity. |
| Confidentiality | Scope: Data Exposure Sensitive data may persist in unreleased resources. |
| Stability | Scope: System Crash Progressive leaks lead to out-of-memory or similar crashes. |
Example Code + Solution Code
Vulnerable Code
# VULNERABLE: File handle leak
def read_config_vulnerable(path):
f = open(path, 'r')
config = json.load(f)
# File never closed!
return config
# VULNERABLE: Exception prevents cleanup
def process_file_vulnerable(path):
f = open(path, 'r')
data = f.read()
process(data) # If this raises, file never closed!
f.close()
# VULNERABLE: Database connection leak
import sqlite3
def query_user_vulnerable(user_id):
conn = sqlite3.connect('app.db')
cursor = conn.cursor()
cursor.execute("SELECT * FROM users WHERE id=?", (user_id,))
result = cursor.fetchone()
# Connection never closed!
return result
# VULNERABLE: Network socket leak
import socket
def send_data_vulnerable(host, port, data):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
sock.send(data)
# Socket never closed!
return True
# VULNERABLE: Lock not released on exception
import threading
lock = threading.Lock()
def update_shared_vulnerable(value):
lock.acquire()
shared_data['value'] = value
process_update() # If exception, lock never released!
lock.release()
# VULNERABLE: Memory not cleared before release
def process_secret_vulnerable(secret):
key = derive_key(secret)
encrypted = encrypt(data, key)
# Secret and key still in memory when function returns!
return encrypted
// VULNERABLE: Java resource leaks
public class VulnerableResources {
// VULNERABLE: Stream not closed
public String readFileVulnerable(String path) throws IOException {
FileInputStream fis = new FileInputStream(path);
byte[] data = fis.readAllBytes();
// Stream never closed!
return new String(data);
}
// VULNERABLE: Connection leak
public User getUserVulnerable(int userId) throws SQLException {
Connection conn = DriverManager.getConnection(URL, USER, PASS);
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id=?");
ps.setInt(1, userId);
ResultSet rs = ps.executeQuery();
// Nothing closed!
if (rs.next()) {
return mapUser(rs);
}
return null;
}
// VULNERABLE: Exception path leak
public void processDataVulnerable(String path) throws Exception {
FileInputStream fis = new FileInputStream(path);
BufferedReader reader = new BufferedReader(new InputStreamReader(fis));
String line = reader.readLine();
processLine(line); // If exception here, streams never closed!
reader.close();
fis.close();
}
// VULNERABLE: Lock not released
private final ReentrantLock lock = new ReentrantLock();
public void updateVulnerable(String value) {
lock.lock();
data.setValue(value);
notifyListeners(); // If exception, lock held forever!
lock.unlock();
}
}
// VULNERABLE: Node.js resource leaks
const fs = require('fs');
// VULNERABLE: File descriptor leak
function readFileVulnerable(path) {
return new Promise((resolve, reject) => {
fs.open(path, 'r', (err, fd) => {
if (err) return reject(err);
const buffer = Buffer.alloc(1024);
fs.read(fd, buffer, 0, 1024, 0, (err, bytes) => {
if (err) return reject(err); // fd not closed!
resolve(buffer.slice(0, bytes));
// fd never closed on success either!
});
});
});
}
// VULNERABLE: Database connection leak
async function getUserVulnerable(userId) {
const client = new Client();
await client.connect();
const result = await client.query('SELECT * FROM users WHERE id = $1', [userId]);
// If query fails, connection never closed!
return result.rows[0];
// Even on success, connection leaks!
}
// VULNERABLE: Event listener leak
class VulnerableComponent {
constructor() {
this.onData = this.handleData.bind(this);
eventEmitter.on('data', this.onData);
// Listener never removed - memory leak!
}
handleData(data) {
// Handle data...
}
// No cleanup method!
}
// VULNERABLE: Interval not cleared
function startPollingVulnerable() {
const interval = setInterval(() => {
fetchData().then(processData);
}, 1000);
// Interval never cleared - runs forever!
return { start: Date.now() };
}
Fixed Code
# SAFE: Context managers for automatic cleanup
import contextlib
# SAFE: Using 'with' statement
def read_config_safe(path):
with open(path, 'r') as f:
return json.load(f)
# File automatically closed!
# SAFE: Database with context manager
import sqlite3
def query_user_safe(user_id):
with sqlite3.connect('app.db') as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM users WHERE id=?", (user_id,))
return cursor.fetchone()
# Connection automatically closed!
# SAFE: Socket with context manager
import socket
def send_data_safe(host, port, data):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((host, port))
sock.send(data)
return True
# Socket automatically closed!
# SAFE: Lock with context manager
import threading
lock = threading.Lock()
def update_shared_safe(value):
with lock:
shared_data['value'] = value
process_update()
# Lock automatically released, even on exception!
# SAFE: try-finally for explicit cleanup
def process_multiple_resources_safe():
resource1 = None
resource2 = None
try:
resource1 = acquire_resource1()
resource2 = acquire_resource2()
do_work(resource1, resource2)
finally:
# Always cleanup, in reverse order
if resource2:
resource2.close()
if resource1:
resource1.close()
# SAFE: Clearing sensitive data
import ctypes
def process_secret_safe(secret):
try:
key = derive_key(secret)
encrypted = encrypt(data, key)
return encrypted
finally:
# Clear sensitive data from memory
# For bytes/bytearray:
if hasattr(key, '__iter__'):
for i in range(len(key)):
key[i] = 0
# For strings, this is harder - avoid storing secrets as strings
# SAFE: Custom context manager
@contextlib.contextmanager
def managed_connection(url):
conn = create_connection(url)
try:
yield conn
finally:
conn.close()
# Usage
def use_connection_safe():
with managed_connection('db://localhost') as conn:
return conn.query('SELECT * FROM users')
// SAFE: Java with try-with-resources
public class SafeResources {
// SAFE: Try-with-resources for auto-close
public String readFileSafe(String path) throws IOException {
try (FileInputStream fis = new FileInputStream(path)) {
return new String(fis.readAllBytes());
}
// Stream automatically closed!
}
// SAFE: Multiple resources in try-with-resources
public User getUserSafe(int userId) throws SQLException {
try (Connection conn = DriverManager.getConnection(URL, USER, PASS);
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id=?")) {
ps.setInt(1, userId);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return mapUser(rs);
}
}
}
// All resources closed!
return null;
}
// SAFE: Lock in try-finally
private final ReentrantLock lock = new ReentrantLock();
public void updateSafe(String value) {
lock.lock();
try {
data.setValue(value);
notifyListeners();
} finally {
lock.unlock(); // Always released!
}
}
// SAFE: Connection pool usage
@Autowired
private DataSource dataSource; // Pooled connection
public User getUserPooled(int userId) throws SQLException {
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id=?")) {
ps.setInt(1, userId);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return mapUser(rs);
}
}
}
// Connection returned to pool!
return null;
}
// SAFE: Clear sensitive data
public byte[] processSecretSafe(byte[] secret) {
byte[] key = null;
try {
key = deriveKey(secret);
return encrypt(data, key);
} finally {
// Clear sensitive data
if (key != null) {
Arrays.fill(key, (byte) 0);
}
Arrays.fill(secret, (byte) 0);
}
}
}
// SAFE: Custom AutoCloseable
public class ManagedResource implements AutoCloseable {
private boolean closed = false;
public void doWork() {
if (closed) throw new IllegalStateException("Resource closed");
// Work...
}
@Override
public void close() {
if (!closed) {
cleanup();
closed = true;
}
}
}
// Usage
try (ManagedResource resource = new ManagedResource()) {
resource.doWork();
}
// SAFE: Node.js with proper cleanup
const fs = require('fs').promises;
// SAFE: Using promises and finally
async function readFileSafe(path) {
let handle;
try {
handle = await fs.open(path, 'r');
const buffer = Buffer.alloc(1024);
const { bytesRead } = await handle.read(buffer, 0, 1024, 0);
return buffer.slice(0, bytesRead);
} finally {
if (handle) {
await handle.close();
}
}
}
// SAFE: Database connection with cleanup
async function getUserSafe(userId) {
const client = new Client();
try {
await client.connect();
const result = await client.query('SELECT * FROM users WHERE id = $1', [userId]);
return result.rows[0];
} finally {
await client.end(); // Always close!
}
}
// SAFE: Using connection pool
const { Pool } = require('pg');
const pool = new Pool();
async function getUserPooled(userId) {
const client = await pool.connect();
try {
const result = await client.query('SELECT * FROM users WHERE id = $1', [userId]);
return result.rows[0];
} finally {
client.release(); // Return to pool!
}
}
// SAFE: Event listener cleanup
class SafeComponent {
constructor() {
this.onData = this.handleData.bind(this);
eventEmitter.on('data', this.onData);
}
handleData(data) {
// Handle data...
}
destroy() {
// Remove listener on cleanup!
eventEmitter.off('data', this.onData);
}
}
// SAFE: Interval cleanup
function startPollingSafe() {
const interval = setInterval(() => {
fetchData().then(processData);
}, 1000);
return {
start: Date.now(),
stop: () => clearInterval(interval) // Cleanup method!
};
}
// SAFE: AbortController for cleanup
async function fetchWithTimeout(url, timeout) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, { signal: controller.signal });
return response.json();
} finally {
clearTimeout(timeoutId); // Always cleanup timeout!
}
}
// SAFE: Async resource cleanup pattern
class AsyncResource {
constructor() {
this.resource = null;
}
async initialize() {
this.resource = await createResource();
}
async [Symbol.asyncDispose]() {
if (this.resource) {
await this.resource.close();
this.resource = null;
}
}
}
// Usage with explicit disposal
async function useResourceSafe() {
const resource = new AsyncResource();
try {
await resource.initialize();
// Use resource...
} finally {
await resource[Symbol.asyncDispose]();
}
}
Exploited in the Wild
Resource Exhaustion DoS
Applications have been DoS'd by repeatedly triggering resource leaks until system capacity is exhausted.
Memory Leak Information Disclosure
Sensitive data remaining in uncleared memory has been extracted through various memory disclosure techniques.
Connection Pool Exhaustion
Database connection leaks have caused application outages when connection pools are exhausted.
Tools to test/exploit
-
Valgrind — memory and resource leak detection.
-
Application profilers — track resource usage.
-
Static analyzers — detect missing close/release.
-
Load testing tools — trigger resource exhaustion.
CVE Examples
-
CVE-2019-11358 — Resource leak issues.
-
CVE-2021-22096 — Memory leak in Spring.
-
Numerous application-specific resource leak vulnerabilities.
References
-
MITRE. "CWE-404: Improper Resource Shutdown or Release." https://cwe.mitre.org/data/definitions/404.html
-
OWASP. "Resource Management." https://owasp.org/www-community/vulnerabilities/