Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')
Description
Race Condition vulnerabilities occur when multiple threads, processes, or systems access and modify shared resources concurrently without proper synchronization, causing the outcome to depend on the timing of operations. A particularly dangerous variant is Time-of-Check Time-of-Use (TOCTOU), where a security check is performed on a resource, but the resource can be modified by an attacker between the check and subsequent use. Race conditions can affect memory, files, network sockets, or any shared resource, leading to security bypasses, privilege escalation, or data corruption.
Risk
Race conditions are subtle vulnerabilities that are difficult to detect and exploit consistently, but they can have severe consequences. In file operations, TOCTOU vulnerabilities allow attackers to substitute files (often via symlinks) between security checks and actual use, enabling privilege escalation or arbitrary file access. In memory operations, race conditions can corrupt data structures, bypass authentication, or enable double-free vulnerabilities. The exploitation window may be narrow, but attackers can widen it through system stress or precise timing. Dirty COW (CVE-2016-5195) demonstrated that kernel race conditions can enable root privilege escalation.
Solution
Use atomic operations when possible to eliminate race windows. Implement proper synchronization primitives (mutexes, semaphores) for shared resources. For file operations, use file descriptors instead of paths after opening, avoid checking then acting patterns, and use secure temporary file creation (mkstemp). Minimize shared resource usage and the time spent holding locks. Use thread-safe data structures and libraries. Apply the principle of least privilege to limit race condition impact. Test with stress testing and deliberate delay injection to detect race windows.
Common Consequences
| Impact | Details |
|---|---|
| Access Control | Scope: Privilege Escalation TOCTOU file races allow attackers to substitute privileged files, bypass access checks, or gain elevated access. |
| Integrity | Scope: Data Corruption Unsynchronized concurrent modifications corrupt shared data structures and state. |
| Availability | Scope: System Instability Race conditions cause deadlocks, crashes, and unpredictable behavior. |
Example Code + Solution Code
Vulnerable Code
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
// VULNERABLE: TOCTOU race condition
int process_file_unsafe(const char *filename) {
struct stat st;
// TIME OF CHECK: Verify file is safe
if (stat(filename, &st) != 0) {
return -1;
}
// Attacker can replace file with symlink here!
// Race window between check and use
if (st.st_uid != getuid()) {
return -1; // Not our file
}
// TIME OF USE: Open and read
// May now be a different file (attacker's symlink)!
FILE *f = fopen(filename, "r");
process_contents(f);
fclose(f);
return 0;
}
// VULNERABLE: Check-then-act race
int withdraw_money(account_t *acc, int amount) {
// Another thread might withdraw between check and update
if (acc->balance >= amount) {
// Race window here!
acc->balance -= amount; // Double-withdraw possible
return amount;
}
return 0;
}
# VULNERABLE: TOCTOU file check
import os
def safe_write_file(filepath, data):
# Check if file exists and is writable
if os.path.exists(filepath):
if not os.access(filepath, os.W_OK):
return False
# RACE WINDOW: Attacker replaces with symlink to /etc/passwd
# Write to file - might now be a different file!
with open(filepath, 'w') as f:
f.write(data)
return True
Fixed Code
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
// SAFE: Use file descriptor, not path
int process_file_safe(const char *filename) {
// Open first, then check
int fd = open(filename, O_RDONLY | O_NOFOLLOW); // Don't follow symlinks
if (fd < 0) {
return -1;
}
// Check using file descriptor - no TOCTOU
struct stat st;
if (fstat(fd, &st) != 0) {
close(fd);
return -1;
}
if (st.st_uid != getuid()) {
close(fd);
return -1;
}
// Use the already-opened file descriptor
FILE *f = fdopen(fd, "r");
process_contents(f);
fclose(f); // Also closes fd
return 0;
}
// SAFE: Atomic operation with mutex
#include <pthread.h>
pthread_mutex_t account_mutex = PTHREAD_MUTEX_INITIALIZER;
int withdraw_money_safe(account_t *acc, int amount) {
pthread_mutex_lock(&account_mutex);
int result = 0;
if (acc->balance >= amount) {
acc->balance -= amount;
result = amount;
}
pthread_mutex_unlock(&account_mutex);
return result;
}
// SAFE: Using atomic compare-and-swap (lock-free)
#include <stdatomic.h>
int withdraw_money_atomic(atomic_int *balance, int amount) {
int expected = atomic_load(balance);
do {
if (expected < amount) {
return 0; // Insufficient funds
}
} while (!atomic_compare_exchange_weak(balance, &expected, expected - amount));
return amount;
}
import os
import tempfile
import fcntl
# SAFE: Create temporary file securely
def safe_write_file(directory, data):
# mkstemp creates file atomically with secure permissions
fd, filepath = tempfile.mkstemp(dir=directory)
try:
os.write(fd, data.encode())
finally:
os.close(fd)
return filepath
# SAFE: Use file locking
def atomic_update_file(filepath, data):
with open(filepath, 'r+') as f:
# Exclusive lock prevents concurrent access
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
try:
f.truncate(0)
f.write(data)
finally:
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
# SAFE: Open with O_NOFOLLOW and check after
import stat
def safe_open_file(filepath):
fd = os.open(filepath, os.O_RDONLY | os.O_NOFOLLOW)
try:
# Check file properties using fd, not path
st = os.fstat(fd)
if stat.S_ISLNK(st.st_mode):
raise SecurityError("Symlink detected")
return os.fdopen(fd, 'r')
except:
os.close(fd)
raise
Exploited in the Wild
Dirty COW (Linux Kernel, 2016)
CVE-2016-5195 was a race condition in the Linux kernel's copy-on-write (COW) mechanism that allowed unprivileged local users to gain write access to read-only memory mappings, enabling root privilege escalation. It affected Linux systems for 9 years before discovery.
filelock TOCTOU Symlink Attack (Python, 2025)
CVE-2025-68146 in Python's filelock library allows attackers to corrupt or truncate arbitrary files via symlink attacks in the race window between existence check and file open with O_TRUNC.
Docker runc Container Escape (Docker, 2019)
CVE-2019-5736 was a race condition in runc allowing container escape by overwriting the host runc binary, demonstrating the severe impact of race conditions in containerization.
Tools to test/exploit
-
ThreadSanitizer — detects data races at runtime.
-
Helgrind — Valgrind tool for detecting synchronization errors.
-
race-the-web — test web endpoints for race conditions.
CVE Examples
-
CVE-2016-5195 — Dirty COW Linux kernel privilege escalation.
-
CVE-2019-5736 — runc container escape race condition.
-
CVE-2025-68146 — Python filelock TOCTOU vulnerability.
References
-
MITRE. "CWE-362: Concurrent Execution using Shared Resource with Improper Synchronization." https://cwe.mitre.org/data/definitions/362.html
-
CERT. "FIO01-C. Be careful using functions that use file names for identification." https://wiki.sei.cmu.edu/confluence/display/c/FIO01-C