TOCTOU Visualization

Race Conditions

The instant between “check” and “use” is a window. An attacker who fits through that window owns the outcome.

Ready. A privileged binary needs to read a file the user owns.
Current Scenario
A setuid root program is asked to print the contents of a file. To avoid letting users read arbitrary root-owned files, it first checks ownership with access("/tmp/userfile", R_OK). If the check passes, it calls open() and reads the file. The gap between access and open is the TOCTOU windowTime Of Check to Time Of Use. An attacker who replaces the file in that window reads anything the privileged process can read.
Victim Process (uid=0, setuid root)PID 7104
v1 stat("/tmp/userfile")
v2 if (owner == real_uid) ...
v3 open("/tmp/userfile", O_RDONLY)
v4 read(fd, buf, 4096)
v5 write(stdout, buf)
Attacker Process (uid=1000)PID 7211
a1 wait for victim to stat()
a2 unlink("/tmp/userfile")
a3 symlink("/etc/shadow","/tmp/userfile")
a4 wait for victim to read
a5 capture /etc/shadow from stdout
Filesystem at /tmp/userfile
/tmp/userfile
type: regular file
owner: uid 1000 (attacker)
contents: "hello world\n"
/etc/shadow
type: regular file
owner: uid 0 (root)
contents: root:$6$xLR5...:19000:0:99999:7:::
Interleaved Timeline
t=0 Pick a scenario above to begin.

Why timing is a vulnerability class

A race condition is a defect whose outcome depends on the order in which two or more concurrent operations happen. TOCTOU — Time Of Check to Time Of Use — is the security-relevant form: between the moment a program checks a condition and the moment it acts on it, an attacker changes the underlying state. The check is satisfied for one entity (the original file, the original owner); the use happens against another.

The classic example is filesystem races against setuid binaries. A root-owned program checks permissions with access() and then opens the file with open(). Between those two calls, an attacker swaps the user's file for a symlink to /etc/shadow. The privileged process opens what is now a link to a sensitive file and faithfully prints it. The bug isn't in either system call — it's in the assumption that the filesystem doesn't change between them.

Races appear anywhere two paths touch shared state: kernel concurrency, web requests against the same database row, lock-free data structures, signal handlers, and even hardware speculation (Meltdown and Spectre exploit micro-architectural races). The defensive principle is the same in every case: operate on a handle, not a name. Open the file once, then ask the kernel about the file descriptor (fstat, fchown, fchmod) — nothing in user-space can substitute a different file behind your back once you hold the fd.

When you can't operate on a handle, the answer is an atomic operation: a single primitive that performs check-and-act indivisibly. Compare-and-swap, file locking, database transactions with SELECT ... FOR UPDATE, or O_CREAT | O_EXCL when creating files. The shape of the fix changes; the principle — collapse the window to zero — does not.

In the wild

CVE-2016-5195
Dirty COW
A nine-year-old race in the Linux kernel's copy-on-write handling. Any local user could write to read-only mappings, including setuid binaries. Patched October 2016 after observed exploitation.
CVE-2019-1003000
Jenkins Script Security
A race in the Jenkins sandbox allowed parallel pipeline builds to bypass the script-approval mechanism, executing arbitrary Groovy as the controller.
CVE-2022-0847
Dirty Pipe
A race in Linux pipe-buffer flags let unprivileged users overwrite data in arbitrary read-only files, including setuid binaries. Local root on every distro shipping kernel 5.8+.
Meltdown / Spectre
CVE-2017-5754 et al.
Speculative execution exposes a race between the CPU starting a forbidden read and the permission check catching it. Bounds-check bypass became a hardware vulnerability class.