Skip to content

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.

  1. Identify duplicate operations
  2. Trace resource lifecycle
  3. Find duplicate release paths
  4. Use automatic resource management: Implement RAII (C++), try-with-resources (Java), context managers (Python), defer (Go)
  5. Add state tracking: Set pointers to NULL after free, use flags to track resource state, implement once-only patterns
  6. Test error paths: Force exceptions and errors to ensure resources are released exactly once, not zero or multiple times
  7. 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:

synchronized(lock) {
    // ...
    synchronized(lock) {  // DEADLOCK!
        // ...
    }
}

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)
}

Additional Resources