Join our Newsletter — 33% off our NHI Course

What causes use of expired file descriptor?

Use of expired file descriptor happens when code keeps using a file descriptor after it has been closed, reassigned, or otherwise become invalid. That can lead to data corruption, unexpected access to the wrong file or socket, or crashes. Defenders should track descriptor lifetimes carefully and avoid reuse after close.

Why expired file descriptor use happens

Use of an expired file descriptor usually points to a lifecycle bug, not a mystery in the file system. The program closes a descriptor, reuses the same numeric slot for something else, or lets the descriptor outlive the object or resource wrapper that was meant to own it. The result is that later code still believes it has a valid handle.

The root cause is often weak ownership tracking. In lower-level code, file descriptors are small integers, so an old value can look valid even after the underlying open file description is gone or repurposed. That makes this class of bug especially easy to hide during testing, because the failure may appear only when the slot is recycled.

This is also why descriptor misuse often shows up alongside broader resource-lifetime mistakes such as double close, use after close, or mismatched cleanup across error paths. A bug may be triggered by an exception, early return, timeout, or callback that closes the descriptor sooner than the caller expects.

What goes wrong when code keeps using a closed descriptor

Once a descriptor is expired, the numeric value no longer guarantees the same target. Code may write to the wrong file, read from an unrelated socket, or act on a completely different resource that inherited the recycled descriptor number. In shared services, that can become data corruption or cross-request confusion.

Failure can also be silent. If the descriptor has been closed and the slot has not yet been reused, the code may only see an error on the next system call. If the slot has been reused, the operation may succeed against the wrong target, which is usually more dangerous than an immediate crash because it can corrupt state before anyone notices.

Descriptor expiration is therefore both a correctness problem and a security problem. A stale handle can break trust in I/O boundaries, confuse logging or audit output, and undermine any code that assumes a descriptor still refers to the same file, pipe, or socket it opened earlier.

How to prevent descriptor lifetime bugs in practice

The safest pattern is to make ownership explicit and keep the close site close to the open site. Use structured cleanup, avoid sharing raw descriptor values across unrelated code paths, and prefer wrappers or abstractions that invalidate the handle immediately after close. That reduces the chance that stale integers survive in memory and get reused accidentally.

When a descriptor is passed between functions or threads, define who owns closure and who may only borrow it. If the codebase allows aliasing, treat every handoff as a lifetime boundary and make invalidation visible in the API design. That is more reliable than trying to remember not to reuse a number after close.

Defensive checks help too: log close events, centralize descriptor allocation where possible, and add assertions or sanitizers in debug builds to catch use after close during development. For code that multiplexes many descriptors, this is especially important because reuse pressure increases as the process opens and closes resources quickly.

Risk and Threat Considerations

Expired descriptor use matters because it can turn an ordinary cleanup mistake into corruption, data leakage, or wrong-destination I/O. In networked services, the same bug can affect confidentiality and integrity if a stale socket or file handle is later reused in a different context.

Failure mechanism: The program retains a descriptor value after close, then another allocation reuses the same integer for a different resource. Later I/O operates on the recycled handle or fails in a way that leaves the original logic out of sync with reality.

Impact: The service may corrupt data, read or write the wrong object, mis-handle a client connection, or crash under load. In security-sensitive code, that can also create unintended access to a file, stream, or session boundary.

Standards & Framework Alignment

This section maps relevant standards and security frameworks to the operational risks and controls described in this guidance.

CIS Controls v8 and NIST SP 800-53 Rev 5 set the governance and control requirements practitioners need to meet.

Framework Control / Reference Relevance
CIS Controls v8 CIS-4 — Secure Configuration of Enterprise Assets and Software Descriptor lifetime bugs are reduced by enforcing secure runtime and cleanup configuration.
Recommendation — Harden runtime handling so invalid handles are not reused or left ambiguous after close.
NIST SP 800-53 Rev 5 SI-10 — Information Input Validation Guarding resource state transitions helps stop invalid descriptor values from being used.
AU-12 — Audit Record Generation Logging close and reuse events helps detect stale descriptor use and unexpected handle recycling.
Recommendation — Validate handle state before every operation that depends on an open descriptor. Record descriptor open and close events so stale-handle misuse can be traced.

Practitioner Guidance

What to verify: Check that every descriptor has a single, clear owner and that close always invalidates the local reference immediately. In review, look for borrowed handles stored in globals, reused after error handling, or passed into callbacks without an explicit lifetime contract.

Common mistake: Treating descriptor numbers as stable identities. They are only temporary references, so any code path that caches them, logs them without context, or compares them long after close should be treated as suspect.

Practitioner takeaway: The key control is not just closing descriptors, it is making stale handles impossible to trust by design, so reuse cannot silently turn an old reference into the wrong resource.