Concurrency, Race Conditions and Thread Safety

5 minutes 5 Questions

Concurrency occurs when multiple threads or processes execute simultaneously, sharing resources such as memory, files, or database records. In the CSSLP Secure Software Implementation domain, concurrency is a critical concern because improperly managed shared state can create exploitable vulnerabilities, not just functional bugs. A race condition arises when the correctness of a program depends on the relative timing or ordering of events that are not properly controlled. The classic example is a Time-of-Check to Time-of-Use (TOCTOU) flaw: a program checks a condition (e.g., verifying that a user has permission to access a file, or that an account balance is sufficient) and then acts on that check moments later. If an attacker can change the state between the check and the use—swapping a file for a symbolic link, or initiating a second withdrawal—the security decision becomes invalid. Race conditions can lead to privilege escalation, data corruption, double-spending, authentication bypass, and denial of service. They are notoriously difficult to detect through testing because they manifest intermittently and depend on scheduling. Thread safety is the property of code that functions correctly when accessed by multiple threads concurrently. Achieving it requires deliberate design: using synchronization primitives such as mutexes, semaphores, locks, and monitors to serialize access to shared resources; favoring immutable objects and thread-local storage to eliminate shared mutable state; using atomic operations provided by the language or platform; and leveraging thread-safe collections and libraries rather than hand-rolling synchronization. Developers must also guard against deadlocks, livelocks, and starvation, which can themselves become availability attacks. From a secure implementation perspective, CSSLP guidance emphasizes performing checks and actions atomically, minimizing the window between validation and use, applying the principle of least privilege to limit damage from exploited races, and incorporating concurrency analysis into code review and static/dynamic testing. Treating concurrency as a security requirement, rather than merely a performance consideration, reduces the attack surface significantly.

Concurrency, Race Conditions and Thread Safety (ISC2 CSSLP – Secure Software Implementation)

Why This Topic Matters
Modern software is almost never single-threaded. Web servers handle thousands of simultaneous requests, mobile apps run background tasks while the UI stays responsive, and cloud services scale horizontally across many processes and nodes. Whenever two or more execution paths touch the same resource at the same time, the door opens to a class of defects that are subtle, hard to reproduce, and frequently exploitable. For the CSSLP exam, this subject falls under Domain 4, Secure Software Implementation, and it connects directly to secure coding practices, defensive programming, and the CWE/OWASP weakness categories you are expected to recognise. Race conditions appear in CWE-362 (Concurrent Execution using Shared Resource with Improper Synchronization) and the well-known CWE-367 (Time-of-Check Time-of-Use). Attackers exploit them to escalate privileges, bypass authorisation checks, double-spend funds, corrupt data, or crash services. Because the failure is timing-dependent, traditional testing often misses it, which is exactly why the exam wants you to understand how to design it out from the start.

What Concurrency Is
Concurrency means multiple units of execution (threads, processes, coroutines, or distributed workers) progressing during overlapping time periods. On a multi-core machine they may truly run in parallel; on a single core the scheduler interleaves them so quickly that they appear simultaneous. Concurrency delivers throughput and responsiveness, but it introduces non-determinism: the order in which instructions from different threads interleave is not fixed and can vary between runs.

What a Race Condition Is
A race condition occurs when the correctness of a program depends on the relative timing or ordering of events that the developer did not control. Two threads "race" to access or modify a shared resource, and the outcome depends on who wins. The resource could be a variable in memory, a file on disk, a database row, a session object, a hardware register, or an external service.

The classic illustration is a bank balance. Thread A reads balance = 100. Thread B reads balance = 100. Thread A withdraws 80 and writes 20. Thread B withdraws 80 and writes 20. The account should have rejected the second withdrawal or shown -60, but instead the customer has withdrawn 160 while the ledger says 20. This is a check-then-act or read-modify-write race.

Key Terminology You Must Know
Critical section: a segment of code that accesses a shared resource and must not be executed by more than one thread at a time.
Atomic operation: an operation that completes entirely or not at all with no observable intermediate state; cannot be interrupted midway by another thread.
Mutual exclusion (mutex): a mechanism guaranteeing only one thread enters a critical section at a time.
Semaphore: a counter-based synchronisation primitive allowing up to N concurrent accesses; a binary semaphore behaves like a mutex.
Lock / monitor: language-level constructs (synchronized in Java, lock in C#, Mutex/RwLock in Rust) that wrap mutual exclusion.
Deadlock: two or more threads each hold a resource the other needs and wait forever. Requires four conditions: mutual exclusion, hold-and-wait, no pre-emption, circular wait.
Livelock: threads keep changing state in response to each other but make no progress.
Starvation: a thread is perpetually denied access because others keep winning.
Thread safety: code behaves correctly when accessed from multiple threads simultaneously, regardless of scheduling.
Reentrancy: a function can be safely interrupted and called again before the first invocation completes; it does not rely on shared mutable state.
TOCTOU (Time-of-Check to Time-of-Use): a race where a condition is verified, then the state changes before the result is used.
Immutability: an object whose state cannot change after creation; inherently thread-safe for reads.

How Race Conditions Work in Practice

1. TOCTOU in file systems. A privileged program checks that /tmp/report is a regular file owned by the user (the check), then opens it for writing (the use). Between those two system calls an attacker deletes the file and replaces it with a symbolic link to /etc/passwd. The program, now writing to the link, overwrites a system file. The fix is not "check faster"; it is to eliminate the gap, for example by opening the file first and then performing checks on the open file descriptor (fstat rather than stat), using O_NOFOLLOW and O_EXCL flags, or operating within a directory the attacker cannot write to.

2. Double-spend and limit bypass in web applications. An e-commerce site checks that a coupon has not been used, then marks it used and applies the discount. An attacker fires 50 identical requests within a few milliseconds. Many pass the "not used" check before any of them writes the "used" flag, so the coupon is applied dozens of times. Similar logic flaws allow exceeding withdrawal limits, exceeding vote counts, or bypassing rate limits. The remedy is to make the check and the update a single atomic database operation (UPDATE ... WHERE used = false and check affected rows), use proper transaction isolation, or acquire a row-level lock.

3. Shared mutable state in servers. A servlet or controller stores request-specific data in an instance field. Because the framework reuses one instance for many threads, user A's data leaks into user B's response. This is a thread-safety bug rather than a classic TOCTOU, but it is still a race over shared state. The fix is to keep per-request data in local variables or thread-local storage and avoid mutable singletons.

4. Lazy initialisation and singletons. Two threads simultaneously see that an object is null and both create it, breaking the singleton guarantee or wasting expensive resources. Double-checked locking without proper memory-visibility guarantees (volatile or equivalent) can even return a partially constructed object.

5. Signal handlers and interrupts. In C, a signal handler that calls non-reentrant functions like malloc or printf can corrupt internal state if the main program was in the middle of the same function. Only async-signal-safe functions should be used in handlers.

Why Races Are Security Problems, Not Just Bugs
Attackers can deliberately widen the race window (by filling the disk, consuming CPU, or sending floods of parallel requests) and can retry thousands of times until timing lines up. A one-in-a-million window becomes reliable within seconds under automation. The consequences map to the CIA triad: integrity (corrupted balances, overwritten files), confidentiality (data leaking across sessions), and availability (deadlocks and crashes).

Designing for Thread Safety: Defensive Strategies
Avoid shared mutable state. The simplest thread-safe code shares nothing. Prefer immutable objects, pure functions, and message passing between workers.
Confine state. Keep data in local variables, stack frames, or thread-local storage so no other thread can reach it.
Make operations atomic. Use atomic types (AtomicInteger, Interlocked, std::atomic) or database constructs (transactions, SELECT FOR UPDATE, compare-and-swap, unique constraints) so the check and the act cannot be separated.
Synchronise properly. Protect every access to shared mutable data with the same lock, keep critical sections short, and always release locks in finally blocks or with RAII patterns. Acquire multiple locks in a consistent global order to prevent deadlock.
Use higher-level abstractions. Concurrent collections, thread pools, executor frameworks, actors, and channels are designed and tested by experts; hand-rolled synchronisation is error-prone.
Eliminate TOCTOU gaps. Operate on handles rather than names, use atomic create-if-not-exists operations, and re-validate at the point of use.
Apply idempotency and uniqueness. Unique tokens, idempotency keys, and database unique constraints turn duplicate racing requests into harmless no-ops or clean errors.
Fail securely. If a lock cannot be acquired or a transaction conflict occurs, deny the operation rather than proceeding.

Detecting Concurrency Flaws
Because they are non-deterministic, these defects demand specialised approaches:
Static analysis tools can flag CWE-362/367 patterns, unsynchronised access to shared fields, and inconsistent lock usage.
Dynamic race detectors such as ThreadSanitizer, Helgrind, or Java's built-in detection libraries instrument execution to catch unsynchronised accesses.
Stress and fuzz testing with many parallel threads increases the probability of hitting the window.
Code review should specifically ask: what is shared, who writes to it, and what guarantees ordering?
Penetration testers use tools like Burp Suite's Turbo Intruder or single-packet attack techniques to send many requests that arrive at essentially the same instant.
Formal methods and model checkers (TLA+, SPIN) can prove the absence of races in critical designs.

Mapping to the CSSLP Body of Knowledge
In Domain 4 the exam expects you to apply secure coding practices such as avoiding race conditions, using safe APIs, and managing shared resources. It also expects you to recognise relevant weakness classifications (CWE-362, CWE-366 race within a thread, CWE-367 TOCTOU, CWE-412 unrestricted externally accessible lock, CWE-833 deadlock) and to understand how secure design principles such as least privilege, complete mediation, economy of mechanism, and fail-safe defaults reduce the impact of concurrency errors. Complete mediation is especially relevant: every access must be checked at the moment of use, not merely once earlier.

Exam Tips: Answering Questions on Concurrency, Race Conditions and Thread Safety

Tip 1 – Spot the signature. If a scenario describes a check followed by an action with any gap between them, or the words "simultaneously", "at the same time", "intermittently", "multiple threads", "cannot reproduce", or "under load", think race condition or TOCTOU first.

Tip 2 – Know the difference between TOCTOU and general race conditions. TOCTOU is a specific race where a validated condition becomes stale before use. If the question mentions file checks, permission checks, or validation followed by use, the answer is TOCTOU (CWE-367). If it is two threads clobbering a counter or collection, it is a generic race or thread-safety flaw (CWE-362).

Tip 3 – The best fix removes the window, not shrinks it. Distractor answers often say "perform the check immediately before the use" or "add a delay" or "check twice". These reduce but never eliminate the race. Choose answers that make the check and act atomic, operate on the opened handle, use a transaction or lock, or avoid shared state altogether.

Tip 4 – Prefer design-level answers over patches. CSSLP is a lifecycle certification. When offered a choice between "add a mutex here" and "redesign to use immutable objects and stateless services", the exam usually rewards the architectural, root-cause solution, unless the question explicitly asks for the immediate code-level remediation.

Tip 5 – Recognise the four deadlock conditions. Questions may ask which condition a strategy breaks. Ordering locks consistently breaks circular wait; acquiring all locks at once breaks hold-and-wait; timeouts or lock pre-emption break no-pre-emption. Mutual exclusion is generally the one you cannot remove.

Tip 6 – Distinguish thread-safe from reentrant. Reentrant functions use no shared static state and can be safely interrupted; thread-safe functions may use shared state but protect it with synchronisation. A function can be thread-safe but not reentrant (it holds a lock that a recursive or signal-triggered call would deadlock on). The exam likes this subtlety.

Tip 7 – Connect to secure design principles. If an answer choice references complete mediation, least privilege, or fail-safe defaults in the context of a concurrency scenario, it is often correct, because these principles directly counter the impact of a race.

Tip 8 – Match the testing method to the flaw. For "which testing technique is most likely to uncover race conditions", choose dynamic analysis under concurrent load, race detectors, or stress testing, not unit tests or static code review alone (static analysis can help but questions often frame it as insufficient by itself).

Tip 9 – Watch for web-application framing. Coupon reuse, double withdrawal, bypassing a one-time password limit, or exceeding a quota via parallel requests are race conditions. The remediation is server-side atomic operations, idempotency keys, unique constraints, or pessimistic/optimistic locking—never client-side controls.

Tip 10 – Remember the CWE numbers that commonly surface. CWE-362 (race condition), CWE-367 (TOCTOU), CWE-833 (deadlock). You are unlikely to be asked to recite numbers, but recognising them in answer choices helps eliminate distractors.

Tip 11 – Eliminate answers that increase shared state. Options such as "store the result in a global variable" or "cache the validation result in a static field" make races worse, not better.

Tip 12 – Read for the security impact. When asked about consequences, map to privilege escalation (TOCTOU on privileged files), integrity loss (double-spend), confidentiality loss (session data crossing threads), or denial of service (deadlock). Pick the impact that best fits the described resource.

Quick Review Checklist
Concurrency = overlapping execution; race = outcome depends on uncontrolled timing.
TOCTOU = check, gap, use; fix by operating on handles and making check-and-act atomic.
Thread safety strategies: immutability, confinement, atomic operations, correct locking, high-level concurrency libraries.
Deadlock needs mutual exclusion, hold-and-wait, no pre-emption, circular wait; break one to prevent it.
Attackers can widen windows and automate retries; treat every race as exploitable.
Detect with race detectors, stress testing, static analysis, and targeted code review.
On the exam, choose answers that eliminate the window, reduce shared mutable state, and apply complete mediation at the point of use.
Test mode:
More Concurrency, Race Conditions and Thread Safety questions
18 questions (total)