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.

Join the conversation! Your thoughts help the community grow.