CWE-557: Concurrency Issues
Overview
Concurrency issues occur when multiple threads or processes access shared resources without proper synchronization, causing race conditions, data corruption, atomicity violations, and deadlocks.
Relationship to Other CWEs
CWE-557 is a Category, and MITRE's mapping guidance for it is Prohibited. It groups related concurrency weaknesses rather than naming one, and MITRE's rationale is the general one - categories are "informal organizational groupings" rather than weaknesses - so a finding reported as CWE-557 is a finding that has not been classified yet, and the first step is to work out which shape is actually present.
Where a more specific concurrency CWE fits, prefer that page:
- CWE-362 (Race Condition) - the general case
- CWE-364 (Signal Handler Race Condition)
- CWE-366 (Race Condition within a Thread) - state shared between threads of one process
- CWE-367 (Time-of-check Time-of-use)
- CWE-421 (Race Condition During Access to Alternate Channel)
The category also holds several with no page here - CWE-368 (Context Switching Race Condition), CWE-820 (Missing Synchronization), CWE-821 (Incorrect Synchronization) and CWE-1322 (Use of Blocking Code in Single-threaded, Non-blocking Context) among them.
Use this page for those, for general concurrency-safety guidance, and when the finding doesn't map cleanly to one of the more specific weaknesses.
OWASP Classification
A06:2025 - Insecure Design
Risk
High: Concurrency bugs cause data corruption (lost updates, inconsistent state), security checks bypassed by a race (check-then-act on a security decision), and deadlocks (application hangs). They are hard to reproduce or debug because they depend on precise timing.
Remediation Steps
Core Principle: Concurrency issues must not bypass security controls; design for thread safety and make security-relevant checks atomic with the action they gate.
Locate Concurrency Issues and Race Conditions
- Identify shared mutable state - variables, collections, or fields accessed by more than one thread
- Find check-then-act sequences (a check followed by a use, with no synchronization between them) - especially where the check is a security decision
- Look for non-atomic read-modify-write operations (
counter++is three operations: read, add, write) - Look for collections used from multiple threads that aren't documented as thread-safe
- Review lock acquisition order across the codebase for potential deadlock cycles
Make the Check and the Action Atomic (Primary Defense)
// VULNERABLE - check-then-act race condition
function withdraw(amount):
if balance >= amount: // CHECK
// another thread can withdraw here, between the check and the use
balance -= amount // ACT
// SECURE - check and act happen as one atomic unit
function withdraw(amount):
with lock:
if balance >= amount:
balance -= amount
Why this works: Wrapping the check and the action in the same lock makes the pair indivisible from every other thread's perspective, so nothing can observe or modify the shared state in between. Guard every access to that state with the same lock, including reads - an unsynchronized read can observe a partially-updated value even when the writes are protected.
Use Thread-Safe Data Structures Instead of Manual Locking Where Possible
Prefer the language or runtime's concurrent collection types - a concurrent hash map, a copy-on-write list, a blocking queue for producer-consumer patterns - over a plain collection guarded by ad hoc locking. They handle the internal synchronization correctly, and they often provide atomic compound operations such as "insert only if absent" that are easy to get wrong with manual locking.
Use Atomic Primitives for Counters and Flags
A plain increment on a shared counter is not atomic. For simple counters and flags, use the language's atomic integer, boolean and reference types: they provide atomic increment, compare-and-swap and similar operations without an explicit lock. Reserve manual locking for the cases that need it.
Avoid Deadlocks With Consistent Lock Ordering
// VULNERABLE - deadlock risk: two threads can lock in opposite order
function transfer(from, to, amount):
with lock(from):
with lock(to):
from.withdraw(amount)
to.deposit(amount)
// Thread 1: transfer(A, B) locks A, waits for B
// Thread 2: transfer(B, A) locks B, waits for A -> DEADLOCK
// SECURE - always acquire locks in a consistent order, regardless of call direction
function transfer(from, to, amount):
first, second = order_by_id(from, to)
with lock(first):
with lock(second):
from.withdraw(amount)
to.deposit(amount)
Why this works: Deadlock requires a cycle of threads each waiting on a lock the next one holds. Acquiring multiple locks in the same global order, for example by a stable ID, makes that cycle impossible: no two threads can each be holding what the other is waiting for.
Test for Concurrency Issues
- Run the code under heavy concurrent load (many threads/requests hitting the same shared state) and verify results are consistent - a counter incremented N times by M threads should read exactly N*M, not less
- Use a thread/data-race sanitizer (e.g. ThreadSanitizer) or static analysis tooling that flags inconsistent synchronization
- Stress-test the specific check-then-act sequence named in the finding with concurrent requests and confirm the security invariant holds under contention
- Where the shared state is outside the process - a file, a row, a queue - run the test with two processes rather than two threads. A one-process test passes against an in-process lock that does nothing for the real race
- Check for deadlocks under load (all threads should complete; none should hang indefinitely)
- Re-scan with the security scanner to confirm the finding is resolved
Common Vulnerable Patterns
// VULNERABLE - pseudo-code
// Check-then-act race
if not file_exists(path):
create_file(path) // another thread's create can land here first
// Read-modify-write race
count = get_count()
count = count + 1
set_count(count) // lost update if another thread interleaves
// Atomicity violation - a partial update is observable by other threads
function set_name_and_age(name, age):
this.name = name // another thread can read here, between the two writes
this.age = age // now name/age are inconsistent with each other
Why this is vulnerable: all three assume that the code between two statements runs without interruption, and nothing enforces that. The first checks a condition that another thread can invalidate before the action runs (CWE-367); the second reads a value, computes from a stale copy and writes the result back, losing whichever update landed in between (CWE-362).
The third is the one worth reading twice, because it has no check and no arithmetic to draw attention to it. set_name_and_age performs two writes that are individually correct and correct together; the only defect is that the pair is not atomic. A reader arriving between them sees the new name against the old age, a combination that has never been valid and that no single line of this code produces. That is why an object with several fields that must agree needs its update guarded as a unit rather than field by field.
The three need different fixes - a lock, an atomic operation, and an invariant-preserving update - so work out which shape the finding is before choosing one. The wrong fix leaves the defect in place.
Secure Patterns
// SECURE - pseudo-code
// Check-then-act made atomic - but only against other threads of this process;
// for a file, the atomic create is the fix (see below)
with lock:
if not file_exists(path):
create_file(path)
// Atomic increment instead of manual read-modify-write
count = atomic_counter()
count.increment() // single indivisible operation, no lost updates
// Atomicity violation fixed by updating under a single lock
function set_name_and_age(name, age):
with lock:
this.name = name
this.age = age // both fields update as one unit; no reader sees a mix
Why this works: Every shared piece of state now has exactly one synchronization mechanism guarding all of its reads and writes, so no other thread can observe it mid-update or race a check against a concurrent modification. An atomic primitive covers the single-variable case without the overhead and deadlock risk of an explicit lock; a lock covers the case where several related fields must update together.
A lock only serializes what shares its address space, which is why the first example carries a caveat the other two do not. count and this.name live in one process, so an in-process lock is a complete fix for them. A file does not: another process, a second worker, or a container sharing the volume never sees the lock, and the check-then-create race is still open. What closes it there is asking the resource itself for atomicity - open(path, O_CREAT | O_EXCL), a unique constraint, a conditional update - so the create either happens or fails, with no window between deciding and doing. See CWE-367. The right mechanism at the wrong scope looks like a fix and leaves the defect in place.