Chapter 28 Β· Examples

Race Conditions β€” Examples

The TOCTOU file symlink attack, the double-spend exploit, and exam scenarios showing how race condition concepts appear in Security+ questions.

The TOCTOU File Symlink Attack

This is the classic TOCTOU demonstration. A privileged program needs to write a log entry. The code does two things in sequence:

  1. Checks if /tmp/logfile is owned by the current user (safe to write)
  2. Opens the file and writes the log entry

Under normal operation, both steps refer to the same file and the same state. The developer assumed the file would not change between step 1 and step 2. That assumption is the vulnerability.

The Attacker's Loop

The attacker runs two concurrent operations simultaneously:

  • In a tight loop, continuously deletes /tmp/logfile and replaces it with a symbolic link pointing to /etc/passwd β€” a critical system file
  • In a tight loop, triggers the logging operation repeatedly

The attacker does not need precision. They run both loops at maximum speed and wait for the replacement to land inside the gap between the check and the write.

What Happens Step by Step

1
Program checks: /tmp/logfile exists and is user-owned β€” safe to write (check passes)
At the time of check, the file is exactly what the program expects. The check is honest β€” the file is user-owned and safe. The race window is now open.
2
Race window opens: before the write executes, the attacker's loop replaces /tmp/logfile with a symlink to /etc/passwd
The replacement happens between step 1 and step 2. The program has already passed the check and is about to execute the write. The file it is about to write to has been silently swapped out.
3
Program writes: opens and writes to /tmp/logfile β€” which is now a symlink to /etc/passwd
The program follows the symlink transparently. It does not re-check the file. It writes attacker-controlled content to whatever /tmp/logfile points to β€” and /tmp/logfile now points to /etc/passwd.
4
/etc/passwd is modified: the privileged program wrote attacker content to the system password file
Because the program runs with elevated privileges, it was able to write to /etc/passwd even though the attacker cannot write to it directly. The race condition granted the attacker a privileged write to a file they cannot normally touch.
The check was honest β€” /tmp/logfile was safe at the time of check. The write was corrupted β€” by the time the use occurred, the file had been replaced. TOCTOU: the check and use referred to different things. The same path name pointed to two different resources depending on when you looked.

The Double-Spend Race Condition

Financial systems are a high-value target for race conditions. An online banking API processes withdrawals using three sequential steps:

  1. Check: SELECT balance FROM accounts WHERE id=X β†’ balance = $1,000
  2. Validate: if balance >= $500: proceed
  3. Execute: UPDATE accounts SET balance = balance - $500 WHERE id=X

Each step is a separate database operation. There is no locking between them. The check and the debit are not atomic β€” they are two separate operations with a gap between them.

The Race

The attacker initiates two simultaneous API requests for $500 each. Both threads execute step 1 at the same instant β€” both read $1,000. Both pass step 2 β€” $1,000 >= $500. Thread 1 executes step 3 β€” balance becomes $500. Thread 2 executes step 3 β€” balance becomes $0.

Two transfers completed. The account started at $1,000 and had two $500 withdrawals. $500 was the intended limit. $1,000 was taken.

Why the Fix Works

Use a database transaction with row-level locking (SELECT ... FOR UPDATE) to make the check and debit atomic. While one transaction holds the lock, the other must wait β€” they cannot both pass the check simultaneously. The second request reads the balance only after the first transaction's debit is committed, sees $500, and correctly allows or rejects based on the real current balance.

The double-spend is a TOCTOU pattern at the database level. Thread 2's check refers to a balance that no longer exists by the time Thread 2's debit executes. Making the check-and-debit a single locked transaction eliminates the window in which the balance can change between check and use.

Exam Scenarios

Scenario 1

Question: A privileged system process checks whether a file has user-level permissions before writing to it. An attacker replaces the file with a symlink to a root-owned configuration file between the check and the write. What type of attack is this, and what is the result?

Answer: TOCTOU race condition. The program checked one resource and wrote to another β€” the symlink redirected the write. If the process runs with elevated privileges, the attacker can write arbitrary content to a root-owned file without elevated access of their own. The check passed honestly on the original file; the write landed on the symlink's destination. This is privilege escalation via race condition β€” the attacker leveraged the privileged process as a proxy to write a file they could not normally access.

Scenario 2

Question: A developer argues that their TOCTOU vulnerability is not exploitable because "the race window is only 2 milliseconds." Evaluate this claim.

Answer: Invalid. 2 milliseconds is 2,000 microseconds β€” during which a tight loop can execute thousands of file system operations. An attacker running a replacement loop at maximum speed will land inside a 2ms window within seconds of starting the attack. The absolute size of the race window does not determine exploitability β€” the ratio of attacker attempts per second to window size does. Even tiny windows are reliably exploitable with automation. The developer's intuition β€” that 2ms "feels fast" β€” reflects human reaction time, not computer execution speed. An attacker does not react manually; they run a loop that never stops.

Scenario 3

Question: A banking application is found to allow double-spend when two identical withdrawal requests are submitted simultaneously. The developer says "we check the balance before every withdrawal." Why is this insufficient?

Answer: The check-then-act pattern is not atomic. Both requests can read the balance simultaneously before either's debit is written back. The check passes for both β€” because both see the same pre-debit balance β€” and both proceed to execute. The fix requires making the balance check and debit a single atomic database operation β€” typically achieved with a transaction and row-level lock β€” so that the second request cannot read the balance until the first request's debit is committed. "We check the balance" is true but irrelevant if two threads can perform that check simultaneously before either writes back.