Use of Object without Invoking Destructor Method
Description
Use of Object without Invoking Destructor Method occurs when code uses an object but fails to invoke its associated finalizer or destructor method afterward. This resource management issue means cleanup operations are neglected, leading to resources being held longer than necessary. In object-oriented programming, destructors are crucial for releasing resources like memory, file handles, network connections, and database connections. Failing to invoke them properly can lead to resource exhaustion.
Risk
Failure to invoke destructor methods has security implications. Memory and other resources are retained longer than necessary, leading to performance degradation. Over time, this can cause resource exhaustion leading to denial of service. File handles, database connections, and other finite resources may be exhausted. Security-sensitive cleanup like clearing credentials from memory may not occur. Lock files may not be released, blocking other processes. Attackers can exploit predictable resource leaks to exhaust system resources. Long-running applications are particularly vulnerable as leaks accumulate.
Solution
Always ensure destructor or finalizer methods are called when objects are no longer needed. Use language constructs that guarantee cleanup such as try-with-resources in Java, using statements in C#, context managers in Python, or RAII in C++. Implement proper resource management patterns. Use smart pointers in C++ to automate destruction. Rely on automatic garbage collection where available but don't depend on it for critical cleanup. Implement and test cleanup code paths. Use static analysis tools to detect missing cleanup calls. Document resource lifecycle requirements for custom classes.
Common Consequences
| Impact | Details |
|---|---|
| Availability | Scope: Availability DoS: Resource Consumption - Resources retained longer than necessary can lead to exhaustion. |
| Other | Scope: Other Reduce Performance - Memory and resource leaks degrade performance over time. |
| Confidentiality | Scope: Confidentiality Information Exposure - Sensitive data may remain in memory if cleanup is not performed. |
Example Code
Vulnerable Code
// Vulnerable: Object used without destructor invocation
class VulnerableResourceHolder {
private:
FILE* file;
char* buffer;
DatabaseConnection* dbConn;
public:
VulnerableResourceHolder(const char* filename, size_t bufSize) {
file = fopen(filename, "r");
buffer = new char[bufSize];
dbConn = new DatabaseConnection("localhost:5432");
}
~VulnerableResourceHolder() {
if (file) fclose(file);
delete[] buffer;
delete dbConn;
}
void processData() {
// Process data using resources
}
};
void vulnerableUsage() {
// Vulnerable: Allocated with new but never deleted
VulnerableResourceHolder* holder =
new VulnerableResourceHolder("data.txt", 4096);
holder->processData();
// BUG: Forgot to delete holder!
// Destructor never called
// Resources leaked: file handle, buffer, db connection
}
void vulnerableArrayUsage() {
// Vulnerable: Array of objects
VulnerableResourceHolder* holders =
new VulnerableResourceHolder[10];
// Use objects...
// BUG: Using delete instead of delete[]
delete holders; // Only first destructor called!
// 9 objects have leaked resources
}
// Vulnerable: Resources not properly cleaned up in Java
public class VulnerableResourceManager {
private Connection dbConnection;
private FileInputStream fileStream;
private Socket socket;
public VulnerableResourceManager(String dbUrl, String filePath, String host)
throws Exception {
dbConnection = DriverManager.getConnection(dbUrl);
fileStream = new FileInputStream(filePath);
socket = new Socket(host, 8080);
}
public void processData() throws Exception {
// Process using resources
Statement stmt = dbConnection.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM data");
// ...
}
// Has a close method but...
public void close() throws Exception {
if (dbConnection != null) dbConnection.close();
if (fileStream != null) fileStream.close();
if (socket != null) socket.close();
}
}
class VulnerableClient {
public void processFile(String dbUrl, String filePath, String host) {
try {
// Vulnerable: close() never called!
VulnerableResourceManager manager =
new VulnerableResourceManager(dbUrl, filePath, host);
manager.processData();
// Forgot to call manager.close()!
// DB connection, file, and socket all leaked
} catch (Exception e) {
// Even worse: exception means close() definitely not called
e.printStackTrace();
}
}
}
# Vulnerable: Python objects without proper cleanup
class VulnerableFileProcessor:
def __init__(self, filename):
self.file = open(filename, 'r')
self.connection = create_database_connection()
self.temp_files = []
def __del__(self):
# Destructor exists but may never be called!
self.file.close()
self.connection.close()
for temp in self.temp_files:
os.remove(temp)
def process(self):
# Create temp files during processing
temp = tempfile.NamedTemporaryFile(delete=False)
self.temp_files.append(temp.name)
# Process data...
def vulnerable_usage():
# Vulnerable: Relying on garbage collector to call __del__
processor = VulnerableFileProcessor("data.txt")
processor.process()
# No explicit cleanup!
# __del__ might not be called immediately
# Or might not be called at all if there are circular references
# Or if interpreter shuts down abnormally
def vulnerable_exception_handling():
processor = VulnerableFileProcessor("data.txt")
try:
processor.process()
raise ValueError("Something went wrong")
except ValueError:
# Vulnerable: processor not cleaned up on exception
pass
# Resources leaked!
Fixed Code
// Fixed: Proper destructor invocation with RAII
class FixedResourceHolder {
private:
std::unique_ptr<FILE, decltype(&fclose)> file;
std::unique_ptr<char[]> buffer;
std::unique_ptr<DatabaseConnection> dbConn;
public:
FixedResourceHolder(const char* filename, size_t bufSize)
: file(fopen(filename, "r"), &fclose),
buffer(std::make_unique<char[]>(bufSize)),
dbConn(std::make_unique<DatabaseConnection>("localhost:5432")) {
if (!file) {
throw std::runtime_error("Failed to open file");
}
}
// Destructor automatically called, resources automatically cleaned
~FixedResourceHolder() = default;
// Non-copyable to prevent double-free
FixedResourceHolder(const FixedResourceHolder&) = delete;
FixedResourceHolder& operator=(const FixedResourceHolder&) = delete;
// Movable
FixedResourceHolder(FixedResourceHolder&&) = default;
FixedResourceHolder& operator=(FixedResourceHolder&&) = default;
void processData() {
// Process data using resources
}
};
void fixedUsage() {
// Fixed: Stack allocation - destructor automatically called
FixedResourceHolder holder("data.txt", 4096);
holder.processData();
// Destructor called automatically when scope exits
}
void fixedHeapUsage() {
// Fixed: Smart pointer ensures destruction
auto holder = std::make_unique<FixedResourceHolder>("data.txt", 4096);
holder->processData();
// Destructor called when unique_ptr goes out of scope
}
void fixedArrayUsage() {
// Fixed: Use vector for automatic cleanup
std::vector<FixedResourceHolder> holders;
holders.reserve(10);
for (int i = 0; i < 10; i++) {
holders.emplace_back("data.txt", 4096);
}
// All destructors called automatically
}
// Fixed: Java with try-with-resources
public class FixedResourceManager implements AutoCloseable {
private final Connection dbConnection;
private final FileInputStream fileStream;
private final Socket socket;
public FixedResourceManager(String dbUrl, String filePath, String host)
throws Exception {
// Initialize resources - if any fail, close those already opened
Connection tempDb = null;
FileInputStream tempFile = null;
try {
tempDb = DriverManager.getConnection(dbUrl);
tempFile = new FileInputStream(filePath);
this.socket = new Socket(host, 8080);
this.dbConnection = tempDb;
this.fileStream = tempFile;
} catch (Exception e) {
// Clean up partial initialization
if (tempDb != null) tempDb.close();
if (tempFile != null) tempFile.close();
throw e;
}
}
public void processData() throws Exception {
try (Statement stmt = dbConnection.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM data")) {
// Process results
}
}
@Override
public void close() throws Exception {
Exception firstException = null;
// Close all resources, collecting any exceptions
try {
if (socket != null) socket.close();
} catch (Exception e) {
firstException = e;
}
try {
if (fileStream != null) fileStream.close();
} catch (Exception e) {
if (firstException == null) firstException = e;
}
try {
if (dbConnection != null) dbConnection.close();
} catch (Exception e) {
if (firstException == null) firstException = e;
}
if (firstException != null) throw firstException;
}
}
class FixedClient {
public void processFile(String dbUrl, String filePath, String host) {
// Fixed: try-with-resources guarantees close() is called
try (FixedResourceManager manager =
new FixedResourceManager(dbUrl, filePath, host)) {
manager.processData();
} catch (Exception e) {
// Even if exception occurs, close() is called
logger.error("Processing failed", e);
}
// Resources always cleaned up
}
}
# Fixed: Python with context managers
class FixedFileProcessor:
def __init__(self, filename):
self._filename = filename
self._file = None
self._connection = None
self._temp_files = []
def __enter__(self):
self._file = open(self._filename, 'r')
self._connection = create_database_connection()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
# Cleanup guaranteed even if exception occurs
if self._file:
self._file.close()
if self._connection:
self._connection.close()
for temp in self._temp_files:
try:
os.remove(temp)
except OSError:
pass
return False # Don't suppress exceptions
def process(self):
temp = tempfile.NamedTemporaryFile(delete=False)
self._temp_files.append(temp.name)
# Process data...
def fixed_usage():
# Fixed: Context manager ensures cleanup
with FixedFileProcessor("data.txt") as processor:
processor.process()
# __exit__ called automatically, even if exception occurs
def fixed_exception_handling():
try:
with FixedFileProcessor("data.txt") as processor:
processor.process()
raise ValueError("Something went wrong")
except ValueError:
pass
# Resources properly cleaned up despite exception
# Alternative: Using contextlib for simpler cases
from contextlib import contextmanager
@contextmanager
def managed_resource(filename):
file = open(filename, 'r')
conn = create_database_connection()
try:
yield file, conn
finally:
file.close()
conn.close()
def using_contextmanager():
with managed_resource("data.txt") as (file, conn):
# Use resources
pass
# Cleanup automatic
CVE Examples
Resource leak vulnerabilities from missing cleanup have contributed to denial-of-service conditions in many applications, though CVEs typically describe the resulting impact rather than this specific cause.
Related CWEs
- CWE-772: Missing Release of Resource after Effective Lifetime (parent)
- CWE-1076: Insufficient Adherence to Expected Conventions (parent)
- CWE-401: Missing Release of Memory after Effective Lifetime (similar)
References
- MITRE Corporation. "CWE-1091: Use of Object without Invoking Destructor Method." https://cwe.mitre.org/data/definitions/1091.html
- C++ Core Guidelines. R.11: Avoid calling new and delete explicitly.
- CISQ Quality Measures - Performance Efficiency.