Safeguard
Industry Analysis

Race Conditions (TOCTOU) in Application Code

From Dirty COW to runc's CVE-2021-30465, TOCTOU race conditions keep slipping past code review. Here's why they're invisible to standard tooling — and how to catch them.

Vikram Iyer
Security Researcher
7 min read

On October 19, 2016, a nine-year-old bug in the Linux kernel's memory subsystem was assigned CVE-2016-5195 and a name that would outlive most vulnerabilities: Dirty COW. The flaw let a local attacker write to files they should only be able to read — including files owned by root — by exploiting the tiny gap between when the kernel checked a memory mapping and when it actually used it. That gap is the essence of a Time-Of-Check-Time-Of-Use (TOCTOU) race condition, and it is not a relic of 2016. The same pattern resurfaced in runc (CVE-2019-5736, CVE-2021-30465), in Kubernetes volume handling, and in countless checkout, coupon, and withdrawal endpoints that never touch a kernel at all. TOCTOU bugs are dangerous precisely because static analyzers see well-formed code, tests pass in isolation, and the vulnerability only appears when two threads, processes, or requests interleave in exactly the wrong order.

What Is a TOCTOU Race Condition, Exactly?

A TOCTOU vulnerability exists whenever code validates a condition (the "check") and then acts on a resource (the "use") without guaranteeing the resource hasn't changed in between — and an attacker can win that window. The canonical example is a permission check on a file path followed by a separate open() call: between the access() check and the open() use, an attacker with a symlink can swap the target file, and the program ends up operating on a completely different resource than the one it validated. This is distinct from a generic "race condition" in that TOCTOU specifically concerns state that is checked once and trusted for a subsequent operation. The pattern shows up in filesystem calls, database read-then-write sequences, API rate limiters, and even hardware-level speculative execution paths. What makes it exploitable in practice is timing: an attacker doesn't need to win every attempt, just one, and automated race tools can fire thousands of concurrent requests in milliseconds to widen a microsecond gap into a reliable exploit.

Why Did Dirty COW Become the Textbook TOCTOU Example?

Dirty COW earned its reputation because it combined a nine-year dormancy period with trivial local-to-root escalation. CVE-2016-5195 lived in the Linux kernel's copy-on-write logic from 2007 until its 2016 disclosure, affecting essentially every Linux distribution in production at the time, including Android devices running affected kernel versions. The race existed between the kernel checking whether a memory mapping was writable and the point where it actually performed the copy-on-write operation; a well-timed second thread could trick the kernel into writing to a read-only, root-owned mapping. Exploits reached public proof-of-concept status within days of disclosure, and the bug was rated high severity across virtually every major distribution's advisory. Dirty COW mattered to the industry not because it was novel in concept — TOCTOU races were already textbook material by 2016 — but because it proved a race condition could sit unnoticed through nine years of kernel code review, fuzzing, and static analysis.

How Do TOCTOU Bugs Slip Into Container Runtimes Like runc?

They slip in because container runtimes perform exactly the check-then-use sequence TOCTOU thrives on: verifying a path or file descriptor belongs to the container, then acting on it moments later while the container's own filesystem is technically under attacker influence. CVE-2019-5736, disclosed February 11, 2019, let a malicious container overwrite the host runc binary by racing the runtime's own /proc/self/exe handling during docker exec, enabling full host compromise from inside a container that was never supposed to have that reach. Two years later, CVE-2021-30465 — explicitly documented as a TOCTOU vulnerability — showed that runc's volume-mount validation checked a symlink target and then mounted it in a separate step, letting an attacker swap the target in between and mount arbitrary host paths into a container. Both CVEs carried CVSS scores in the high range and both were fixed not by adding more checks, but by collapsing the check and the use into a single atomic operation (using file descriptors instead of paths, for instance) so there was no window left to race.

Why Are Business-Logic Race Conditions a Growing Risk Beyond the Kernel?

They're growing because the same check-then-use pattern appears in ordinary application code every time a developer verifies a balance, a stock count, or a coupon's redemption status before committing a database write. A widely cited pattern in bug bounty writeups involves gift card and wallet balance checks: an application reads "balance = $50," authorizes a $50 purchase, and only decrements the balance afterward — if a second request is fired in the same window, both purchases can be authorized against the same $50, since the second check also reads the pre-decrement balance. Security researcher James Kettle's 2023 published research on "smashing the state machine" demonstrated single-packet race attack techniques that reduced network jitter enough to reliably win these races in production web applications across multiple bug bounty targets, turning what used to require lucky timing into a repeatable technique. Because these races live in ordinary business logic — checkout flows, invite systems, password reset tokens — they don't require memory corruption or kernel access, which means they bypass most tooling built around traditional injection and memory-safety classes entirely.

Why Are TOCTOU Bugs So Hard to Catch With Standard AppSec Tooling?

They're hard to catch because static application security testing (SAST) analyzes code paths individually and has no model of concurrent execution across requests, threads, or processes, so a check and a use that are individually correct never trigger a rule. Dynamic testing tools generally send one request at a time and evaluate the response, which is exactly the wrong shape for a bug that only manifests under concurrent load. Unit and integration tests almost always run functions serially, so a TOCTOU bug can have full test coverage and a green CI pipeline while remaining fully exploitable. Detecting these issues typically requires either dedicated race-condition fuzzers that fire concurrent requests at shared endpoints, code review specifically trained to spot check-then-use patterns on shared mutable state (files, balances, counters, tokens), or runtime instrumentation that flags time-of-check to time-of-use gaps as code executes. This is also why TOCTOU bugs persist for years in mature, well-reviewed codebases like the Linux kernel and major container runtimes — the review process that catches most vulnerability classes structurally cannot see this one without concurrency-aware analysis.

How Safeguard Helps

Safeguard's software supply chain security platform is built around the reality that the bugs most likely to reach production undetected are the ones standard tooling structurally can't see — and TOCTOU race conditions are a leading example. Safeguard continuously maps the CVE landscape, including runc-class TOCTOU disclosures like CVE-2019-5736 and CVE-2021-30465, against your actual dependency and container base image graph, so a new race-condition advisory in a runtime or library you ship is flagged against your real exposure within hours of publication rather than surfacing weeks later in a generic feed. For custom application code, Safeguard's code and dependency analysis pipeline flags check-then-use patterns on shared mutable state — file paths, balances, tokens, counters — during the build and PR stage, so a developer reviewing a pull request sees a race-condition risk annotation before it ships, not after a bug bounty report or an incident. Because TOCTOU exploitability is a function of your deployment environment (thread models, concurrency limits, replica counts) as much as the code itself, Safeguard correlates flagged patterns with your actual runtime configuration to prioritize the handful that are genuinely reachable over the hundreds that are theoretically possible but practically inert. The result is a supply chain security posture that treats concurrency-dependent vulnerabilities as a first-class category — tracked, triaged, and prioritized with the same rigor as injection or memory-safety findings — instead of a blind spot that only becomes visible after an attacker finds the window first.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.