Loading content...
Among race conditions, one class stands out for its security implications and practical prevalence: Time-of-Check to Time-of-Use (TOCTOU) vulnerabilities. These races occur when a program checks a condition and later acts on that condition, but the checked state can change between the check and the use.
TOCTOU vulnerabilities are not merely theoretical concerns. They have been exploited to bypass security controls, escalate privileges, corrupt file systems, and compromise systems from the smallest embedded devices to the largest data centers. Understanding TOCTOU is essential for any engineer building secure software, especially systems that interact with file systems, network resources, or shared state.
This page provides a rigorous, comprehensive treatment of TOCTOU vulnerabilities: their structure, manifestations, exploitation techniques, and prevention strategies.
By the end of this page, you will: (1) Define TOCTOU precisely and distinguish it from other race types, (2) Understand the structural pattern that creates TOCTOU vulnerabilities, (3) Analyze classic TOCTOU attack scenarios, (4) Recognize TOCTOU vulnerabilities in code, and (5) Apply defensive techniques to eliminate or mitigate TOCTOU risks.
A Time-of-Check to Time-of-Use (TOCTOU) vulnerability exists when:
The fundamental problem is that the check and use are not atomic—they are separate operations that can be interleaved with other operations.
| Time | Victim Program | Attacker / Concurrent Process |
|---|---|---|
| T1 | Check resource state (e.g., file permissions) | — |
| T2 | Check passes, proceed to use | — |
| T3 | — | Modify resource (swap file, change permissions) |
| T4 | Use resource (open, read, execute) | — |
| T5 | Unexpected outcome: Wrong file accessed, privilege escalation, etc. | — |
The window between T2 and T4 is the vulnerability window. The longer this window and the more predictable the timing, the easier the attack.
TOCTOU vulnerabilities are particularly concerning because:
TOCTOU is formally catalogued as CWE-367 in the Common Weakness Enumeration. It has been the root cause of numerous CVEs (Common Vulnerabilities and Exposures) across operating systems, web servers, and applications. It's listed among the CWE Top 25 Most Dangerous Software Weaknesses.
The most common and historically significant TOCTOU vulnerabilities involve file system operations. Let's examine the classic attack pattern in detail.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546
/* * VULNERABLE CODE: Classic TOCTOU vulnerability * A setuid-root program that writes to a user-specified file */ #include <stdio.h>#include <unistd.h>#include <sys/stat.h>#include <fcntl.h> int write_log(const char *filename) { struct stat st; // TIME OF CHECK: Verify the file is owned by the calling user // and is a regular file (not a symlink to /etc/passwd) if (lstat(filename, &st) != 0) { return -1; } if (st.st_uid != getuid()) { fprintf(stderr, "Error: You don't own this file"); return -1; } if (!S_ISREG(st.st_mode)) { fprintf(stderr, "Error: Not a regular file"); return -1; } /* VULNERABILITY WINDOW BEGINS HERE */ /* Attacker can replace the file with a symlink during this gap! */ // TIME OF USE: Open and write to the file int fd = open(filename, O_WRONLY | O_APPEND); if (fd == -1) { return -1; } write(fd, "Log entry", 10); close(fd); return 0;}An attacker runs this simultaneously with the vulnerable program:
#!/bin/bash
# Attacker script - runs while vulnerable program is executing
# Create a file the attacker owns
touch /tmp/mylogfile
# Start a loop that rapidly swaps the file with a symlink
while true; do
# Replace the file with a symlink to /etc/passwd
rm /tmp/mylogfile
ln -s /etc/passwd /tmp/mylogfile
# Change back to normal file
rm /tmp/mylogfile
touch /tmp/mylogfile
done &
# Invoke the vulnerable setuid program repeatedly
while true; do
/path/to/vulnerable_program /tmp/mylogfile
done
The attack works as follows:
/tmp/mylogfile (a regular file they own)lstat() - sees a regular file owned by attackerln -s /etc/passwd /tmp/mylogfileopen() - follows symlink, opens /etc/passwd/etc/passwd with root privilegesThe symlink race can be won surprisingly often—on a typical system, with enough iterations, the attacker wins within seconds.
This exact vulnerability pattern has been found in real setuid programs, mail systems, temporary file handlers, and web servers. The classic /tmp symlink attacks of the 1990s and 2000s exploited exactly this structure. Many of these led to immediate root compromise of Unix systems.
The symlink race is the quintessential TOCTOU attack, warranting deeper examination of its mechanics and variations.
Symbolic links (symlinks) are ideal for TOCTOU exploitation because:
stat() follows symlinks, making checks oblivious to the redirection. Even lstat() can be defeated with careful timing./tmp, home directories, and web upload directories are natural attack venues.Attackers employ various techniques to increase the probability of winning the race:
CPU Contention: Spawn many CPU-intensive processes to slow down the victim program's execution between check and use.
Priority Manipulation: Use nice or real-time scheduling to give the attacker's swap process higher priority.
SIGSTOP Attack: If the attacker can signal the victim process (same user), they can pause it during the vulnerability window:
kill(victim_pid, SIGSTOP); // Pause victim mid-execution
rm_symlink_and_replace(); // Swap the file
kill(victim_pid, SIGCONT); // Resume victim
File System Latency: Exploit network file systems (NFS, SMB) where operations are slower, creating longer windows.
The vulnerability window duration depends on what happens between check and use. Adding logging, I/O operations, or complex logic between check and use widens the window significantly.
123456789101112
// EXTREMELY VULNERABLE: Widened window if (access(filename, W_OK) == 0) { // Check // These operations widen the vulnerability window: log_access_attempt(filename); // Database write notify_audit_system(filename); // Network call check_quota_limits(user); // Complex computation fd = open(filename, O_WRONLY); // Use (much later) ...}Understanding how attackers widen windows helps defenders shrink them. Minimize operations between check and use. Better yet, eliminate the check-then-use pattern entirely by using operations that are atomic or that derive the checked property from the same operation as the use.
While file system TOCTOU is most common, the pattern appears in many other contexts. Recognizing these variations is essential for comprehensive security.
1234567891011121314151617181920212223
# VULNERABLE: Database TOCTOU in a ticket booking system def book_ticket(user_id, event_id): # TIME OF CHECK: Query available seats result = db.query( "SELECT available_seats FROM events WHERE id = ?", event_id ) if result.available_seats > 0: # VULNERABILITY WINDOW: Another request could book the last seat # TIME OF USE: Perform the booking db.execute( "UPDATE events SET available_seats = available_seats - 1 " "WHERE id = ?", event_id ) db.execute( "INSERT INTO bookings (user_id, event_id) VALUES (?, ?)", user_id, event_id ) # Result: Possible overbooking if concurrent requests1234567891011121314
# VULNERABLE: API TOCTOU in authorization def transfer_funds(auth_token, from_account, to_account, amount): # TIME OF CHECK: Verify user can access from_account user = validate_token(auth_token) if not can_access_account(user, from_account): raise PermissionError("Access denied") # VULNERABILITY WINDOW # Admin could revoke access between check and use # Account ownership could change # TIME OF USE: Perform the transfer execute_transfer(from_account, to_account, amount)A particularly dangerous variant occurs in operating system kernels when copying data from user space:
1234567891011121314151617181920212223
// VULNERABLE: Kernel double-fetch int syscall_handler(struct user_request __user *req) { size_t size; // First fetch: Check size for sanity if (copy_from_user(&size, &req->size, sizeof(size))) return -EFAULT; if (size > MAX_SIZE) return -EINVAL; // Reject oversized requests // VULNERABILITY WINDOW: User-space can change req->size // Second fetch: Read the actual data // Uses req->size again - assumes it's still valid! char *buffer = kmalloc(size, GFP_KERNEL); if (copy_from_user(buffer, req->data, req->size)) // BUG! ... // If attacker changed req->size between checks, // we may read more than allocated: kernel heap overflow}Double-fetch vulnerabilities in kernels typically lead to arbitrary kernel code execution and complete system compromise. They've been found in Linux, Windows, and macOS kernels. The attacker uses a second thread to modify the user-space memory while the kernel is between fetches.
Eliminating TOCTOU vulnerabilities requires understanding the fundamental problem: non-atomic check-then-use. Prevention strategies fall into several categories.
The ideal fix is to use operations that check and use atomically:
123456789101112
-- SECURE: Atomic database operation -- Instead of: SELECT, then UPDATE-- Use: UPDATE with condition UPDATE events SET available_seats = available_seats - 1 WHERE id = :event_id AND available_seats > 0; -- Check rows affected: if 0, no seat was available-- The check and update are atomic within one statementFor file operations, open the file first, then check properties of the open file descriptor:
12345678910111213141516171819202122232425262728
// SECURE: Check after open using file descriptor int secure_write_log(const char *filename) { // Open with restrictive flags int fd = open(filename, O_WRONLY | O_APPEND | O_NOFOLLOW); if (fd == -1) { return -1; // O_NOFOLLOW rejects symlinks } // Now check properties of the OPEN file, not the path struct stat st; if (fstat(fd, &st) != 0) { close(fd); return -1; } // Check ownership and type of the actual opened file if (st.st_uid != getuid() || !S_ISREG(st.st_mode)) { close(fd); return -1; } // Safe: we know exactly what file we have open write(fd, "Log entry", 10); close(fd); return 0;}12345678910111213141516171819202122
// SECURE: Use directory file descriptors and *at() functions int secure_operations(const char *directory, const char *filename) { // Open the directory - establishes a fixed reference point int dirfd = open(directory, O_RDONLY | O_DIRECTORY); if (dirfd == -1) return -1; // Use openat() - operates relative to dirfd // Even if an attacker manipulates the path, dirfd is fixed int fd = openat(dirfd, filename, O_WRONLY | O_CREAT | O_NOFOLLOW, 0600); // Use fchown(), fchmod() on the fd - not path-based versions fchown(fd, getuid(), getgid()); fchmod(fd, 0600); // Operations happen on the fd we opened, not resolvable paths write(fd, "data", 4); close(fd); close(dirfd); return 0;}Once you have a file descriptor (or any handle to a resource), you've established a fixed binding. Subsequent operations on the fd are not subject to path manipulation. This is why 'open first, then check the fd' is the fundamental mitigation pattern for file system TOCTOU.
Operating system developers have implemented various mitigations to reduce TOCTOU attack surfaces, even when applications have vulnerabilities.
Linux introduced protected symlinks (kernel.protected_symlinks sysctl):
This prevents the classic /tmp symlink attack even against vulnerable programs.
Linux 5.6 introduced openat2() with powerful flags for safe path resolution:
12345678910111213
#include <linux/openat2.h> int secure_open_modern(int dirfd, const char *path) { struct open_how how = { .flags = O_RDONLY, .resolve = RESOLVE_NO_SYMLINKS // Reject any symlinks in path | RESOLVE_BENEATH // Don't escape starting directory | RESOLVE_NO_MAGICLINKS // Reject /proc/self/fd/* etc. | RESOLVE_NO_XDEV, // Don't cross mount points }; return syscall(SYS_openat2, dirfd, path, &how, sizeof(how));}Systems like SELinux and AppArmor can confine TOCTOU damage:
These defenses provide defense in depth—even if the application has a TOCTOU vulnerability, system-level protections limit exploitability.
OS-level protections are mitigations, not complete solutions. Applications should still be designed to avoid TOCTOU patterns. Kernel protections may not exist on all systems, may be disabled, or may not cover all attack variants. Relying on application-level correctness is essential.
Recognizing TOCTOU vulnerabilities during code review is a critical skill. Here are patterns and red flags to watch for.
| Check API | Use API | Safe Alternative |
|---|---|---|
access(path, W_OK) | open(path, O_WRONLY) | Open, then fstat() the fd |
stat(path, &st) | open(path, ...) | Open first with O_NOFOLLOW, fstat() fd |
lstat(path, &st) | open(path, ...) | Use openat() with RESOLVE flags |
realpath(path, buf) | open(buf, ...) | Avoid realpath; use fd-based operations |
readdir(dir) | unlink(entry->name) | Use unlinkat() with directory fd |
| SELECT ... WHERE | UPDATE / INSERT | Single atomic UPDATE with WHERE clause |
authorization_check() | perform_action() | Re-check at action time or use capabilities |
if (can_access(...)) { actually_access(...); } patterns across separate operations.If answers are Yes, Yes, Yes, No (or uncertain)—you likely have a TOCTOU vulnerability.
Static analysis tools can detect some TOCTOU patterns. Coverity, CodeQL, and specialized tools like MOPS identify dangerous API sequences. However, semantic TOCTOU (like business logic races) often requires manual review. Combine automated scanning with human expertise.
Examining real vulnerabilities illuminates how TOCTOU appears in production systems and the consequences of exploitation.
The Intel e1000e network driver had a double-fetch vulnerability. User-space could pass a structure specifying buffer sizes. The driver copied the size, validated it, but then used the original (user-space) size for a subsequent copy—allowing heap buffer overflow.
Impact: Arbitrary kernel code execution, complete system compromise Fix: Copy all user data to kernel memory once; never re-read from user-space
Nagios XI (monitoring software) had a local privilege escalation via TOCTOU. A setuid helper script checked file ownership, then performed operations on the file. An attacker could replace the file with a symlink between check and operation.
Impact: Local users could gain root access on monitoring servers Fix: Use file descriptors instead of paths; apply O_NOFOLLOW
While technically a heap overflow, this famous sudo vulnerability exploited a check-then-use pattern in argument parsing. The vulnerability existed for nearly 10 years before discovery.
Impact: Any local user could become root on Linux, macOS, BSD Lesson: TOCTOU-like patterns in security-critical parsing code are high-value targets
TOCTOU vulnerabilities routinely receive high CVSS scores (7.0+) because they often lead to privilege escalation. A seemingly minor race condition in a setuid binary or kernel driver can result in complete system compromise. Security audits must systematically check for these patterns.
TOCTOU vulnerabilities represent a critical intersection of race conditions and security. Let's consolidate the essential knowledge:
You now have a comprehensive understanding of TOCTOU vulnerabilities—their structure, exploitation, and prevention. The next page examines concrete examples of race conditions across different contexts, building a catalog of patterns you'll recognize in real systems.