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 window — Time 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.