A TOCTOU race condition occurs when code checks a resource's state such as file permissions, account balance, or user role and then uses that resource later…
A TOCTOU race condition occurs when code checks a resource's state (such as file permissions, account balance, or user role) and then uses that resource later, without ensuring the state hasn't changed in between. An attacker can modify the resource during that gap, causing the code to make decisions based on outdated information. This is a classic concurrency vulnerability that affects file systems, databases, and any shared resource accessed by multiple processes or threads.
02How It Happens
The vulnerability arises when a program performs a security-relevant check on a resource, then performs an action on that same resource, without atomically locking or protecting both operations together. Between the check and the use, another process or thread can alter the resource's state. For example, code might verify that a file exists and is readable, then open and read it—but an attacker could delete or replace the file between the check and the open. Similarly, a banking application might check an account balance, then deduct funds, but a concurrent withdrawal could reduce the balance below zero if both operations aren't synchronized. The gap is often microseconds, but in high-concurrency environments or on slow storage, it can be exploited reliably.
03Real-World Impact
TOCTOU vulnerabilities can lead to unauthorized file access, privilege escalation, financial fraud, or data corruption. An attacker might replace a temporary file with a symlink to a sensitive system file, causing the application to overwrite it. In financial systems, race conditions on balance checks can allow overdrafts or double-spending. In multi-user systems, an attacker could change their role or permissions between a privilege check and a sensitive operation, bypassing access controls. The impact depends on what resource is checked and what action follows, but the consequences are often severe because the vulnerability is difficult to detect and reproduce.
04Vulnerable & Fixed Patterns
Vulnerable pattern
import os
import time
# Check if file exists and is readable
if os.path.exists('/tmp/config.txt') and os.access('/tmp/config.txt', os.R_OK):
# Attacker can delete or replace the file here
time.sleep(0.1) # Simulates delay
with open('/tmp/config.txt', 'r') as f:
config = f.read()
Why it's vulnerable: The code checks file existence and readability, but an attacker can delete, replace, or modify the file between the check and the open. The file might not exist when opened, or it might be a symlink to a sensitive file.
Fixed pattern
import os
# Attempt to open directly; let the OS handle atomicity
try:
with open('/tmp/config.txt', 'r') as f:
config = f.read()
except FileNotFoundError:
config = None
except PermissionError:
config = None
Vulnerable pattern
<?php
// Check if file exists
if (file_exists('/tmp/config.txt')) {
// Attacker can delete or replace the file here
usleep(100000); // Simulates delay
$config = file_get_contents('/tmp/config.txt');
}
?>
Why it's vulnerable: The code checks file existence separately from reading it. An attacker can delete, replace, or symlink the file between the check and the read operation.
Fixed pattern
<?php
// Attempt to read directly; handle errors if file is unavailable
$config = @file_get_contents('/tmp/config.txt');
if ($config === false) {
// File does not exist, is unreadable, or was deleted
$config = null;
}
?>
05Prevention Checklist
Use atomic operations: Combine check and use into a single system call or transaction whenever possible (e.g., open a file directly rather than checking then opening).
Lock resources during critical sections: Use file locks, database transactions, or mutex primitives to prevent concurrent modification between check and use.
Avoid symlinks in sensitive paths: Disable symlink following in temporary directories, or use secure temporary file creation functions that prevent symlink attacks.
Validate state at use time, not just at check time: Re-verify assumptions immediately before the action, not just before the check.
Use temporary files securely: Create temporary files with restricted permissions and unique names; avoid predictable paths.
Prefer database transactions: For multi-step operations on shared data, wrap them in a single transaction with appropriate isolation levels.
06Signs You May Already Be Affected
Look for unexpected file modifications in temporary directories, log entries showing file operations on paths that should not exist, or reports of data inconsistency in concurrent scenarios (e.g., account balances that don't match expected totals, or files being overwritten unexpectedly). If your application uses predictable temporary file paths or performs security checks in separate operations without synchronization, you may be vulnerable.