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

ImpactDetails
Access ControlScope: Privilege Escalation

TOCTOU file races allow attackers to substitute privileged files, bypass access checks, or gain elevated access.
IntegrityScope: Data Corruption

Unsynchronized concurrent modifications corrupt shared data structures and state.
AvailabilityScope: 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.

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


CVE Examples


References

  1. MITRE. "CWE-362: Concurrent Execution using Shared Resource with Improper Synchronization." https://cwe.mitre.org/data/definitions/362.html

  2. CERT. "FIO01-C. Be careful using functions that use file names for identification." https://wiki.sei.cmu.edu/confluence/display/c/FIO01-C