Skip to content

CWE-675: Multiple Operations on Resource in Single-Operation Context

Overview

This weakness occurs when code performs the same operation on a resource two or more times in a context that expects the operation exactly once - releasing a lock, closing a handle, or freeing memory more than once. The second operation acts on a resource that is already gone or already held.

How badly that ends is decided by the resource, not by the language, and the range is wider than "undefined behavior". A double free() in C corrupts the allocator and is undefined. A raw OS handle gives a defined error - close() on a released descriptor returns EBADF - whose danger is not the error but the descriptor number being handed back out to the next open(), so the duplicate close lands on somebody else's file. At the other end, most managed close/dispose methods are contractually idempotent: measured on JDK 26, .NET 10 and CPython 3.13, calling close() or Dispose() twice on a file stream is a silent no-op. Which of the three you have decides whether this is a latent bug or memory corruption.

Relationship to Other CWEs

CWE-675 is a child of CWE-573 (Improper Following of Specification by Caller), not of the resource-lifetime pillar CWE-664 where CWE-404 and CWE-665 sit - MITRE classes it as a caller who ignored an API's "call this once" contract rather than as a resource managed wrongly across its lifetime. Being a Class, it is Allowed with careful review rather than allowed outright; where a finding matches one of its children, that number is better:

  • CWE-764 (Multiple Locks of a Critical Resource)
  • CWE-765 (Multiple Unlocks of a Critical Resource)
  • CWE-1341 (Multiple Releases of Same Resource or Handle)
  • CWE-605 (Multiple Binds to the Same Port)
  • CWE-174 (Double Decoding of the Same Data)

The memory case has a page here: CWE-415 (Double Free) sits under CWE-1341 and is the number for a duplicated free(). Use this page for the general one-operation-once contract, and for locks and handles, which have no dedicated page.

OWASP Classification

A06:2025 - Insecure Design

Risk

High: A duplicate free can corrupt the heap allocator's metadata, which attackers can exploit for arbitrary code execution. A duplicate lock on a non-reentrant mutex deadlocks the process. A duplicate close or release can close a handle whose number has since been reassigned, shutting a file that another part of the program has just opened.

Remediation Steps

Core Principle: Ensure exactly one code path is responsible for releasing or finalizing a resource, and make repeat calls safe no-ops rather than relying on callers never invoking cleanup twice.

Trace the Data Path

  • Source: The point where a resource is acquired - a lock, a memory allocation, a file or socket handle, a database connection.
  • Sink: Every code path that releases the resource - normal completion, error handlers, exception/catch blocks, destructors, and any explicit cleanup calls.
  • Missing control: No single owner for the release, and no tracking of whether the resource has already been released, so more than one path performs the release operation.

Use Scope-Bound or Automatic Resource Management (Primary Defense)

Prefer a language construct that ties release to scope exit and runs exactly once, instead of manual release calls scattered across normal and error paths:

// SECURE - pseudo-code
acquire_resource_in_scope(res)
use(res)
// resource is released automatically and exactly once when the scope ends,
// on both the normal path and any error/exception path

This removes the extra release path rather than the possibility of a second call: a close() written inside the block still runs, and the construct then calls close() again on the way out. Measured, that is harmless: JDK 26's try-with-resources over an explicitly-closed BufferedReader and .NET 10's using over an explicitly-disposed FileStream both complete without error. That holds only because those methods are idempotent by contract. For a release that is not, the scope-bound construct is the single owner and nothing else may call it.

Track Resource State When Automatic Management Isn't Available

Where the language or API doesn't offer scope-bound release, make the release function idempotent: check and update an explicit "already released" marker as a single atomic step, so a second call is a safe no-op instead of a duplicate operation.

// SECURE - pseudo-code
function release(resource):
    if resource.state != RELEASED:
        do_release(resource)
        resource.state = RELEASED
    // second call sees RELEASED and does nothing

Add Least Privilege / Additional Hardening

  • Give each resource one owner responsible for its release, and do not pass ownership through multiple layers without an explicit handoff.
  • In concurrent code, guard the release-state check and the release itself with the same synchronization used to guard the resource, so two threads cannot both observe "not yet released" and both release it.

Test with Malicious Inputs

  • Force every error and exception path to execute and confirm the resource is released exactly once, not zero or multiple times. A memory sanitizer such as AddressSanitizer catches double frees, and a mutex implementation that errors on non-reentrant re-lock catches double locks.
  • Call the cleanup/close/release function twice in a row deliberately and confirm the second call is a safe no-op rather than a crash or corruption.
  • Re-scan with the security or static analysis tool to confirm the finding is resolved.

Common Vulnerable Patterns

// VULNERABLE - pseudo-code
resource = acquire()
try:
    use(resource)
    release(resource)          // released here, on what the author thinks is the last line
    report(use.result)         // ...but this can throw too, and now the resource is gone
except error:
    release(resource)          // released again - duplicate
    raise

Why this is vulnerable: the release on the success path is correct, the release on the error path is correct, and the handler cannot tell which of them has already run. If use throws, only the handler releases and the code is fine - which is the version that gets tested. If anything after the release throws, both run, and the second acts on a handle that no longer refers to what the variable says it does.

That asymmetry is the reason this survives review. The duplicate needs a throw from the narrow window between the release and the end of the block, so the obvious test - make use fail - exercises the safe path and passes. Any line added to the try block after the release widens the window, and nothing about adding a line looks like touching resource management.

What that costs depends on the resource, and none of the outcomes are visible where the bug is. A freed memory block corrupts the allocator's bookkeeping (CWE-415). A file descriptor number is reused as soon as it is closed, so a second close can shut a descriptor another thread has just opened for something unrelated - the failure then appears in code that is entirely correct, at a moment unconnected to this function. A lock released twice can leave it held by nobody while a second holder believes it owns it.

The structural fix is one place responsible for the release rather than one per exit path - a finally, a scope-bound construct, or a flag the release checks and clears. Duplicating the call in each branch is what creates the window, and every later edit to either branch changes how wide it is without anyone deciding to.

Secure Patterns

// SECURE - pseudo-code
resource = acquire_in_scope()  // scope-bound: RAII, try-with-resources, context manager, defer
use(resource)
// released automatically exactly once, regardless of which path exits the scope

Why this works: Tying release to scope exit means there is exactly one mechanism - the runtime's scope-exit hook - that can trigger the release, so there is no second code path left to duplicate it. Where scope-bound release isn't available, the state-checked release above gets the same result: every call after the first sees the released state and does nothing.

Additional Resources