Multiple Releases of Same Resource or Handle
Description
Multiple Releases of Same Resource or Handle occurs when a product attempts to close or release a resource or handle more than once, without any successful open between the close operations. Resources like memory, files, devices, and socket connections require "opening" and subsequent "closing" or "releasing." When developers call release APIs (such as free() in C/C++ or close() operations) more than once on the same resource, the API's expectations are violated, leading to undefined or insecure behavior including memory corruption, data corruption, or execution path corruption. The reuse of resource identifiers can result in closing the wrong resource.
Risk
Multiple releases have severe implications. Memory corruption from double-free. Use-after-free vulnerabilities. Application crashes. Arbitrary code execution through heap manipulation. Data corruption. Resource exhaustion. Security bypass through corrupted state. Wrong resource closure. High likelihood in complex error handling paths.
Solution
Simplify logic to ensure resources close only once through refactoring during implementation phase. Use flags to track resource state—set when opened, clear when closed, check before closing. Set resource variables to NULL after closing since some APIs ignore null values without errors. Use RAII patterns in C++ to automatically manage resource lifetimes. Implement resource wrapper classes that track state.
Common Consequences
| Impact | Details |
|---|---|
| Availability | Scope: Availability, Integrity Program faults, crashes, or unpredictable behavior from resource management errors. |
| Integrity | Scope: Integrity Memory or data corruption from invalid resource state. |
Example Code
Vulnerable Code
// Vulnerable: C code with double close/free
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// VULNERABLE: Double fclose
void vulnerable_file_handling(const char* filename) {
char buffer[1024];
FILE *f = fopen(filename, "r");
if (f) {
fread(buffer, 1, sizeof(buffer) - 1, f);
int r1 = fclose(f); // First close - OK
// ... some processing ...
int r2 = fclose(f); // VULNERABLE: Double close
// Undefined behavior - may crash or corrupt memory
}
}
// VULNERABLE: Double free
void vulnerable_memory_handling(size_t size) {
char* ptr = (char*)malloc(size);
if (ptr == NULL) {
return;
}
// ... use ptr ...
if (some_error_condition) {
free(ptr); // First free in error path
}
// ... more processing ...
free(ptr); // VULNERABLE: Second free - may already be freed
// Heap corruption, potential code execution
}
// VULNERABLE: Double close in error handling
int vulnerable_socket_handling(int port) {
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
return -1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
close(sock); // Close on bind failure
return -1;
}
if (listen(sock, 5) < 0) {
close(sock); // Close on listen failure
return -1;
}
// ... accept connections ...
close(sock); // Normal close
// Later in cleanup code
close(sock); // VULNERABLE: Double close if error occurred
return 0;
}
// VULNERABLE: Resource handle reuse confusion
void vulnerable_handle_reuse() {
int fd1 = open("file1.txt", O_RDONLY);
int fd2 = open("file2.txt", O_RDONLY);
// ... operations ...
close(fd1); // Close fd1
// fd1's number might be reused for new file
int fd3 = open("file3.txt", O_RDONLY); // May get same fd as fd1
// Mistakenly close what we think is fd1 again
close(fd1); // VULNERABLE: Actually closes fd3!
}
// Vulnerable: Java resource handling
import java.io.*;
import java.sql.*;
public class VulnerableResources {
// VULNERABLE: Double close on stream
public void vulnerableStreamHandling(String filename) {
FileInputStream fis = null;
BufferedInputStream bis = null;
try {
fis = new FileInputStream(filename);
bis = new BufferedInputStream(fis);
// ... read data ...
bis.close(); // Closes both bis and fis
fis.close(); // VULNERABLE: fis already closed by bis
} catch (IOException e) {
try {
if (bis != null) bis.close(); // May throw if already closed
if (fis != null) fis.close(); // Double close
} catch (IOException ex) {
// Suppressed
}
}
}
// VULNERABLE: Database connection double close
public void vulnerableDbHandling() throws SQLException {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
conn = DriverManager.getConnection("jdbc:...");
stmt = conn.createStatement();
rs = stmt.executeQuery("SELECT * FROM users");
// ... process results ...
} finally {
// VULNERABLE: May close multiple times on exception
if (rs != null) rs.close();
if (stmt != null) stmt.close(); // Also closes rs
if (conn != null) conn.close(); // Also closes stmt
// Later cleanup attempt
if (rs != null) rs.close(); // VULNERABLE: Double close
}
}
}
# Vulnerable: Python resource handling
# VULNERABLE: Double close on file
def vulnerable_file_handling(filename):
f = open(filename, 'r')
try:
data = f.read()
f.close() # First close
# ... process data ...
finally:
f.close() # VULNERABLE: Double close
# May raise ValueError: I/O operation on closed file
# VULNERABLE: Thread-unsafe resource release
import threading
class VulnerableResource:
def __init__(self):
self.handle = allocate_resource()
self.released = False
def release(self):
# VULNERABLE: Race condition allows double release
if not self.released:
# Thread A and B both pass this check
free_resource(self.handle) # Both threads free
self.released = True
# VULNERABLE: Exception handling double release
def vulnerable_multi_resource():
res1 = None
res2 = None
try:
res1 = acquire_resource1()
res2 = acquire_resource2() # May throw
# ... use resources ...
except Exception as e:
if res1:
release_resource(res1) # Release in exception
if res2:
release_resource(res2)
raise
finally:
# VULNERABLE: Also releases in finally
if res1:
release_resource(res1) # Double release if exception
if res2:
release_resource(res2)
Fixed Code
// Fixed: Safe resource handling in C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// FIXED: Single close with NULL check
void safe_file_handling(const char* filename) {
char buffer[1024];
FILE *f = fopen(filename, "r");
if (f) {
fread(buffer, 1, sizeof(buffer) - 1, f);
int r = fclose(f);
f = NULL; // FIXED: Set to NULL after close
// ... some processing ...
// FIXED: Check before close
if (f != NULL) {
fclose(f);
f = NULL;
}
}
}
// FIXED: Memory handling with NULL pattern
void safe_memory_handling(size_t size) {
char* ptr = (char*)malloc(size);
if (ptr == NULL) {
return;
}
// ... use ptr ...
if (some_error_condition) {
free(ptr);
ptr = NULL; // FIXED: Set to NULL after free
}
// ... more processing ...
// FIXED: Check before free
if (ptr != NULL) {
free(ptr);
ptr = NULL;
}
}
// FIXED: Using flag-based tracking
typedef struct {
FILE* file;
int is_open;
} safe_file_t;
void safe_file_open(safe_file_t* sf, const char* filename, const char* mode) {
sf->file = fopen(filename, mode);
sf->is_open = (sf->file != NULL) ? 1 : 0;
}
void safe_file_close(safe_file_t* sf) {
// FIXED: Check flag before closing
if (sf->is_open && sf->file != NULL) {
fclose(sf->file);
sf->file = NULL;
sf->is_open = 0;
}
}
// FIXED: Socket handling with single close point
int safe_socket_handling(int port) {
int sock = -1;
int result = -1;
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
goto cleanup;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
goto cleanup;
}
if (listen(sock, 5) < 0) {
goto cleanup;
}
// ... accept connections ...
result = 0;
cleanup:
// FIXED: Single close point
if (sock >= 0) {
close(sock);
sock = -1;
}
return result;
}
// FIXED: RAII-style wrapper in C
typedef struct {
int fd;
int valid;
} safe_fd_t;
safe_fd_t safe_fd_open(const char* path, int flags) {
safe_fd_t sfd;
sfd.fd = open(path, flags);
sfd.valid = (sfd.fd >= 0);
return sfd;
}
void safe_fd_close(safe_fd_t* sfd) {
if (sfd->valid && sfd->fd >= 0) {
close(sfd->fd);
sfd->fd = -1;
sfd->valid = 0;
}
}
// Fixed: Safe resource handling in Java
import java.io.*;
import java.sql.*;
public class SafeResources {
// FIXED: Use try-with-resources
public void safeStreamHandling(String filename) {
// FIXED: Automatic resource management
try (FileInputStream fis = new FileInputStream(filename);
BufferedInputStream bis = new BufferedInputStream(fis)) {
// ... read data ...
} catch (IOException e) {
// Handle exception - resources auto-closed
e.printStackTrace();
}
// No manual close needed - guaranteed single close
}
// FIXED: Database connection with try-with-resources
public void safeDbHandling() {
// FIXED: Automatic resource management
try (Connection conn = DriverManager.getConnection("jdbc:...");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users")) {
// ... process results ...
} catch (SQLException e) {
e.printStackTrace();
}
// Resources closed in reverse order, exactly once
}
// FIXED: Custom AutoCloseable with state tracking
public class SafeResource implements AutoCloseable {
private Object handle;
private boolean closed = false;
public SafeResource() {
this.handle = acquireResource();
}
@Override
public synchronized void close() {
// FIXED: Thread-safe single close
if (!closed && handle != null) {
releaseResource(handle);
handle = null;
closed = true;
}
}
public boolean isClosed() {
return closed;
}
}
}
# Fixed: Safe resource handling in Python
# FIXED: Using context manager
def safe_file_handling(filename):
# FIXED: Context manager ensures single close
with open(filename, 'r') as f:
data = f.read()
# ... process data ...
# File automatically closed exactly once
# FIXED: Thread-safe resource release
import threading
class SafeResource:
def __init__(self):
self.handle = allocate_resource()
self.released = False
self._lock = threading.Lock()
def release(self):
# FIXED: Thread-safe with lock
with self._lock:
if not self.released:
free_resource(self.handle)
self.handle = None
self.released = True
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.release()
return False
# FIXED: Using context manager for multiple resources
from contextlib import ExitStack
def safe_multi_resource():
# FIXED: ExitStack handles multiple resources safely
with ExitStack() as stack:
res1 = stack.enter_context(acquire_resource1())
res2 = stack.enter_context(acquire_resource2())
# ... use resources ...
# Both resources closed exactly once, even on exception
# FIXED: Custom context manager with state
class ManagedResource:
def __init__(self):
self._handle = None
self._acquired = False
def __enter__(self):
self._handle = acquire_raw_resource()
self._acquired = True
return self
def __exit__(self, exc_type, exc_val, exc_tb):
# FIXED: Only release if acquired
if self._acquired and self._handle is not None:
release_raw_resource(self._handle)
self._handle = None
self._acquired = False
return False
# Usage
def safe_operation():
with ManagedResource() as res:
# ... use res ...
pass
# Guaranteed single release
CVE Examples
- CVE-2019-5018: SQLite3 window function double-free vulnerability enabling code execution.
- CVE-2018-14618: curl double-free in NTLM authentication code.
- CVE-2017-9047: libxml2 double-free in XML validation.
Related CWEs
- CWE-675: Multiple Operations on Resource in Single-Operation Context (parent)
- CWE-415: Double Free (child)
- CWE-672: Operation on a Resource after Expiration or Release (can follow)
- CWE-399: Resource Management Errors (category)
References
- MITRE Corporation. "CWE-1341: Multiple Releases of Same Resource or Handle." https://cwe.mitre.org/data/definitions/1341.html
- CERT C. "MEM30-C. Do not access freed memory"
- SEI CERT. "Double-Free Vulnerabilities"