opened, never closed

Resource Leaks & Improper Shutdown

Every acquire needs a matching release. Forget it — or skip it on the error path — and file descriptors, connections, and memory pile up until the pool, the heap, or the OS limit runs out. The crash lands far from the leak, long after the request that caused it.

A handle held past its lifetime — file, socket, connection, lock — is shared state nobody released; leaks accumulate until the system starves.

01in the wild

In the wild

Opened, Never Closed

A resource acquired in a loop and never released exhausts a finite pool.

example.py
# SMELL: a connection per iteration, none ever returned
for row in rows:
    conn = pool.acquire()      # leaks: never released
    conn.execute(row)
# ...pool exhausted; the next acquire() blocks forever

# RIGHT: a context manager releases on every path
for row in rows:
    with pool.acquire() as conn:   # returned even on exception
        conn.execute(row)
An acquire with no release drains the pool; once it's empty, every caller hangs. A with-block ties release to scope exit, including exceptions.
// observed
leak: pool drains -> acquire() hangs
with: connection returned every iteration
example.go
// SMELL: response body is a file descriptor -- never closed
resp, _ := http.Get(url)
body, _ := io.ReadAll(resp.Body)   // resp.Body leaks every call
// thousands of requests -> "too many open files"

// RIGHT: defer Close right after the error check
resp, err := http.Get(url)
if err != nil { return err }
defer resp.Body.Close()            // released when the function returns
body, _ := io.ReadAll(resp.Body)
resp.Body holds an open socket/fd; without Close it leaks until the process hits its descriptor limit. defer pins the release to function exit.
// observed
no close: fd leak -> too many open files
defer:    each body closed on return

Cleanup Skipped on the Error Path

An early return or exception jumps over the release, so the happy path closes but the failure path leaks.

example.java
// SMELL: an exception before close() leaks the stream
InputStream in = new FileInputStream(path);
parse(in);                 // throws -> close() never runs
in.close();

// RIGHT: try-with-resources closes on every exit path
try (InputStream in = new FileInputStream(path)) {
    parse(in);             // close() runs even if parse throws
}
A close() after the work only runs when the work succeeds; any throw leaks the handle. try-with-resources guarantees close on normal and exceptional exit.
// observed
manual: parse() throws -> stream leaked
try-with: closed on success and on throw
example.c
/* SMELL: early return leaks the file and the buffer */
int load(const char *path) {
    FILE *f = fopen(path, "r");
    char *buf = malloc(N);
    if (read_header(f) < 0) return -1;   /* leaks f AND buf */
    /* ...use buf... */
    free(buf); fclose(f);
    return 0;
}

/* RIGHT: single cleanup path via goto */
int load(const char *path) {
    int rc = -1;
    FILE *f = fopen(path, "r");
    char *buf = malloc(N);
    if (read_header(f) < 0) goto out;
    rc = 0;
out:
    free(buf); if (f) fclose(f);   /* always runs */
    return rc;
}
Each early return must remember to free everything acquired so far — easy to miss. A single cleanup label centralizes release so no exit path can skip it.
// observed
early return: f and buf leaked on error
goto out:   every exit frees both
02cross-pollination

Where this compounds

03weakness catalog

Mapped weaknesses (CWE)

On its own, this defect is catalogued by MITRE as one or more of these weaknesses. The exploitable vulnerability usually appears only when it chains or combines with another.