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
synchronizedblock (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
synchronizedor uselock.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 ofstat()thenopen()(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!
}