TOCTOU Vulnerability — Time Of Check To Time Of Use
Alright, let’s get started. Today we’re going to look at a classic vulnerability in Unix/Linux systems called TOCTOU, which stands for “Time Of Check To Time Of Use”. The name already gives us a hint about what it’s all about. View Slides ![]()
What’s the core idea?
Imagine a program that does two things in sequence:
- Checks whether you have permission to do something
- Does that thing The problem is that time passes between step 1 and step 2. And during that time… an attacker can change the state of the system. The program has already checked, and now it acts on something different from what it checked.
It’s like the bouncer at a club who looks at your ID, says “yes, come in”, and in the second it takes you to walk in… someone swaps the door. You’ve already been granted access, but now you’re walking into somewhere else.
Background concepts we need to understand Permissions in Unix In Linux, every file has permissions for three actors: the owner (user), the group, and others. Each one can have read (r), write (w) and execute (x) permissions. There are also special permissions, and the one that matters to us today is SUID (Set User ID). When an executable has the SUID bit set and its owner is root, any user who runs it runs it with root privileges. That is very powerful… and very dangerous if the program has bugs. Links in Unix There are two types:
- Hard link: another name that points directly to the same inode (the actual data of the file)
- Soft link / Symlink: a shortcut that points to another file by name Symlinks are the protagonists of today’s attack.
The vulnerable program
Let’s look at the code on the slide:
int main(int argc, char *argv[]) {
char *file = argv[1]; // receives the file name as an argument
scanf("%50s", buffer); // reads user input
// CHECK
if (!access(file, W_OK)) { // ← checks whether the user has write permission
// DELAY (processing)
for(i = 0; i < 99999999; i++) { int a = i^2; }
// USE
fd = fopen(file, "a+"); // ← opens and writes to the file
fwrite(buffer, ...);
fclose(fd);
} else {
printf("No permission\n");
}
return 0;
}Do you see the problem? There are three separate moments:
access()— checks permissions- The delay loop — simulates processing (in real life it would be actual work)
fopen()— opens the file Theaccess()function checks permissions using the user’s real UID (that is, the regular user, not root). Butfopen()uses the effective UID — which in a SUID binary is root. That’s the trap.
The attack step by step
The diagram on the slide shows it very well. There are two “actors” running in parallel:
PROGRAM (blue line) ATTACKER (red line)
─────────────────────────────────────────────────────▶ time
[checks access(f)]
│
│ ← ATTACK WINDOW →
│ [unlink(f)] ← deletes the original file
│ [symlink(f, /etc/passwd)] ← creates a symlink with the same name
│
[fopen(f)] ← now "f" points to /etc/passwd !!
[fwrite()] ← writes to /etc/passwd as rootThe attack works like this:
The attacker has a file miarchivo.txt that does belong to them → access() approves it ✅
During the delay, the attacker deletes that file and creates a symlink with the same name pointing to a protected file (such as /etc/passwd, /etc/shadow, etc.)
When the program runs fopen(), it follows the symlink and opens the protected file
fwrite() writes there with root privileges 💀
Why doesn’t it always work the first time?
The slide asks: what happens if you run the exploit several times in a row?
The answer is that the attack is probabilistic — you depend on timing. If the symlink isn’t ready right in the window between access() and fopen(), the attack fails. That’s why the exploit script is run several times: to increase the chances of winning the “race” (hence it’s also called a race condition).
How is it mitigated?
Some ways to defend against it:
- Use
open()withO_NOFOLLOW— don’t follow symlinks when opening files. - Use file descriptors instead of paths — once the fd is open, work only with it.
openat()with safe flags — atomic operations on the filesystem. - Reduce the delay to the bare minimum.
- Don’t use
access()to make security decisions — the Linux manpage actually warns about this explicitly. View documentation →
Summary
────────────────────────────────────────────────────────────────────────────────────────
│ Concept: │ What is it? │
────────────────────────────────────────────────────────────────────────────────────────
│ TOCTOU: │ Vulnerability caused by a time window between checking and acting │
────────────────────────────────────────────────────────────────────────────────────────
│ SUID: │ Bit that makes an executable run with its owner's permissions │
────────────────────────────────────────────────────────────────────────────────────────
│ Symlink: │ Symbolic link that can be redirected by the attacker │
────────────────────────────────────────────────────────────────────────────────────────
│ Race condition: │ Success depends on timing │
────────────────────────────────────────────────────────────────────────────────────────In security, a check and an action are never atomic if there is time between them. A patient attacker (or a fast script) can slip into that window.
