Introduction
As .NET developers, we are taught to write defensive code. We validate our inputs, we check for nulls, and when dealing with the file system, we make sure a file is safe before we execute it.
A common pattern looks like this:
Check if the file exists.
Read the file to verify its signature or compute its hash.
If the hash is safe, read the file again to process its contents.
It looks logical. It looks secure. It is actually a critical vulnerability.
The key issue is not timing — it is design. If the same file is accessed multiple times without guarantees of consistency, the system is exposed.
In this article, we are going to dive deep into a system-level flaw known as TOCTOU (Time-of-Check to Time-of-Use) . We will look at why innocent-looking C# code creates a silent vulnerability, how operating system behavior makes it exploitable, and the universal design pattern you can use to permanently fix it.
What is a TOCTOU Race Condition?
TOCTOU (mapped as CWE-367) is a race condition that occurs when a system validates a shared resource (like a file) but later uses it without ensuring it has not changed.
Because the file system is a shared, mutable environment, an attacker can modify the file in the narrow execution gap between the check and the use. The application ultimately executes a malicious file, falsely believing it is safe because the previous check passed.
The Vulnerable C# Pattern
Let's look at a real-world example. Imagine a server that processes XML documents. It first computes a SHA256 hash to verify the file hasn't been tampered with, and then it reads the text.
C#
public void ProcessSecureFile(string path)
{
// 1. T1 (Time of Check): Verify the file exists
if (!File.Exists(path))
{
Console.WriteLine("File not found.");
return;
}
// 2. T1.5: Read the file into memory to verify it
byte[] data = File.ReadAllBytes(path);
// 3. The Gap: CPU processes the data
using (var sha = SHA256.Create())
{
byte[] hash = sha.ComputeHash(data);
if (!IsHashSafe(hash)) return;
}
// 4. T2 (Time of Use): The Fatal Flaw
string content = File.ReadAllText(path);
ExecuteContent(content);
}
The Architectural Flaw (Where the Gap Occurs)
The flaw lies in the transition between Step 3 and Step 4.
ReadAllBytes and ReadAllText are two separate operations.
Each operation independently accesses the file system.
Between these operations, the file is no longer under control.
The application is trusting a reference (the string path) to remain stable in a mutable environment.
How Attackers Exploit This (Why This Happens in Real Systems)
Most developers struggle to reproduce TOCTOU locally because modern systems are optimized:
Files are cached in memory.
Disk reads can complete extremely quickly.
The gap becomes difficult to observe.
However, under realistic conditions, the gap becomes observable. Attackers take advantage of system behavior:
Cache-Busting: Using unique filenames to avoid OS caching.
I/O Amplification: Increasing file size to introduce measurable read latency.
State Observation: Monitoring when the file is in use versus when it becomes available.
The Critical Moment
At the transition point when the server finishes reading and begins processing the data, the file is no longer locked by the system. This creates a window where the file can be modified before it is used again.
Execution Timeline (Mapped to Telemetry)
Based on the telemetry proof below, we can map the exact microsecond timeline of the vulnerability:
0ms [Check] → Server validates file existence (T1).
405ms [Boundary] → Physical SSD read File.ReadAllBytes() completes. The OS file handle is released.
406ms [Swap] → Attacker exception loop detects the dropped lock. The file is wiped and modified.
672ms [Use] → CPU hashing finishes (267ms later). Server blindly re-reads the file File.ReadAllText() and executes the poisoned content.
⏱️ A Note on Timing Variability: The exact millisecond durations shown in the telemetry are not fixed. They fluctuate wildly based on your NVMe/SSD read speeds, current CPU load, and OS thread scheduling. However, the exploit is deterministic because it does not guess time. The attacker's polling loop simply waits for the OS to release the I/O lock, executing the swap regardless of whether the read phase takes 40ms or 4000ms.
![Phase-04-POC]()
Figure 1: The Deterministic Exploit in Action
The telemetry above demonstrates the successful hijack of the execution window. By monitoring the OS file lock via forced exceptions, the attacker script perfectly aligns the payload swap during the sub-second gap before the server's final read operation.
The Universal Fix
Acquire → Check → Use
To eliminate TOCTOU vulnerabilities, we must stop relying on external mutable state.
Never verify a reference. Verify the value.
C#
public void ProcessSecureFile(string path)
{
// 1. ACQUIRE: Read once into a private memory buffer
byte[] data = File.ReadAllBytes(path);
// 2. CHECK: Validate the memory, not the file system
using (var sha = SHA256.Create())
{
byte[] hash = sha.ComputeHash(data);
if (!IsHashSafe(hash)) return;
}
// 3. USE: Operate on the exact same data
string content = Encoding.UTF8.GetString(data);
ExecuteContent(content);
}
Why This Works
Eliminates multiple file reads.
Removes dependency on external mutable state.
Ensures validation and execution operate on the exact same data.
Validation and execution operate on the same in-memory data, permanently eliminating the race condition.
The Invisible Breach
The most dangerous aspect of a TOCTOU vulnerability is its silence.
The system does not crash.
No exceptions are thrown.
Logs indicate normal execution.
The issue occurs because the system assumes consistency between two operations on a mutable resource.
TOCTOU is not about timing precision. It is about exploiting inconsistency between validation and use. These are not theoretical edge cases. They are reproducible vulnerabilities under observable system conditions.
The next time you review code that accesses a file multiple times, remember: Acquire it once. Validate the data. Use the same data.
Explore the Proof-of-Concept
[ Github Code]
Access the complete Phase 04 Lab on GitHub to see the bare-metal architecture in action. This repository contains the deterministic exploit that bypasses Windows RAM caching and exhausts physical SSD I/O to force the TOCTOU execution gap.