CWE-367, Time-of-check Time-of-use (TOCTOU) Race Condition, is one of the best known race conditions. MITRE describes it as a product that checks the state of a resource before using it, where the state can change between the check and the use in a way that invalidates the check.
It matters most for programs that touch files, and especially privileged programs that handle paths supplied by less privileged users. It also appears with sockets, locks, permissions and other shared resources.
How it happens
The pattern is two steps. First ask a question about a resource: does it exist, who owns it, can this user write to it. Then act on it. The answer was true at the time of asking. It may not be true a few instructions later.
An attacker who can change the resource in that gap wins. They might replace a file with a link to a protected one, swap a directory, or alter permissions. The program, believing its check, then acts on the new thing with its own authority.
The gap is small, but small is not safe. Attackers can repeat the attempt many times and can sometimes widen the window by loading the system.
The MITRE record's observed examples include CVE-2015-1743, where a TOCTOU flaw in a sandbox process allowed installation of untrusted browser add-ons by replacing a file after it had been verified but before it was used. That is a clean illustration: the verification was real, and it was about a file that was no longer there.
Two other examples show chains. CVE-2003-0813 describes a multithreaded race condition that lets attackers cause two threads to process the same RPC request. CVE-2004-0594 describes a PHP flaw where remote attackers execute arbitrary code by aborting execution before key data structures are initialized.
What an attacker gains
MITRE lists several outcomes.
- Gaining access to resources the attacker is not authorized to reach, by altering execution logic and reaching an unexpected state.
- Read or write access to files, memory or other resources that are not normally readable or writable by that user.
- Changes to the resource in question, or to other resources reached through the corrupted one.
- Hiding activities: a file written this way may bypass the normal logging path.
- In some cases, deleting files the attacker would not normally have access to, such as log files.
How to find it
MITRE notes that automated static analysis can find some instances of this weakness by analyzing source code.
Manual search is quick too.
- Look for pairs: access, stat or lstat followed by open, rename, chmod or unlink on the same path.
- Look for existence checks before creating a file.
- For each pair, ask who else can write to that directory, and what privileges the program has.
How to fix it
MITRE's mitigations, in plain steps:
- The most basic advice is to not perform a check before the use. This does not by itself solve the underlying problem of acting on a resource whose state and identity cannot be assured, but it limits the false sense of security the check gives.
- When the file is owned by the current user and group, set the effective user and group IDs to those of the current user while acting.
- Limit the interleaving of operations on files from multiple processes.
- If you cannot perform operations atomically and must share the resource, limit the time between check and use. This does not fix the problem but makes an attack harder.
- Recheck the resource after the use call to verify the action was taken appropriately.
- Use an environmental locking mechanism to protect the resource.
- Make sure the lock is taken before the check, not after, so the resource as checked is the same as when used.
In code, the most dependable approach is to bind the check and the use to the same object. A vulnerable C example:
if (access(path, W_OK) == 0) { /* check */
int fd = open(path, O_WRONLY); /* use: path may now point elsewhere */
write(fd, data, len);
}
The safer version drops the separate check, opens once, and examines the opened handle:
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
int fd = open(path, O_WRONLY | O_NOFOLLOW);
if (fd < 0) { /* refuse */ }
struct stat st;
if (fstat(fd, &st) != 0 || !S_ISREG(st.st_mode)) {
close(fd);
/* refuse */
}
write(fd, data, len);
Once the file is open, the descriptor refers to that specific file no matter what happens to the name afterward. Checking the descriptor, not the path, closes the gap. For temporary files, use functions designed to create them safely, such as mkstemp, instead of building a predictable name and checking for it.
Try it yourself
Static analysis can flag check then use pairs. The demo shows how a code scan reports this class of issue.
Click through it at your own pace, or open it full screen.
Quick check
Why does checking a file path and then opening it fail?
The path can point to a different file by the time of the open. The check described an earlier state, so it no longer protects the action.
What is the most dependable fix?
Avoid a separate check. Act on the resource directly and verify properties on the handle you hold, so the check and the use refer to the same object.
Does a very short window make the flaw unexploitable?
No. Attackers can retry many times or try to widen the window, so a short gap only makes the attack harder, not impossible.
Sources
- MITRE CWE-367: https://cwe.mitre.org/data/definitions/367.html