CWE-404: Improper Resource Shutdown or Release
Overview
Improper resource shutdown occurs when applications fail to close files, database connections, sockets, or other resources after use, leading to resource exhaustion, file descriptor leaks, connection pool depletion, and denial of service.
Risk
Medium-High: Missing resource cleanup causes connection pool exhaustion, file descriptor limits hit, memory leaks, degraded performance, denial of service, locked files, and eventual application crashes. Can expose sensitive data in open connections.
Remediation Steps
Core principle: Ensure cleanup occurs on all paths (including errors); secure failure includes resource and state cleanup.
Locate the Resource Leak Vulnerability
When reviewing security scan results:
- Examine data_paths: Identify where resources are acquired but not properly released
- Identify resource types: Files, database connections, sockets, streams, handles
- Trace resource lifecycle: Look for missing close/dispose calls in all code paths
- Check exception paths: Ensure resources are closed even when exceptions occur
- Review control flow: Identify branches where close() calls might not be reached
Use Automatic Resource Management (Primary Defense)
Java (try-with-resources):
try (Connection conn = getConnection();
Statement stmt = conn.createStatement()) {
stmt.execute(sql);
} // Auto-closed
Python (with statement):
C# (using statement):
Why this works: Language-specific automatic resource management ensures cleanup occurs even during exceptions, prevents forgetting to close resources, and handles complex cleanup scenarios correctly.
Ensure Finally Blocks Execute Properly
For languages without automatic resource management:
InputStream is = null;
try {
is = new FileInputStream(file);
process(is);
} finally {
if (is != null) {
try { is.close(); } catch (IOException e) { }
}
}
Implementation details:
- Always use finally blocks for resource cleanup when try-with-resources is unavailable
- Check for null before closing to avoid NullPointerException
- Catch and suppress close() exceptions to avoid masking original exceptions
- Place all cleanup code in finally to ensure execution
Close Resources in Correct Order
Ordering rules:
- Close in reverse order of opening (LIFO - Last In, First Out)
- Close ResultSet before Statement before Connection
- Close derived streams before base streams
- Close streams before sockets
- Close child resources before parent resources
Example:
try (Connection conn = getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
// Resources closed in reverse order: rs, stmt, conn
}
Handle Cleanup Exceptions Appropriately
Exception handling strategies:
- Don't let cleanup exceptions mask the original exception
- Log cleanup failures for debugging
- Use try-with-resources which automatically handles suppressed exceptions
- Use addSuppressed() to attach cleanup exceptions to primary exception
Example:
Exception primary = null;
try {
riskyOperation();
} catch (Exception e) {
primary = e;
} finally {
try {
cleanup();
} catch (Exception e) {
if (primary != null) {
primary.addSuppressed(e);
}
}
if (primary != null) throw primary;
}
Monitor and Test for Resource Leaks
Testing approaches:
- Monitor file descriptor usage during testing
- Use memory profilers to detect resource leaks
- Test exception paths explicitly
- Run load tests to expose connection pool exhaustion
- Check metrics: open files, connections, handles
Verification steps:
- Run application under normal and error conditions
- Monitor resource usage over extended periods
- Use tools like lsof (Linux) or Resource Monitor (Windows)
- Set up alerts for resource threshold violations
Common Vulnerable Patterns
// No close
BufferedReader reader = new BufferedReader(new FileReader(file));
String line = reader.readLine();
// Missing reader.close()
// Close only on success
Connection conn = getConnection();
if (condition) {
conn.close(); // Not always reached
}
// Exception prevents close
FileInputStream fis = new FileInputStream(file);
process(fis); // Might throw
fis.close(); // Never reached if exception