Skip to content

CWE-366: Race Condition within a Thread

Overview

Race conditions occur when multiple threads access shared resources without proper synchronization, leading to time-of-check/time-of-use (TOCTOU) vulnerabilities, data corruption, inconsistent state, and security bypass when operations assumed to be atomic aren't.

Risk

High: Race conditions enable privilege escalation (checking then using privileges), double-spending, inventory issues, authentication bypass, file system races (symlink attacks), inconsistent data state, and security check bypass.

Remediation Steps

Core principle: Concurrency must not break security invariants; synchronize access to shared state and make operations atomic.

Locate the race condition in your code

  • Review the flaw details to identify the specific file, line number, and code pattern with unsynchronized shared resource access
  • Identify the shared resource: shared variable, file, database record, inventory count, balance
  • Determine the race window: where check and use are separated, or where multiple threads can interleave
  • Trace the concurrent operations: identify all threads/processes that access the shared resource

Use proper synchronization (Primary Defense)

  • Use locks/mutexes for critical sections: Wrap check-and-use in synchronized block (Java), lock.acquire() (Python), std::mutex (C++)
  • Atomic operations for simple updates: Use AtomicInteger.incrementAndGet() (Java), std::atomic<int> (C++) for simple increment/decrement
  • Thread-safe data structures: Use ConcurrentHashMap (Java), queue.Queue (Python), thread-safe collections instead of regular collections
  • Synchronized methods/blocks: Mark methods as synchronized or use lock.synchronized { } to protect critical sections

Avoid time-of-check/time-of-use (TOCTOU)

  • Combine check and use in atomic operation: Use compare-and-swap instead of separate check and update
  • Hold lock from check through use: Acquire lock before check, don't release until after use
  • Use file descriptor instead of path: Open file and use fstat() instead of stat() then open() (prevents symlink TOCTOU)
  • Verify after acquiring lock: After getting lock, re-check condition before proceeding

Use atomic operations

  • Atomic compare-and-swap (CAS): compareAndSet(expected, new) for lock-free updates
  • Atomic increment/decrement: AtomicInteger.incrementAndGet(), std::atomic::fetch_add()
  • AtomicInteger, AtomicReference (Java): Use for lock-free counters, flags, references
  • std::atomic (C++): Use for lock-free primitives in C++

Design thread-safe code

  • Minimize shared mutable state: Prefer immutable objects, reduce shared variables
  • Use immutable objects: Once created, can't be changed; inherently thread-safe
  • Thread-local storage: Use ThreadLocal (Java), threading.local() (Python) for per-thread data
  • Lock-free data structures: Use concurrent data structures when performance critical

Test the race condition fix

  • Verify proper synchronization is in place (locks acquired, atomic operations used)
  • Test with concurrent access: Run multiple threads simultaneously accessing shared resource
  • Use race detection tools: ThreadSanitizer, Helgrind, Java's -XX:+UseParallelGC with assertions
  • Test under high load: Race conditions often appear under stress
  • Re-scan with security scanner to confirm the issue is resolved

Common Vulnerable Patterns

if (balance >= amount) {  // Check
    balance -= amount;    // Use - RACE!
}

if (file.exists()) {      // Check
    file.open();          // Use - RACE!
}

if (isAdmin(user)) {      // Check
    doAdminAction();      // Use - RACE!
}

Additional Resources