A time-of-check to time-of-use (TOCTOU) race condition vulnerability occurs when a program checks a resource's state — a file's permissions, a signature, an account balance — and then acts on that resource moments later, assuming nothing changed in between. An attacker who wins that race swaps the file, symlink, or value during the gap, so the code ends up trusting something it never actually verified. TOCTOU bugs carry the CWE-367 designation and have caused real damage: CVE-2016-5195 ("Dirty COW") sat unpatched in the Linux kernel for nine years before its October 2016 disclosure, and CVE-2019-5736 let a malicious container overwrite the host's runc binary during a docker exec call. Both trace back to the same root cause — a check and a use separated by time, with no atomic guarantee binding the two together. For teams shipping containerized software, TOCTOU flaws are a supply chain risk, not just a kernel curiosity.
What is a TOCTOU race condition vulnerability?
A TOCTOU race condition vulnerability is a software defect in which the result of a security check — a file permission lookup, a hash comparison, a path resolution — becomes stale by the time the program acts on it, letting an attacker substitute a different resource during that window. It is tracked as CWE-367 ("Time-of-check Time-of-use Race Condition") and sits under the broader race condition category CWE-362. The vulnerable pattern always has two distinguishable steps: a check (stat(), access(), a signature verification, a balance read) and a use (open(), an execution, a fund transfer) that happens afterward, on the assumption the checked state is still valid. The gap between the two calls is frequently measured in microseconds, but on a loaded system, or with an attacker actively widening the window using techniques like forcing page faults or filesystem delays, that gap is enough to win the race reliably, sometimes on the first attempt and almost always within a few hundred tries.
How does an attacker actually exploit a TOCTOU window?
An attacker exploits a TOCTOU window by racing a second process to modify the checked resource after the check call returns but before the use call executes, most commonly by replacing a target file with a symlink between a permission check and a later file open. The canonical vulnerable pattern looks like this in C:
if (access("/tmp/report.txt", W_OK) == 0) {
// attacker deletes /tmp/report.txt and symlinks it to /etc/shadow here
fd = open("/tmp/report.txt", O_WRONLY);
write(fd, data, len);
}
Between the access() call and the open() call, an attacker-controlled process (or a symlink dropped into a world-writable /tmp) swaps the path's target. The program's open() now writes to /etc/shadow instead of the file it validated. Attackers widen this window in practice using tools such as FUSE-backed filesystems that can be made to stall on demand, or by pinning the victim process with SIGSTOP/SIGCONT cycles to buy milliseconds of extra race time — techniques documented in kernel exploit research since at least 2011 and refined in Dirty COW exploit code published in 2016.
What real-world CVEs have been caused by TOCTOU flaws?
The two most cited real-world TOCTOU-class CVEs are Dirty COW (CVE-2016-5195) and the runc container breakout (CVE-2019-5736), both of which converted a race window into privileged code execution. Dirty COW was disclosed on October 19, 2016 by researcher Phil Oester, carries a CVSS score of 7.8, and exploited a race in the Linux kernel's copy-on-write handling of the madvise(MADV_DONTNEED) path: a local attacker could win the race between a write-permission check and the actual memory write to modify read-only files, including /etc/passwd, for privilege escalation. It had been present in the kernel since version 2.6.22, released in July 2007 — nine years of exposure before a patch landed in kernel 4.8.3, and Red Hat reported evidence suggesting in-the-wild exploitation before public disclosure. CVE-2019-5736, disclosed February 11, 2019 by researchers Adam Iwaniuk and Borys Popławski, carries a CVSS score of 8.6 and let a malicious container overwrite the host's runc binary by racing a file-descriptor reference to /proc/self/exe during container startup, giving the attacker root-equivalent code execution on the host. It affected Docker versions prior to 18.09.2, containerd, and Podman, and was fixed by having runc clone itself into a sealed memory file descriptor before executing rather than trusting a path reference that could be swapped mid-race.
How is a TOCTOU flaw different from other race conditions?
A TOCTOU flaw is a specific subtype of race condition defined by two clearly separable operations — a check and a use — connected only by an exploitable time gap, whereas other race conditions like use-after-free or double-free bugs involve concurrent, unsynchronized access to the same memory without that clean check-then-act structure. A related but distinct pattern is the "double-fetch" bug common in kernel drivers, where a driver reads a value from user-controlled memory twice (once to validate it, once to use it) and an attacker changes the value between the two reads from a second thread — functionally a TOCTOU bug, but scoped to memory reads rather than filesystem or network resources. Both are filed under the CWE-362 race condition family, but CWE-367 specifically requires the check/use split, which is what makes the fix pattern — collapsing two operations into one atomic syscall — different from the locking-based fixes typically used for shared-memory races.
How can developers prevent TOCTOU vulnerabilities in code?
Developers prevent TOCTOU vulnerabilities by replacing separate check-then-use calls with a single operation the operating system guarantees is atomic, rather than trusting that a resource's state holds steady between two calls. Concretely: open a file descriptor once and perform all subsequent operations (permission checks, reads, writes) against that descriptor instead of re-resolving the path; use O_NOFOLLOW to reject symlinks outright and O_EXCL with O_CREAT to guarantee exclusive file creation; and on Linux 5.6 and later (released March 2020), use the openat2() syscall with the RESOLVE_NO_SYMLINKS flag to close an entire class of path-resolution races in one call. runc's fix for CVE-2019-5736 is the reference example: instead of re-executing a path that could be swapped mid-race, the patched code clones the running binary into a sealed, anonymous memfd and executes that copy, so there is no window left for substitution. Beyond code-level fixes, catching these patterns before release requires static analysis rules tuned for CWE-367 (flagging access()/stat() calls followed by a separate open() on the same path) plus code review focused specifically on any code path that touches shared or world-writable directories like /tmp.
How Safeguard Helps
Safeguard's Griffin AI engine flags TOCTOU-prone patterns — split check/use calls, unguarded symlink follows, unsynchronized reads against shared paths — during static analysis and correlates them with reachability analysis to show whether the vulnerable code path is actually invoked from an exposed entry point, cutting through the noise of theoretical CWE-367 matches that never execute in production. For flaws that trace back to open source dependencies, such as a vendored library with an unpatched race condition, Safeguard's SBOM generation and ingest pipeline pinpoints every affected component and version across your container images and services in one query. When a fix is available upstream, Safeguard opens an auto-fix pull request with the patched dependency version pinned and the relevant CVE detail attached, so engineering teams can ship the remediation without manually triaging the advisory first.