Join our Newsletter — 33% off our NHI Course

How can developers prevent use of an expired file descriptor?

Developers prevent use of an expired file descriptor by closing it once, then ensuring no later code keeps or reuses that numeric handle. Track ownership carefully, reset variables after close, and avoid passing descriptors across threads without coordination. In long-running services, descriptor reuse can cause reads or writes against the wrong file, socket, or process channel.

Why a File Descriptor Becomes Dangerous After Close

A file descriptor is only valid while the process still owns that open resource. Once it is closed, the operating system can recycle the same numeric value for a different file, socket, or pipe. The real hazard is not the number itself, but stale code paths that keep treating that number as if it still names the original object.

That is why use-after-close bugs often look harmless in testing and then become unpredictable in long-running services. A reused descriptor can point at a completely different channel, so a read, write, or flush may affect the wrong target without an obvious crash.

Developers should treat descriptor lifetime as a strict ownership problem. Close it once, invalidate every local reference, and make the code path that releases it the only place that can release it.

Where Descriptor Reuse Usually Creeps In

The most common failure mode is duplicated ownership in code that passes integers around as if they were plain values. A helper, callback, or background task may store the descriptor after the original owner has already closed it. Concurrency makes this worse, because one thread can close a descriptor while another thread still assumes it is live.

Another common source is error handling. Cleanup logic that runs on both success and failure can close the same handle twice, or close it in one branch and then continue as if the handle were still available. In large codebases, this is often coupled with stale struct fields, cached globals, or borrowed references that are never cleared after release.

For teams that want a broader view of how these lifetime mistakes show up in practice, the CWE Top 25 Guide is useful because it places use-after-free style weaknesses, race conditions, and premature resource release in the wider weakness landscape, not just as isolated bugs.

How to Design Code So the Handle Cannot Be Reused Accidentally

The safest pattern is to make descriptor ownership explicit. Keep the handle in one owner object or one scope, close it in one place, and clear the stored value immediately after close so later code cannot mistake it for a valid handle. If a function only needs temporary access, pass it by convention as borrowed and never let it outlive the owning scope.

For shared code paths, protect descriptor state with coordination that matches the concurrency model. If multiple threads, workers, or callbacks can observe the same handle, the close operation needs a clear handoff rule so one actor cannot close while another is still using it. This is especially important in event-driven servers, where descriptors may stay in caches, queues, or watcher tables long after the original operation completed.

Teams that manage many open handles should pair this with lifecycle discipline: track creation, ownership, transfer, and release. NHIMG’s NHI Lifecycle Management Guide is about non-human identities, but the lifecycle discipline it describes maps well to resource ownership, because the same control problem exists, who owns it, when it is valid, and when it must be retired.

When you are reviewing code, the key question is whether any later path can still see a closed descriptor and treat it as usable. If the answer is yes, the fix is not just a second close guard, it is eliminating stale references and making invalidation immediate and visible in the code structure.

Risk and Threat Considerations

Expired descriptor bugs are risky because reuse can silently redirect I/O to the wrong resource. In a server, that can corrupt responses, cross request boundaries, or leak data between sessions without producing an obvious error. The defect is especially dangerous when the descriptor is reused across asynchronous work, pooled workers, or long-lived processes.

Failure mechanism: The application closes a descriptor but leaves one or more references alive, then later code reuses the numeric handle after the operating system has assigned it to a different open object.

Impact: Reads and writes may hit the wrong file, socket, or process channel, creating data integrity problems, cross-connection contamination, or unintended access to unrelated resources.

Standards & Framework Alignment

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

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

Framework Control / Reference Relevance
CIS Controls v8 CIS-3 — Data Protection File descriptor reuse can expose or corrupt data flows that need protected handling.
Recommendation — Protect sensitive data paths and verify controls that prevent stale handle reuse from crossing data boundaries.
NIST SP 800-53 Rev 5 AC-6 — Least Privilege Limiting which code paths can hold or reuse a descriptor reduces misuse after close.
SI-11 — Error Handling Robust error handling helps prevent cleanup paths from reusing closed resources.
Recommendation — Restrict descriptor access to the smallest set of components that truly need it. Ensure failure paths invalidate closed descriptors and stop later code from acting on them.
OWASP ASVS V15 — Secure Coding and Architecture The issue is a secure coding lifetime bug that requires disciplined resource ownership.
V16 — Security Logging and Error Handling Logging and error handling can help detect invalid handle use and cleanup mistakes.
Recommendation — Design resource ownership so closed handles cannot be reused by later code. Log descriptor lifecycle faults and fail closed when stale handles are detected.
NIST CSF 2.0 PR.PS-02 — Software, Data, and Hardware Assets are Protected The bug is a software asset protection issue involving correct resource lifetime handling.
Recommendation — Apply resource lifecycle controls that prevent stale descriptors from being reused.

Practitioner Guidance

What to verify: Check that every close path also clears or invalidates the stored descriptor value, and confirm that no callback, queue item, or worker can retain an old handle after ownership transfers.

Common mistake: Treating the descriptor as a harmless integer instead of a stateful capability. Once the close happens, any later use should fail fast in code review, testing, or runtime checks rather than depending on memory of the original ownership.

What good looks like: Each descriptor has a single clear owner, close occurs once, stale copies are removed immediately, and concurrent code has an explicit handoff rule for when a handle may no longer be used.

Practitioner takeaway: Preventing expired descriptor use is mostly about lifecycle discipline, not special syntax. If your code can still “remember” a closed handle, it can still misuse it.