CWE-675: Multiple Operations on Resource in Single-Operation Context
Overview
Performing duplicate or redundant operations on resources (multiple calls to free(), close(), lock()) in contexts expecting single operation causes double-free vulnerabilities, resource leaks, deadlocks, and undefined behavior from operating on already-released resources.
Risk
High: Duplicate operations cause double-free exploits (heap corruption), deadlocks (double-locking), resource exhaustion (unclosed handles), crashes (operating on freed memory), and race conditions.
Remediation Strategy
Use RAII/Automatic Resource Management (Primary Defense)
// VULNERABLE - manual resource management
void process() {
Resource* res = acquire_resource();
try {
use_resource(res);
} catch (...) {
release_resource(res); // Released here
throw;
}
release_resource(res); // And here - duplicate!
}
// SECURE - RAII
void process() {
std::unique_ptr<Resource> res(acquire_resource());
use_resource(res.get());
// Automatically released once, no duplicates
}
Track Resource State
// VULNERABLE - no state tracking
void cleanup(Resource *res) {
if (res) {
free(res->data);
free(res); // First free
}
}
// Later in code
cleanup(resource);
// ...
cleanup(resource); // DOUBLE FREE!
// SECURE - track state
void cleanup(Resource **res) {
if (res && *res) {
free((*res)->data);
free(*res);
*res = NULL; // Mark as freed
}
}
// Usage
cleanup(&resource);
cleanup(&resource); // Safe - NULL check prevents double-free
Use Once-Only Patterns
// VULNERABLE - no duplicate prevention
public class ConnectionPool {
public void shutdown() {
closeAllConnections();
// May be called multiple times
}
}
// SECURE - once-only pattern
import java.util.concurrent.atomic.AtomicBoolean;
public class ConnectionPool {
private final AtomicBoolean isShutdown = new AtomicBoolean(false);
public void shutdown() {
if (isShutdown.compareAndSet(false, true)) {
closeAllConnections();
}
// Subsequent calls are no-ops
}
}
Use try-with-resources
// VULNERABLE - manual close
public void processFile(String path) throws IOException {
FileInputStream fis = new FileInputStream(path);
try {
process(fis);
fis.close(); // Closed here
} catch (IOException e) {
fis.close(); // And here - duplicate!
throw e;
}
fis.close(); // And possibly here - triple close!
}
// SECURE - try-with-resources
public void processFile(String path) throws IOException {
try (FileInputStream fis = new FileInputStream(path)) {
process(fis);
}
// Automatically closed once
}
Remediation Steps
Core principle: Avoid unsafe function use patterns; prefer safe wrappers and centralized, reviewed security helpers.
- Identify duplicate operations
- Trace resource lifecycle
- Find duplicate release paths
- Use automatic resource management: Implement RAII (C++), try-with-resources (Java), context managers (Python), defer (Go)
- Add state tracking: Set pointers to NULL after free, use flags to track resource state, implement once-only patterns
- Test error paths: Force exceptions and errors to ensure resources are released exactly once, not zero or multiple times
- Review cleanup code: Audit all resource cleanup paths to ensure single release regardless of execution path
Common Duplicate Operation Patterns
Double Free:
char *buf = malloc(100);
free(buf);
// ... more code ...
free(buf); // DOUBLE FREE - heap corruption
Double Close:
file = open('data.txt')
file.close()
# ... more code ...
file.close() // Error: I/O operation on closed file
Double Lock:
Multiple Releases:
std::unique_ptr<Object> ptr(new Object());
ptr.release(); // Release ownership
delete ptr.get(); // Manual delete - DOUBLE FREE!
Secure Resource Management
Python Context Managers:
class Resource:
def __init__(self):
self.acquired = False
def __enter__(self):
if self.acquired:
raise RuntimeError("Already acquired")
self.acquire()
self.acquired = True
return self
def __exit__(self, *args):
if self.acquired:
self.release()
self.acquired = False
with Resource() as res:
use(res)
# Automatically released once
C++ Smart Pointers:
// Unique ownership - prevents double-free
std::unique_ptr<Object> obj(new Object());
// Automatically deleted when out of scope
// Shared ownership - reference counted
std::shared_ptr<Object> obj = std::make_shared<Object>();
// Deleted when last reference goes away
Java AutoCloseable:
public class DatabaseConnection implements AutoCloseable {
private boolean closed = false;
@Override
public void close() {
if (!closed) {
actuallyClose();
closed = true;
}
}
}
try (DatabaseConnection conn = new DatabaseConnection()) {
useConnection(conn);
}
// Safely closed once
Go defer:
func processFile(path string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close() // Called once at function exit
return process(file)
}