Security and Resource Safety
Attackers love concurrency bugs because they can trigger them on purpose: send two requests at exactly the same time to spend a coupon twice, or open thousands of slow connections to tie up every worker.
Learning Objectives
Prevent resource exhaustion: unbounded tasks and queues, thread-pool and event-loop starvation, slow-client denial of service.
Ensure cancellation and cleanup correctness; avoid deadlocks in security-sensitive paths.
Prevent race conditions in authorization checks, TOCTOU vulnerabilities and file/temporary-resource races; protect shared credentials, secrets in task context and cross-request context leakage.
Handle unsafe callbacks and re-entrancy, locking around untrusted operations, protocol request smuggling concepts, backpressure and connection limits, timeout budgets and safe shutdown.
Understand concurrency in cryptographic libraries, thread-safe random number generation, secure session state, dependency/runtime vulnerabilities, and threat modeling of concurrent systems.
Plain-Language Explanation
Attackers love concurrency bugs because they can trigger them on purpose: send two requests at exactly the same time to spend a coupon twice, or open thousands of slow connections to tie up every worker. This module looks at concurrency from a defender's point of view — every example is a small, harmless demonstration of a problem *and* its fix.
Precise Technical Explanation
Every concurrent resource — threads, tasks, connections, memory, file descriptors — must be bounded, because an attacker controls how much work arrives.
Unbounded task creation create_task/go/Task.Run per incoming item without limits → memory exhaustion. Cap with semaphores or pools (demonstrated below).
Module 8: bound queues, reject excess.
Thread pool starvation Module 15/17: blocking calls let a few slow requests stall everything.
Module 11: one expensive request (ReDoS, huge JSON) blocks all others.
Denial of service through slow clients Slowloris-style clients send requests byte by byte to hold connections open; defend with header/body read timeouts, minimum data rates and per-IP connection limits (reverse proxies help).
Cancellation and cleanup correctness If cancellation skips cleanup, resources leak (connections, locks, temp files) — eventually a DoS. Test cancellation paths (Module 24).
Deadlocks in security-sensitive paths A deadlock in authentication, session or audit code turns into an outage attackers can trigger; keep such paths lock-light with timeouts.
Race conditions in authorization checks "Check permission/limit, then act" with a gap allows double-spending coupons, exceeding quotas, or acting after revocation. Make check and act atomic in the database (conditional update, unique constraint) — demonstrated below.
Checking a file (permissions, existence, type) and then using it by *name* lets an attacker swap the file (symlink) in between. Use handles: open first, then check with fstat; O_NOFOLLOW; openat relative to trusted directories.
File and temporary-resource races Predictable temp names (/tmp/app.123) can be pre-created by attackers. Use atomic exclusive creation (O_CREAT | O_EXCL, tempfile.mkstemp, NamedTemporaryFile) — demonstrated below.
Shared mutable credentials Rotating credentials shared by many threads must be swapped atomically (immutable credential object behind an atomic reference); never mutate in place.
Secret propagation through task context Context variables/AsyncLocal/MDC propagate into child tasks and logs — avoid putting secrets there, or mark them redacted.
Cross-request context leakage Thread-locals in pooled threads keep the previous request's data (user ID, tenant) unless cleared; the next request on that thread may act as the wrong user (demonstrated below); use per-task context (contextvars) or clear in finally.
Unsafe callbacks and re-entrancy Invoking untrusted or user-supplied callbacks while holding a lock or mid-update allows re-entrant calls to observe or corrupt broken invariants (the DAO smart-contract re-entrancy exploit is a famous example in a different domain). Complete state updates before calling out; don't hold locks during callbacks.
Locking around untrusted operations Never hold a lock while doing I/O to or computation on attacker-controlled input (slow parsing, remote calls) — an attacker can hold your lock.
Async request smuggling concepts where applicable to protocol handling
When a front-end proxy and a back-end server disagree on where one HTTP request ends (Content-Length vs Transfer-Encoding, HTTP/2→1 downgrades), an attacker can "smuggle" a request that is processed in another client's context. Defences: consistent parsing, rejecting ambiguous requests, HTTP/2 end-to-end. (Conceptual only.) Backpressure and connection limits
Per-client and global connection limits, request size limits, and backpressure keep attackers from consuming unbounded resources.
Every external wait has a deadline (Module 14) — slow dependencies, slow clients and slow disks all become bounded.
Shutdown must not skip audit logging, flushing of security events or revocation propagation; drain with deadlines.
Concurrency in cryptographic libraries Some crypto objects (cipher contexts, Mac, MessageDigest in Java, OpenSSL EVP_*_CTX) are not thread-safe — create one per thread/task or per operation; shared keys are fine if immutable.
Thread-safe random number generation Use cryptographically secure generators for secrets (secrets, SecureRandom, RandomNumberGenerator, crypto.getRandomValues); avoid sharing non-thread-safe PRNG instances (Java Random is thread-safe but contended — ThreadLocalRandom for non-secret use).
Secure session state Session updates from concurrent requests (two tabs) must not lose security-relevant changes (logout, privilege drop) — use versioned or server-side atomic updates; invalidate sessions atomically.
Dependency/runtime vulnerabilities Concurrency bugs in runtimes and libraries (HTTP/2 Rapid Reset, CVE-2023-44487; data races in parsers) — keep dependencies updated and limits configured.
Threat modeling concurrent systems For each shared resource and check: what if two requests arrive at once? What if one is very slow? What if the client cancels halfway? What if a dependency hangs? What if the same message arrives twice?
Why It Matters in Real Systems
Race-condition vulnerabilities have enabled gift-card double spending, limit bypasses at banks and crypto exchanges, file-system privilege escalations (TOCTOU), and the 2023 HTTP/2 Rapid Reset DDoS. Resource bounds and atomic checks are basic security hygiene.
Key Terms
Time-of-check to time-of-use race Atomic "create only if absent"
Data from one request visible to another DoS holding connections with slow requests
Request boundary confusion between servers Atomic conditional update Check and act in one database operation
Syntax and API Overview
os.open(path, os.O_CREAT | os.O_EXCL | os.O_WRONLY) # atomic create-if-absent
db.execute("UPDATE coupons SET used = 1 WHERE code = ? AND used = 0") # check-and-act in one statement
contextvars.ContextVar("user") # per-task context instead of thread-locals
asyncio.Semaphore(100) # cap concurrent workComplete Code Example
A coupon double-redemption race and its database-level fix, exclusive file creation, thread-local leakage in a pool versus contextvars, and capped task creation (CPython 3.13.1, SQLite; harmless, self-contained):
import asyncio
import contextvars
import os
import sqlite3
import tempfile
import threading
from concurrent.futures import ThreadPoolExecutor
# 1. authorization race: "is the coupon unused?" then "mark used" -> double redemption
path = os.path.join(tempfile.mkdtemp(), "shop.db")
setup = sqlite3.connect(path)
setup.executescript("CREATE TABLE coupons(code TEXT PRIMARY KEY, used INT); INSERT INTO coupons VALUES ('SAVE50', 0), ('SAVE60', 0);")
setup.commit(); setup.close()
def redeem_unsafe(code, barrier, results):
con = sqlite3.connect(path, timeout=5)
unused = con.execute("SELECT used FROM coupons WHERE code = ?", (code,)).fetchone()[0] == 0
barrier.wait() # both requests pass the check before either acts
if unused:
con.execute("UPDATE coupons SET used = 1 WHERE code = ?", (code,)); con.commit()
results.append("discount applied")
con.close()
def redeem_safe(code, barrier, results):
con = sqlite3.connect(path, timeout=5)
barrier.wait()
cur = con.execute("UPDATE coupons SET used = 1 WHERE code = ? AND used = 0", (code,)) # atomic check-and-act
con.commit()
if cur.rowcount == 1:
…coupon redeemed by 2 simultaneous requests (check-then-act): discount applied 2 time(s)
coupon redeemed by 2 simultaneous requests (conditional UPDATE): discount applied 1 time(s)
O_CREAT|O_EXCL from 4 threads: ['created', 'refused: already exists', 'refused: already exists', 'refused: already exists']
thread-local: anonymous request on a reused thread saw user = 'alice' (leaked from the previous request)
contextvars (fresh context per request): anonymous request saw user = None
1,000 requested fetches, concurrency capped by a semaphore: peak in flight 10Line-by-Line Explanation
Coupon race: both requests read used = 0, wait at the barrier (forcing the worst-case interleaving that attackers aim for), then both apply the discount. The conditional UPDATE … AND used = 0 lets the database perform check and act atomically; only one update matches.
Exclusive create: O_CREAT | O_EXCL fails if the file exists — exactly one of four racing threads creates it, and a pre-planted file would be refused too.
Thread-local leak: the single pooled thread keeps local.user = "alice"; the next, anonymous request on that thread sees Alice. Running each request in a fresh copy of the context keeps contextvars isolated.
Bounded fan-out: the semaphore caps in-flight work at 10 no matter how many items the request contains.
Output
Shown above; deterministic (the barrier forces the racy interleaving).
Interleaving or State Trace
UPDATE used = 1 → discount UPDATE used = 1 → discount (stale check)
Correctness and Safety Analysis
Security-relevant invariants (a coupon is used once; a file is created once; a request sees only its own identity; concurrency is bounded) are enforced by atomic primitives in the component that owns the state — the database, the file system, the runtime — not by application-level check-then-act.
Common Mistakes and Misconceptions
Application-level "check then update" for balances, quotas and coupons.
os.path.exists before creating files.
Thread-locals without clearing in pooled environments.
Unbounded fan-out from user input; no request size or connection limits.
Holding locks while calling untrusted code.
Failure Modes and Debugging Steps
Reproduce suspected races with barriers or parallel request tools (e.g. sending simultaneous requests in tests); audit every check-then-act on security state; review thread-local and context usage; load-test with slow clients.
Performance and Scalability Implications
Atomic database updates are usually *faster* than read-then-write; bounds and timeouts protect capacity for legitimate users.
Security Considerations
This module is security; examples are defensive and harmless.
Platform and Runtime Caveats
O_EXCL semantics on network file systems (older NFS) may not be atomic; Windows uses CREATE_NEW. Database isolation levels determine whether separate SELECT/UPDATE can race — conditional updates and constraints are robust across levels.
Real-World Applications
Payment and promotion systems (conditional updates, idempotency), file-handling daemons (openat, O_NOFOLLOW), web frameworks clearing request context, API gateways with connection and rate limits.
Best Practices
Enforce security invariants atomically where the state lives.
Use exclusive creation and handle-based file checks.
Keep request context per task; clear pooled thread state.
Bound every resource; put deadlines on every wait.
Threat-model "two at once, one very slow, one cancelled, one duplicated".
Exercises
E1 · Beginner. Why is if not os.path.exists(p): open(p, "w") unsafe in a shared directory?
Answer: Another process can create (or symlink) p between the check and the open; use open(p, "x") / O_CREAT|O_EXCL.
E2 · Intermediate. Rewrite "if user.balance >= price: user.balance -= price" safely in SQL.
Answer: UPDATE users SET balance = balance - :price WHERE id = :id AND balance >= :price and check that exactly one row was updated.
E3 · Advanced challenge. A service stores the current user in a thread-local and uses virtual threads. Is the leak in the example still possible?
Answer: Less likely, since virtual threads are not pooled (one per task) — but thread-locals copied into child threads, carrier-thread confusion in native code, or any pooled executor elsewhere can still leak. Prefer scoped/explicit context (Java ScopedValue, passing a request object).
Quiz
? What makes the conditional UPDATE safe against double redemption?
+ The database checks used = 0 and sets used = 1 in one atomic statement. :: Only one request's update matches.
- SQLite is single-threaded. :: The race exists with separate SELECT/UPDATE.
- The barrier. :: The barrier only forces the worst-case timing.
- Commit order. :: The condition in the UPDATE is what matters.
? Which defends against TOCTOU on files?
+ Opening the file first and checking the opened handle (fstat), or O_CREAT|O_EXCL :: Operate on handles atomically.
- Checking permissions twice :: Still separated from use.
- Adding sleep between check and use :: Widens the window.
- Using a longer file name :: Predictability is not the main issue.
? Which are resource-exhaustion defences? (select all that apply)
+ Semaphores capping concurrent work :: Bounded fan-out.
+ Header/body read timeouts :: Defeat slow clients.
- Unbounded queues :: They enable exhaustion.
+ Per-client connection limits :: Prevent one client from taking everything.Summary
Concurrency is an attack surface: bound every resource, put deadlines on every wait, make security checks atomic with the actions they guard (conditional updates, exclusive creation, handles), keep request context per task, avoid locks and partial states around untrusted calls, use thread-safe crypto and secure randomness, and threat-model simultaneous, slow, cancelled and duplicated requests.
Further Reading
OWASP: "Race Conditions", "Denial of Service Cheat Sheet".
James Kettle, "Smashing the state machine: the true potential of web race conditions" (PortSwigger Research, 2023).
CWE-362 (Race Condition), CWE-367 (TOCTOU), CWE-400 (Uncontrolled Resource Consumption).
RFC 9113 §10.5 and CVE-2023-44487 (HTTP/2 Rapid Reset).