Safeguard
Application Security•

CWE-775 Missing Release of File Descriptor or Handle: Fixing Leaks

A file descriptor that is never closed is a small leak with a big effect. See how CWE-775 leads to denial of service and how to close handles on every code path.

Safeguard Security Team
3 min read

CWE-775 describes a program that opens a file descriptor or handle and never releases it after it is no longer needed. Sockets, files, pipes and database connections all count. Each one takes a slot from a limited pool.

Any long-running service that opens resources on behalf of outside callers can be hit by this: web servers, file scanners, upload handlers, background workers.

How it happens

The usual cause is an early return. A function opens a file, starts parsing, finds something wrong and returns. The close call sits at the bottom of the function and never runs.

The MITRE record gives a real example. In CVE-2007-0897, an anti-virus product met a malformed file and returned from a function without closing a file descriptor. The leaked descriptors piled up, and scans began to fail.

Here is the vulnerable pattern in Python:

def scan(path):
    f = open(path, "rb")
    header = f.read(4)
    if header != b"MAGIC":
        return None          # f is never closed on this path
    data = f.read()
    f.close()
    return analyze(data)

Garbage collection may close it eventually in some runtimes, but you cannot count on the timing, and in C there is no safety net at all.

What an attacker gains

The record lists one consequence: denial of service through resource consumption. An attacker who can influence how many resources get allocated can drain the pool and block every other process from getting its own descriptor or handle.

The attacker needs no special access. Sending the same malformed input many times is enough.

How to find it

MITRE notes that static analysis can find some instances by modeling data flow and checking that every path from an open reaches a close. Beyond tools:

  • Watch the open descriptor count of a running service. On Linux, count the entries in the process fd directory over time. A number that only goes up is a leak.
  • Replay bad inputs (truncated files, malformed headers) and see whether the count moves.
  • Search code review for every open, connect or accept, and ask where the matching close lives on the error path.

How to fix it

Tie the lifetime of the resource to a scope so cleanup is automatic.

def scan(path):
    with open(path, "rb") as f:
        if f.read(4) != b"MAGIC":
            return None
        data = f.read()
    return analyze(data)

The same idea exists in other languages: try-with-resources in Java, using in C#, defer file.Close() in Go, RAII wrappers in C++. In plain C, use a single cleanup label that every path jumps to.

Also set sensible limits. Cap concurrent connections, set timeouts, and raise an alert when descriptor use approaches the process limit, so a leak shows up as an alert and not an outage.

Quick check

Why is a leak on an error path worse than one on the normal path?

Because an attacker controls the error path. They can send malformed input on purpose and repeat it until the pool is empty.

What language features prevent this weakness?

Scoped cleanup: with in Python, try-with-resources in Java, using in C#, defer in Go and RAII in C++.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.